/*
 * UTIL/DSYNCGROUPS.C
 *
 * dsyncgroups -h remotehost [-a] [-o] [-D] [-N[B,E,R]] [-g] [-i] [-m] [-w wildcard] [-p port] [-f/F dactive.kp]
 *
 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution
 *    for specific rights granted.
 */

#include "defs.h"

typedef struct Group {
    struct Group *gr_Next;
    int		gr_State;
    int		gr_StartNo;
    int		gr_StartDiff;
    int		gr_EndNo;
    int		gr_EndDiff;
    int		gr_SynNo;
    int		gr_SynDiff;
    int		gr_NoCTS;
    int		gr_NoLMTS;
    char	*gr_GroupName;
    char	*gr_Flags;
    char	*gr_Description;
} Group;

#define GRF_DESCRIPTION	0x0001
#define GRF_STARTNO	0x0002
#define GRF_ENDNO	0x0004
#define GRF_FLAGS	0x0008
#define GRF_SYNNO	0x0010
#define GRF_LMTS	0x0020	/* last modified time stamp */
#define GRF_CTS		0x0040	/* creation time stamp */
#define GRF_FROMLOCAL	0x0800
#define GRF_NEW		0x1000
#define GRF_FROMREMOTE	0x2000
#define GRF_MODIFIED	0x8000

#define GHSIZE		1024
#define GHMASK		(GHSIZE-1)

FILE *Fi;
FILE *Fo;
Group *GHash[GHSIZE];
KPDB  *KDBActive;

int SyncOverwriteOpt = 0; /* allow remote info to replace existing info	*/
int SyncDeleteOpt = 0;	/* delete local info not present on remote	*/
int SyncGroupsOpt = 0;	/* synchronize newsgroup names 			*/
int SyncDescrOpt = 0;	/* synchronize newsgroup descriptions		*/
int SyncModStatusOpt = 0; /* synchronize newsgroup moderation status	*/
int SyncArtNumbersBegOpt = 0; /* synchronize article numbers	*/
int SyncArtNumbersEndOpt = 0; /* synchronize article numbers	*/
int SyncArtNumbersSynOpt = 0; /* synchronize article numbers	*/
int SyncArtNumbersRange = 0;  /* relative sync beginning based on range	*/
int SyncArtNumbersAddition = 0;  /* add this when syncing ending article no. */
int SyncArtNumbersMaxPerc = 0;	/* max % value to increase upwards	*/
int VerboseOpt = 0;
int ForReal = 1;
char *SyncModStatusFlag = NULL;
char *BindHost = NULL;
char *BindPort = NULL;

void Usage(void);
void processLine(char *cmd, char *wild, int *count, int *ok);
char *allocTmpCopy(const char *buf, int bufLen);
Group *EnterGroup(int pass, const char *, int, int, int, int, int, const char *, const char *);
int ConnectToHost(const char *host, int port);
int CommandResponse(const char *cmd, char **pres);
int hash(const char *str);
int SetField(char **pptr, const char *str);

void
Usage(void)
{
    fprintf(stderr, "dsyncgroups -h remotehost | -G filename [-p port] [-B hostname] [-w wild] [-f/F file.kp] [-a] [-o] [-g] [-i] [-m] [-M c] [-D] [-N[B,E,X,R]] [-X[EX]] [-n#] [-v] [-z]\n");
    fprintf(stderr, "  -h remotehost = the host that we connect to\n");
    fprintf(stderr, "  -G filename   = specify an active file to use as input\n");
    fprintf(stderr, "  -p port       = port on remote host (default: 119)\n");
    fprintf(stderr, "  -B bindhost   = interface to bind to\n");
    fprintf(stderr, "  -w wild       = only update groups matching wildmat (default: *)\n");
    fprintf(stderr, "  -f file.kp    = specify the active to update (Default: dactive.kp)\n");
    fprintf(stderr, "  -F file.kp    = create active file if it doesn't exist\n");
    fprintf(stderr, "  -a            = shortcut for -o, -g, -i and -m\n");
    fprintf(stderr, "  -o            = overwrite description\n");
    fprintf(stderr, "  -g            = add missing groups\n");
    fprintf(stderr, "  -i            = sync newsgroup description\n");
    fprintf(stderr, "  -m            = update moderation status\n");
    fprintf(stderr, "  -M c          = update moderation status to the specified flag\n");
    fprintf(stderr, "  -D            = delete local groups in not on remote\n");
    fprintf(stderr, "  -N            = sync begin and end numbers\n");
    fprintf(stderr, "  -NB           = sync begin numbers\n");
    fprintf(stderr, "  -NE           = sync end numbers\n");
    fprintf(stderr, "  -NX           = sync end numbers but only upwards\n");
    fprintf(stderr, "  -u            = Log into remotehost using this password\n");
    fprintf(stderr, "  -U            = Log into remotehost using this username\n");
    fprintf(stderr, "  -XE           = sync NX field - feeder sync\n");
    fprintf(stderr, "  -XX           = sync NX but only upwards\n");
    fprintf(stderr, "  -n#           = if we sync end number then increase local value by remote+#\n");
    fprintf(stderr, "  -N#           = don't sync end number  more than #\n");
    fprintf(stderr, "  -v            = be more verbose about what is being done\n");
    fprintf(stderr, "  -z            = don't actually make changes\n");
    exit(1);
}
int
main(int ac, char **av)
{
    int i;
    int ocreate = 0;
    char *host = NULL;
    char *wild = NULL;
    char *dbfile = NULL;
    char *grpfile = NULL;
    char *username = NULL;
    char *password = NULL;
    int port = 119;

    LoadDiabloConfig(ac, av);

    for (i = 1; i < ac; ++i) {
	char *p;
	char *ptr = av[i];

	if (*ptr != '-') {
	    fprintf(stderr, "Unexpected argument: %s\n", ptr);
	    exit(1);
	}
	ptr += 2;
	switch(ptr[-1]) {
	case 'a':
	    SyncOverwriteOpt = 1;
	    SyncGroupsOpt = 1;
	    SyncDescrOpt = 1;
	    SyncModStatusOpt = 1;
	    break;
	case 'B':
	    if (*ptr == 0)
		ptr = av[++i];
	    if ((p = strrchr(ptr, ':')) != NULL) {
		*p++ = 0;
		BindPort = p;
		BindHost = strdup(SanitiseAddr(ptr));
	    } else {  
		BindHost = strdup(SanitiseAddr(ptr));
	    }
	    break;
	case 'C':
	    if (*ptr == 0)
		++i;
	    break;
	case 'D':
	    SyncDeleteOpt = 1;
	    break;
	case 'd':
	    DebugOpt = (*ptr) ? strtol(ptr, NULL, 0) : 1;
	    break;
	case 'F':
	    ocreate = O_CREAT;
	    /* fall through */
	case 'f':
	    dbfile = (*ptr) ? ptr : av[++i];
	    break;
	case 'G':
	    grpfile = (*ptr) ? ptr : av[++i];
	    break;
	case 'g':
	    SyncGroupsOpt = 1;
	    break;
	case 'i':
	    SyncDescrOpt = 1;
	    break;
	case 'h':
	    host = (*ptr) ? ptr : av[++i];
	    break;
	case 'm':
	    SyncModStatusOpt = 1;
	    break;
	case 'M':
	    SyncModStatusOpt = 1;
	    SyncModStatusFlag = (*ptr) ? ptr : av[++i];
	    break;
	case 'N':
	    if (*ptr == 0) {
		SyncArtNumbersBegOpt = 1;
		SyncArtNumbersEndOpt = 1;
	    }
	    while (*ptr) {
		if (*ptr == 'B')
		    SyncArtNumbersBegOpt = 1;
		if (*ptr == 'E')
		    SyncArtNumbersEndOpt = 1;
		if (*ptr == 'X')
		    SyncArtNumbersEndOpt = 2;
		if (*ptr == 'R')
		    SyncArtNumbersRange = 1;
		++ptr;
	    }
	    break;
	case 'n':
	    SyncArtNumbersAddition = strtol((*ptr) ? ptr : av[++i], NULL, 0);
	    break;
	case 'o':
	    SyncOverwriteOpt = 1;
	    break;
	case 'P':
	    SyncArtNumbersMaxPerc = strtol((*ptr) ? ptr : av[++i], NULL, 0);
	    break;
	case 'p':
	    ptr = (*ptr) ? ptr : av[++i];
	    port = strtol(ptr, NULL, 0);
	    break;
	case 'U':
	    username = (*ptr) ? ptr : av[++i];
	    break;
	case 'u':
	    password = (*ptr) ? ptr : av[++i];
	    break;
	case 'w':
	    wild = (*ptr) ? ptr : av[++i];
	    break;
	case 'X':
	    if (*ptr == 0) {
		SyncArtNumbersSynOpt = 1;
	    }
	    while (*ptr) {
		if (*ptr == 'E')
		    SyncArtNumbersSynOpt = 1;
		if (*ptr == 'X')
		    SyncArtNumbersSynOpt = 2;
		++ptr;
	    }
	    break;
	case 'V':
	    PrintVersion();
	    break;
	case 'v':
	    VerboseOpt = 1;
	    break;
	case 'z':
	    ForReal = 0;
	    break;
	default:
	    fprintf(stderr, "Unknown option: %s\n", ptr - 2);
	    break;
	}
    }
    if (host == NULL && grpfile == NULL) {
	if (ac == 1)
	    Usage();
	fprintf(stderr, "Missing host/filename option\n");
	exit(1);
    }
    if (port <= 0) {
	fprintf(stderr, "Illegal port option\n");
	exit(1);
    }
    if (wild && strlen(wild) > 128) {
	fprintf(stderr, "Wildcard too large\n");
	exit(1);
    }

    /*
     * open dactive.kp file
     */

    if (VerboseOpt)
	printf("%s local active file\n",
				ocreate ? "Loading/creating" : "Loading");

    if (dbfile) {
	KDBActive = KPDBOpen(dbfile, O_RDWR|ocreate);
    } else {
	KDBActive = KPDBOpen(PatDbExpand(ReaderDActivePat), O_RDWR|ocreate);
    }
    if (KDBActive == NULL) {
	printf("Unable to open/create dactive.kp\n");
	exit(1);
    }

    /*
     * scan dactive.kp
     */

    {
	int recLen;
	int recOff;
	int saveSyncGroupsOpt = SyncGroupsOpt;

	SyncGroupsOpt = 1;	/* force new Group structures to be created */

	for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
	     recOff;
	     recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
	) {
	    int groupLen;
	    int flagsLen;
	    const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
	    const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
	    const char *flags = KPDBGetField(rec, recLen, "S", &flagsLen, "y");
	    const char *desc  = KPDBGetFieldDecode(rec, recLen, "GD", NULL, NULL);
	    int begNo = strtol(KPDBGetField(rec, recLen, "NB", NULL, "-1"), NULL, 10);
	    int endNo = strtol(KPDBGetField(rec, recLen, "NE", NULL, "-1"), NULL, 10);
	    int synNo = strtol(KPDBGetField(rec, recLen, "NX", NULL, "-1"), NULL, 10);
	    int noCTS = !(int)strtoul(KPDBGetField(rec, recLen, "CTS", NULL, "0"), NULL, 16);
	    int noLMTS = !(int)strtoul(KPDBGetField(rec, recLen, "LMTS", NULL, "0"), NULL, 16);
	    Group *grp;

	    if (group)
		group = allocTmpCopy(group, groupLen);
	    if (flags)
		flags = allocTmpCopy(flags, flagsLen);
	    if (desc)
		desc = allocTmpCopy(desc, strlen(desc));

	    /*
	     * ignore bad group or group that does not match the wildcard
	     */

	    if (group == NULL)
		continue;
	    if (wild && WildCmp(wild, group) != 0)
		continue;

	    grp = EnterGroup(
		1,
		group,
		begNo,
		endNo,
		synNo,
		noCTS,
		noLMTS,
		flags,
		desc
	    );
	    grp->gr_State &= ~(GRF_NEW|GRF_MODIFIED);
	    grp->gr_State &= ~(GRF_CTS|GRF_LMTS);
	    grp->gr_State |= GRF_FROMLOCAL;
	}

	SyncGroupsOpt = saveSyncGroupsOpt;
    }

    /*
     * Connect to remote host
     */

    if (host != NULL)
    {
	int fd;

	if (VerboseOpt)
	    printf("Connecting to %s:%d\n", host, port);

	fd = ConnectToHost(host, port);

	if (fd < 0) {
	    fprintf(
		stderr, 
		"Unable to connect to %s:%d (%s)\n", 
		host, 
		port, 
		strerror(errno)
	    );
	    exit(1);
	}
	Fi = fdopen(fd, "r");
	Fo = fdopen(dup(fd), "w");
    } else {
	if (VerboseOpt)
	    printf("Syncing with %s\n", grpfile);
	Fi = fopen(grpfile, "r");
	if (Fi == NULL) {
	    printf("Unable to open %s\n", grpfile);
	    exit(1);
	}
	Fo = NULL;
    }

    /*
     * startup
     */

    if (host != NULL)
    {
	char *line;

	switch(CommandResponse(NULL, &line)) {
	case 200: case 201:
	    printf("Successful connection: %s\n", line);
	    break;
	default:
	    printf("Initial Response: %s\n", line);
	    exit(1);
	}
    }

    /*
     * login
     */

    if (host != NULL && username != NULL) {
	char *line;
	char cmd[4096];

	sprintf(cmd, "authinfo user %s",username);
	switch(CommandResponse(cmd, &line)) {
	    case 381:
		printf("authinfo accepted: %s\n", line);
		break;
	    default:
		printf("authinfo denied: %s\n", line);
		exit(1);
	}
    }

    if (host != NULL && password != NULL) {
	char *line;
	char cmd[4096];

	sprintf(cmd, "authinfo pass %s",password);
	switch(CommandResponse(cmd, &line)) {
	    case 281:
		printf("authinfo accepted: %s\n", line);
		break;
	    default:
		printf("authinfo denied: %s\n", line);
		exit(1);
	}
    }

    /*
     * mode reader
     */

    if (host)
    {
	char *line;

	switch(CommandResponse("mode reader", &line)) {
	case 200: case 201:
	    printf("Entering reader mode: %s\n", line);
	    break;
	default:
	    printf("Unable to switch to reader mode: %s\n", line);
	    printf("Going on anyway\n");
	    break;
	}
    }

    /*
     * list active [wild]
     */

    if (host)
    {
	char *line;
	char cmd[4096];
	int count = 0;
	int ok = 0;

	sprintf(cmd, "list active%s%s", ((wild) ? " " : ""), ((wild) ? wild : ""));
	switch(CommandResponse(cmd, &line)) {
	case 215:
	    while (fgets(cmd, sizeof(cmd), Fi) != NULL) {
		processLine(cmd, wild, &count, &ok);
		if (ok)
		    break;
	    }
	    break;
	default:
	    printf("list active command failed: %s\n", line);
	    break;
	}
	printf("%d groups returned by list active\n", count);
	if (ok == 0) {
	    printf("Did not receive . terminator to list active command\n");
	    exit(1);
	}
    } else {
	char cmd[4096];
	int count = 0;
	while (fgets(cmd, sizeof(cmd), Fi) != NULL)
	    processLine(cmd, wild, &count, NULL);
	printf("%d groups found\n", count);
    }

    /*
     * list newsgroups [wild]
     */

   if (host && SyncDescrOpt)
    {
	char *line;
	char cmd[4096];
	int count = 0;
	int ok = 0;

	sprintf(cmd, "list newsgroups%s%s", ((wild) ? " " : ""), ((wild) ? wild : ""));
	switch(CommandResponse(cmd, &line)) {
	case 215:
	    while (fgets(cmd, sizeof(cmd), Fi) != NULL) {
		char *group;
		char *desc;
		Group *grp;

		if (strcmp(cmd, ".\r\n") == 0) {
		    ok = 1;
		    break;
		}
		group = strtok(cmd, " \t\r\n");
		desc  = strtok(NULL, "\r\n");

		/*
		 * ignore group that does not match the wildcard, even though
		 * the remote end is supposed to do this for us.
		 */

		if (wild && WildCmp(wild, group) != 0)
		    continue;

		if (group)
		grp = EnterGroup(
		    2,
		    group, 
		    -1,
		    -1,
		    -1,
		    -1,
		    -1,
		    NULL,
		    desc
		); else grp = NULL;
		if (grp)
		    grp->gr_State |= GRF_FROMREMOTE;
		++count;
	    }
	    break;
	default:
	    printf("list newsgroups command failed: %s\n", line);
	    break;
	}
	printf("%d groups returned by list newsgroups\n", count);
	if (ok == 0) {
	    printf("Did not receive . terminator to list newsgroups command\n");
	    exit(1);
	}
    }

    /*
     * close remote connections
     */

    if (host)
    {
	char *line;

	printf("All remote commands completed, sending quit\n");
	switch(CommandResponse("quit", &line)) {
	case 205:
	    printf("quit response ok\n");
	    break;
	default:
	    printf("error sending quit command: %s\n", line);
	    exit(1);
	}
    }

    fclose(Fi);
    if (Fo)
	fclose(Fo);

    printf("Writing to .kp file\n");
    fflush(stdout);

    /*
     * Writeback
     */

    {
	int i;
	int nowt = (int)time(NULL);

	for (i = 0; i < GHSIZE; ++i) {
	    Group *group;

	    for (group = GHash[i]; group; group = group->gr_Next) {
		/*
		 * If we have a new group not previously in the database,
		 * we only add it if SyncGroupsOpt is set.
		 */
		if (group->gr_State & GRF_NEW) {
		    if (SyncGroupsOpt == 0)
			continue;
		}

		/*
		 * If group did not come from remote and SyncDeleteOpt is set,
		 * delete the group.
		 */
		if (!(group->gr_State & GRF_FROMREMOTE) &&
		    (group->gr_State & GRF_FROMLOCAL) &&
		    SyncDeleteOpt
		) {
		    printf("Deleting %s\n", group->gr_GroupName);
		    if (ForReal)
			KPDBDelete(KDBActive, group->gr_GroupName);
		} else if (group->gr_State & (GRF_MODIFIED | GRF_NEW)) {
		    if (group->gr_State & GRF_NEW) {
			printf("Creating %s", group->gr_GroupName);
		    } else {
			printf("Updating %s", group->gr_GroupName);
		    }
		    if (group->gr_State & GRF_DESCRIPTION) {
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "GD", group->gr_Description, 0);
			if (VerboseOpt)
			    printf(" desc='%s'", group->gr_Description);
		    }
		    if (group->gr_State & (GRF_STARTNO | GRF_NEW)) {
			char startBuf[16];
			sprintf(startBuf, "%010d", group->gr_StartNo);
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "NB", startBuf, 0);
			if (VerboseOpt)
			    printf(" NB=%s(%+d)", startBuf, group->gr_StartDiff);
		    }
		    if (group->gr_State & (GRF_ENDNO | GRF_NEW)) {
			char endBuf[16];
			sprintf(endBuf, "%010d", group->gr_EndNo);
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "NE", endBuf, 0);
			if (VerboseOpt)
			    printf(" NE=%s(%+d)", endBuf, group->gr_EndDiff);
		    }
		    if (group->gr_State & (GRF_SYNNO | GRF_NEW)) {
			char synBuf[16];
			sprintf(synBuf, "%010d", group->gr_SynNo);
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "NX", synBuf, 0);
			if (VerboseOpt)
			    printf(" NX=%s(%+d)", synBuf, group->gr_SynDiff);
		    }
		    if (group->gr_State & GRF_CTS) {
			char ctsBuf[16];
			sprintf(ctsBuf, "%08x", nowt);
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "CTS", ctsBuf, 0);
			if (VerboseOpt)
			    printf(" CTS=%s", ctsBuf);
		    }
		    if (group->gr_State & GRF_LMTS) {
			char lmtsBuf[16];
			sprintf(lmtsBuf, "%08x", nowt);
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "LMTS", lmtsBuf, 0);
			if (VerboseOpt)
			    printf(" LMTS=%s", lmtsBuf);
		    }
		    if (group->gr_Flags && group->gr_State & GRF_FLAGS) {
			if (ForReal)
			    KPDBWriteEncode(KDBActive, group->gr_GroupName, "S", group->gr_Flags, 0);
			if (VerboseOpt)
			    printf(" S=%s",
				(group->gr_Flags ? group->gr_Flags : "y"));
		    }
		    printf("\n");
		}
	    }
	}
    }

    return(0);
}

void
processLine(char *cmd, char *wild, int *count, int *ok)
{
    char *group;
	char *gbeg;
	char *gend;
	char *flags;
	Group *grp;

	if (strcmp(cmd, ".\r\n") == 0) {
	    if (ok)
		*ok = 1;
	    return;
	}
	group = strtok(cmd, " \r\n");
	gend  = strtok(NULL, " \r\n");
	gbeg  = strtok(NULL, " \r\n");
	flags = strtok(NULL, " \r\n");

	/*
	 * ignore group that does not match the wildcard, even though
	 * the remote end is supposed to do this for us.
	 */

	if (wild && WildCmp(wild, group) != 0)
	    return;

	if (*gend == '-' || *gbeg == '-') {
	    printf("Invalid group begin or end number - skipping : %s\n", group);
	    return;
	}

	grp = EnterGroup(
	    2,
	    group, 
	    strtol(gbeg, NULL, 10), 
	    strtol(gend, NULL, 10), 
	    strtol(gend, NULL, 10), /* end repeated */
	    -1,
	    -1,
	    flags, 
	    NULL
	);

	/*
	 * mark group as coming from remote
	 */
	if (grp)
	    grp->gr_State |= GRF_FROMREMOTE;
	++*count;
}

char *
allocTmpCopy(const char *buf, int bufLen)
{
    static char *SaveAry[8];
    static int SaveCnt;
    char **pptr;

    SaveCnt = (SaveCnt + 1) % arysize(SaveAry);
    pptr = &SaveAry[SaveCnt];
    if (*pptr)
	free(*pptr);
    *pptr = malloc(bufLen + 1);
    memcpy(*pptr, buf, bufLen);
    (*pptr)[bufLen] = 0;
    return(*pptr);
}

Group *
EnterGroup(int pass, const char *groupName, int begNo, int endNo, int synNo, int noCTS, int noLMTS, const char *flags, const char *desc)
{
    Group **pgroup = &GHash[hash(groupName)];
    Group *group;

    while ((group = *pgroup) != NULL) {
	if (strcmp(groupName, group->gr_GroupName) == 0)
	    break;
	pgroup = &group->gr_Next;
    }
    if (group == NULL) {
	/*
	 * ignore groups not already in the dactive.kp file if SyncGroupsOpt is not
	 * set.
	 */
	if (SyncGroupsOpt == 0)
	    return(NULL);
	*pgroup = group = calloc(sizeof(Group) + strlen(groupName) + 1, 1);
	group->gr_State = GRF_NEW | GRF_LMTS | GRF_CTS;
	group->gr_GroupName = (char *)(group + 1);
	group->gr_NoCTS = 1;
	group->gr_NoLMTS = 1;
	group->gr_StartNo = 1;
	strcpy(group->gr_GroupName, groupName);
    }

    /*
     * update fields
     */

    if (synNo >= 0) {
	if (pass == 1) {
	    group->gr_SynNo = synNo;
	} else if (SyncArtNumbersSynOpt) {
	    group->gr_State |= GRF_SYNNO;
	    if (group->gr_SynNo != synNo) {
		if (SyncArtNumbersSynOpt != 2 || synNo > group->gr_SynNo) {
		    int perc;
		    if (group->gr_SynNo == 0)
			group->gr_SynNo = 1;
		    group->gr_SynDiff = (synNo + SyncArtNumbersAddition) -
							group->gr_SynNo;
		    perc = group->gr_SynDiff * 100 / group->gr_SynNo;
		    if (SyncArtNumbersMaxPerc && group->gr_SynNo &&
				(group->gr_SynDiff > 0) &&
				(perc > SyncArtNumbersMaxPerc))
			group->gr_SynDiff = SyncArtNumbersMaxPerc *
							group->gr_SynNo / 100;
		    if (group->gr_SynDiff == 0)
			group->gr_SynDiff = 1;
		    group->gr_SynNo += group->gr_SynDiff;
		    group->gr_State |= GRF_MODIFIED;
		}
	    }
	}
    }

    if (endNo >= 0) {
	if (pass == 1) {
	    group->gr_EndNo = endNo;
	} else if (SyncArtNumbersEndOpt) {
	    group->gr_State |= GRF_ENDNO;
	    if (group->gr_EndNo != endNo) {
		if (SyncArtNumbersEndOpt != 2 || endNo > group->gr_EndNo) {
		    int perc;
		    if (group->gr_EndNo == 0)
			group->gr_EndNo = 1;
		    group->gr_EndDiff = (synNo + SyncArtNumbersAddition) -
							group->gr_EndNo;
		    perc = group->gr_EndDiff * 100 / group->gr_EndNo;
		    if (SyncArtNumbersMaxPerc && group->gr_EndNo &&
				(group->gr_EndDiff > 0) &&
				(perc > SyncArtNumbersMaxPerc))
			group->gr_EndDiff = SyncArtNumbersMaxPerc *
							group->gr_EndNo / 100;
		    if (group->gr_EndDiff == 0)
			group->gr_EndDiff = 1;
		    group->gr_EndNo += group->gr_EndDiff;
		    group->gr_NoLMTS = 1;
		    group->gr_State |= GRF_MODIFIED;
		}
	    }
	}
    }

    if (begNo >= 0) {
	if (pass == 1) {
	    group->gr_StartNo = begNo;
	} else if (SyncArtNumbersBegOpt ||
	    (SyncArtNumbersRange && (group->gr_State & GRF_ENDNO) && endNo > 0)
	) {
	    if (SyncArtNumbersRange) {
		/*
		 * Set beginning based on ending minus the difference
		 * between the remote ending and beginning.
		 */
		begNo = group->gr_EndNo - (endNo - begNo);
		if (begNo < group->gr_StartNo)
		    begNo = group->gr_StartNo;
		if (begNo > group->gr_EndNo)
		    begNo = group->gr_EndNo;
	    }
	    if (group->gr_StartNo != begNo) {
		group->gr_State |= GRF_STARTNO;
		group->gr_State |= GRF_MODIFIED;
	    }
	    group->gr_StartDiff = begNo - group->gr_StartNo;
	    group->gr_StartNo = begNo;
	}
    }

    if (flags) {
	if (pass == 1) {
	    SetField(&group->gr_Flags, flags);
	} else if ((SyncOverwriteOpt && SyncModStatusOpt) ||
					(group->gr_State & GRF_NEW)) {
	    group->gr_State |= GRF_FLAGS;
	    if (SetField(&group->gr_Flags,
				SyncModStatusFlag ? SyncModStatusFlag : flags))
		group->gr_State |= GRF_MODIFIED;
	}
    }
    if (desc) {
	if (pass == 1) {
	    SetField(&group->gr_Description, desc);
	} else if ((SyncOverwriteOpt && SyncDescrOpt) ||
			(SyncDescrOpt && (group->gr_State & GRF_NEW))) {
	    group->gr_State |= GRF_DESCRIPTION;
	    if (SetField(&group->gr_Description, desc))
		group->gr_State |= GRF_MODIFIED;
	}
    }

    if (noCTS >= 0 && group->gr_NoCTS != noCTS) {
	group->gr_NoCTS = noCTS;
	if (pass > 1)
	    group->gr_State |= GRF_MODIFIED | GRF_CTS;
    }

    if (noLMTS >= 0 && group->gr_NoLMTS != noLMTS) {
	group->gr_NoLMTS = noLMTS;
	if (pass > 1)
	    group->gr_State |= GRF_MODIFIED | GRF_LMTS;
    }

    return(group);
}

int
ConnectToHost(const char *host, int port)
{
    int fd;
    struct sockaddr_in lsin;
    struct sockaddr_in rsin;

    bzero(&lsin, sizeof(lsin));
    bzero(&rsin, sizeof(rsin));
    lsin.sin_port = 0;
    if (BindPort != NULL) {
	struct servent *sen;
	int port;
	if ((port = strtol(BindPort, NULL, 0)) != 0) {
	    lsin.sin_port = htons(port);
	} else { 
	    if ((sen = getservbyname(BindPort, "tcp")) != NULL) {
		lsin.sin_port = sen->s_port;
	    } else {
		fprintf(stderr, "Unknown service: %s\n", BindPort);
		exit(1);
	    }
	}
    }
    lsin.sin_family = AF_INET;
    if (BindHost == NULL) {
	lsin.sin_addr.s_addr = INADDR_ANY;
    } else {
	if (strtol(BindHost, NULL, 0) > 0) {
	    lsin.sin_addr.s_addr = inet_addr(BindHost);
	} else {
	    struct hostent *he;
                
	    if ((he = gethostbyname(BindHost)) != NULL) {
		lsin.sin_addr = *(struct in_addr *)he->h_addr;
	    } else { 
		fprintf(stderr, "Unknown bind host: %s\n", BindHost);
		exit(1);
	    }
	}
    }

    errno = 0;
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	return(-1);

    if (bind(fd, (struct sockaddr *)&lsin, sizeof(lsin)) < 0) {
	close(fd);
	return(-1);
    }

    rsin.sin_port = htons(port);
    rsin.sin_family = AF_INET;

    {
	struct hostent *he;
	if ((he = gethostbyname(host)) != NULL) {
	    memcpy(&rsin.sin_addr, he->h_addr, he->h_length);
	} else {
	    rsin.sin_addr.s_addr = inet_addr(host);
	    if (rsin.sin_addr.s_addr == INADDR_NONE) {
		printf("host %s lookup failure\n", host);
		close(fd);
		return(-1);
	    }
        }
    }

    if (DebugOpt) {
	fprintf(stderr, "Connecting to %s (%s)\n", host, inet_ntoa(rsin.sin_addr));
	fflush(stderr);
    }

    if (connect(fd, (struct sockaddr *)&rsin, sizeof(rsin)) < 0) {
	if (DebugOpt) {
	    fprintf(stderr, "connection failed: %s\n", strerror(errno));
	    fflush(stderr);
	}
	close(fd);
	return(-1);
    }
    if (DebugOpt) {
	fprintf(stderr, "connection success\n");
	fflush(stderr);
    }
    return(fd);
}

int
CommandResponse(const char *cmd, char **pres)
{
    static char buf[4096];

    if (DebugOpt && cmd) {
	fprintf(stderr, ">> %s\n", cmd);
	fflush(stderr);
    }

    if (cmd)
	fprintf(Fo, "%s\r\n", cmd);
    fflush(Fo);
    if (fgets(buf, sizeof(buf), Fi) != NULL) {
	if (DebugOpt) {
	    fprintf(stderr, "<< %s", buf);
	    fflush(stderr);
	}
	if (pres)
	    *pres = buf;
	return(strtol(buf, NULL, 10));
    } else if (DebugOpt) {
	fprintf(stderr, "<< (EOF)\n");
	fflush(stderr);
    }
    return(0);
}

int
hash(const char *str)
{
    unsigned int hv = 0xA432BCDD;

    while (*str) {
	hv = (hv << 5) ^ *str ^ (hv >> 23);
	++str;
    }
    hv ^= (hv >> 16);
    return(hv & GHMASK);
}

int
SetField(char **pptr, const char *str)
{
    if (*pptr && strcmp(*pptr, str) == 0)
	return(0);
    if (*pptr)
	free(*pptr);
    *pptr = strcpy(malloc(strlen(str) + 1), str);
    return(1);
}


syntax highlighted by Code2HTML, v. 0.9.1