/*
 * GROUPCTL.C
 *
 * Create/Modify/Delete newsgroups in the active file
 *
 */

#include "dreaderd/defs.h"

#define MAXCMDARGS	8

const char *GrpCtlCancel(char *article);
const char *GrpCtlGroupInfo(char *group);
const char *GrpCtlListActive(char *match);
const char *GrpCtlListGroupHash(char *match, char *hash, int fname);
const char *GrpCtlListNewsgroups(char *match);
const char *GrpCtlNewGroup(int ac, char **av, int pos);
const char *GrpCtlRmGroup(char *group);
const char *GrpCtlAdjust(int ac, char **av, int pos);
const char *GrpCtlCheckGroups(int ac, char **av, int pos);

KPDB *KDBActive;

typedef struct GrpList {
    char *group;
    struct GrpList *next;
} GrpList;

void
Usage(void)
{
    printf("Usage:\n");
    printf("  dgrpctl -s  to use server active file (defaults to reader)\n");
    printf("  dgrpctl listactive [PATTERN]\n");
    printf("       - list the contents of active file\n");
    printf("  dgrpctl listnewsgroups [PATTERN]\n");
    printf("       - list the newsgroups and their descriptions\n");
    printf("  dgrpctl listgrouphash [PATTERN [HASH]]\n");
    printf("       - list the hashes of newsgroup names\n");
    printf("  dgrpctl listgrouphashfile [PATTERN [HASH]]\n");
    printf("       - list the index path/filename of a newsgroup\n");
    printf("  dgrpctl groupinfo NEWSGROUP\n");
    printf("       - display detailed information about a newsgroup\n");
    printf("  dgrpctl rmgroup NEWSGROUP\n");
    printf("       - remove the specified newsgroup\n");
    printf("  dgrpctl newgroup NEWSGROUP [FLAGS] [MODERATOR] [DESCRIPTION]\n");
    printf("       - add/modify the specified newsgroup\n");
    printf("	   flags       = 'y' or 'm' (default is 'y')\n");
    printf("	   moderator   = moderator email address or \"\"\n");
    printf("	   description = group description\n");
    printf("  dgrpctl adjust NEWSGROUP NB|NE|NX value\n");
    printf("       - adjust one of the specified numbers for group\n");
    printf("  dgrpctl checkgroups [exec] filename|-\n");
    printf("       - parse the checkgroups list and print changes required\n");
    printf("	   exec = actually make all the necessary changes\n");
    printf("	   -    = read list on stdin\n");
    printf("\n");
}

int
main(int ac, char **av)
{
    int i = 1;
    const char *activefile;

    LoadDiabloConfig(ac, av);

    activefile = PatDbExpand(ReaderDActivePat);

    while (av[i] && av[i][0] == '-') {
	switch (av[i][1]) {
	    case 'V':
		PrintVersion();
		break;
	    case 's':
		activefile = PatDbExpand(ServerDActivePat);
		break;
	    default:
		Usage();
	}
	i++;
    }

    KDBActive = KPDBOpen(activefile, O_RDWR);
    if (!KDBActive) {
	printf("Unable to open active file: %s\n", activefile);
	exit(1);
    }

    if (av[i] && av[i+1] && strcmp(av[i], "newgroup") == 0)
	GrpCtlNewGroup(ac, av, i+1);
    else if (av[i] && av[i+1] && strcmp(av[i], "modgroup") == 0)
	GrpCtlNewGroup(ac, av, i+1);
    else if (av[i] && av[i+1] && strcmp(av[i], "rmgroup") == 0)
	GrpCtlRmGroup(av[i+1]);
    else if (av[i] && strcmp(av[i], "listactive") == 0)
	GrpCtlListActive(av[i+1]);
    else if (av[i] && strcmp(av[i], "listgrouphash") == 0)
	GrpCtlListGroupHash(av[i+1], av[i+1] ? av[i+2] : NULL, 0);
    else if (av[i] && strcmp(av[i], "listgrouphashfile") == 0)
	GrpCtlListGroupHash(av[i+1], av[i+1] ? av[i+2] : NULL, 1);
    else if (av[i] && strcmp(av[i], "listnewsgroups") == 0)
	GrpCtlListNewsgroups(av[i+1]);
    else if (av[i] && strcmp(av[i], "cancel") == 0)
	GrpCtlCancel(av[i+1]);
    else if (av[i] && strcmp(av[i], "groupinfo") == 0)
	GrpCtlGroupInfo(av[i+1]);
    else if (av[i] && strcmp(av[i], "adjust") == 0)
	GrpCtlAdjust(ac, av, i+1);
    else if (av[i] && strcmp(av[i], "checkgroups") == 0)
	GrpCtlCheckGroups(ac, av, i+1);
    else  {
	Usage();
    }

    KPDBClose(KDBActive);
    return(0);
}

/*
 * CANCEL -
 */

const char *
GrpCtlCancel(char *article)
{
#if 0
    char *group;
    int artNo;
    OverInfo *ov;

    MyGroupHome = strdup(PatExpand(GroupHomePat));

    if ((group = strchr(article, ':')) == NULL) {
	printf(">> cancel: Invalid group:artno (%s)\n", article);
	return(NULL);
    }
    *group++ = '\0';
    artNo = atoi(group);
    group = article;
    
    if (ValidGroupName(group) < 0) {
	printf(">> cancel: Illegal newsgroup name\n");
	return(NULL);
    }
    if (artNo <= 0) {
	printf(">> cancel: Illegal article number\n");
	return(NULL);
    }

    ov = GetOverInfo(group);
    CancelOverArt(ov, artNo);
#endif
    return(NULL);
}

/*
 * GROUPINFO -
 */

const char *
GrpCtlGroupInfo(char *group)
{
    int recLen;
    const char *rec;
    int nb;
    int iter = 0;

    if (ValidGroupName(group) < 0) {
	printf(">> groupinfo: Illegal newsgroup name\n");
	return(NULL);
    }

    if ((rec = KPDBReadRecord(KDBActive, group, 0, &recLen)) == NULL) {
	printf(">> groupinfo: Unable to access group active data\n");
	return(NULL);
    } else {
	const char *flags;
	int n;
	int flen;
	char buf[MAXGNAME];

	printf("%s\n", group);
	n = strtol(KPDBGetField(rec, recLen, "NE", NULL, "0"), NULL, 10);
	printf("  NE=%010d ", n);
	nb = strtol(KPDBGetField(rec, recLen, "NB", NULL, "0"), NULL, 10);
	printf("NB=%010d ", nb);
	n = strtol(KPDBGetField(rec, recLen, "NX", NULL, "0"), NULL, 10);
	printf("NX=%010d ", n);
	flags = KPDBGetField(rec, recLen, "S", &flen, "n");
	bcopy(flags, buf, flen);
	buf[flen] = 0;
	printf("flag=%s ", buf);
	iter = strtol(KPDBGetField(rec, recLen, "ITER", NULL, "0"), NULL, 10);
	printf("ITER=%d\n", iter);
	n = strtol(KPDBGetField(rec, recLen, "CTS", NULL, "0"), NULL, 16);
	printf("  CTS=%u=%s", n, ctime((const time_t *)&n));
	n = strtol(KPDBGetField(rec, recLen, "LMTS", NULL, "0"), NULL, 16);
	printf("  LMTS=%u=%s", n, ctime((const time_t *)&n));
	flags = KPDBGetField(rec, recLen, "GD", &flen, "?");
	bcopy(flags, buf, flen);
	buf[flen] = 0;
	printf("  GD=%s\n", buf);
    }
    {
	int fd;
	int maxarts;
	int pos;
	char path[PATH_MAX];
	OverHead oh;
	OverArt oa;
	struct stat st;
	const char *gname = GFName(group, GRPFTYPE_OVER, 0, 1, iter,
						&DOpts.ReaderGroupHashMethod);

	printf("  grouphash=%s\n", GFHash(group, &DOpts.ReaderGroupHashMethod));
	snprintf(path, sizeof(path), "%s/%s", PatExpand(GroupHomePat), gname);
	printf("  overfile=%s\n", path);

	if ((fd = open(path, O_RDONLY)) < 0) {
	    printf("  Unable to open %s\n", path);
	    return(NULL);
	}
	if (read(fd, &oh, sizeof(oh)) != sizeof(oh)) {
	    printf("  Error reading over header data\n");
	    close(fd);
	    return(NULL);
	}
	if (oh.oh_Version != OH_VERSION ||
					oh.oh_ByteOrder != OH_BYTEORDER) {
	    printf("  Wrong version or byte order\n");
	    close(fd);
	    return(NULL);
	}
	fstat(fd, &st);
	maxarts = (st.st_size - oh.oh_HeadSize) / sizeof(OverArt);
	printf("  overfilesize=%u  maxarts=%u\n", (unsigned)st.st_size,
							maxarts);

	pos = oh.oh_HeadSize + ((nb & 0x7FFFFFFF) % maxarts) *
							sizeof(OverArt);
	lseek(fd, pos, 0);
	if (read(fd, &oa, sizeof(oa)) != sizeof(oa)) {
	    printf("  Unable to read OverArt\n");
	    close(fd);
	    return(NULL);
	}
	if (oa.oa_ArtNo > 0) {
	    printf("  StartNo=%u\n", oa.oa_ArtNo);
	} else if (oa.oa_ArtNo == -1) {
	    printf("  Article cancelled\n");
	} else if (oa.oa_ArtNo == -2) {
	    printf("  Article expired\n");
	} else {
	    printf("  Article not found\n");
	}
	if (oa.oa_ArtNo != nb) {
	    printf("  artNoMismatch (got=%d  wanted=%d)\n", oa.oa_ArtNo, nb);
	}
	printf("  Time received: %d = %s", oa.oa_TimeRcvd,
					ctime((time_t *)&oa.oa_TimeRcvd));
    }
    return(NULL);
}

/*
 * LISTACTIVE -
 */

const char *
GrpCtlListActive(char *match)
{
    int recLen;
    int recOff;

    for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
	recOff;
	recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
    ) {
	int groupLen;
	char grpbuf[MAXGNAME];
	const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
	const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
	if (group) {
	    if (groupLen >= MAXGNAME)
		groupLen = MAXGNAME - 1;
	    bcopy(group, grpbuf, groupLen);
	    grpbuf[groupLen] = 0;
	    if (match == NULL || WildCmp(match, grpbuf) == 0) {
		const char *flags;
		int ne;
		int nb;
		int flen;

		flags = KPDBGetField(rec, recLen, "S", &flen, "n");
		ne = strtol(KPDBGetField(rec, recLen, "NE", NULL, "0"), NULL, 10);
		nb = strtol(KPDBGetField(rec, recLen, "NB", NULL, "0"), NULL, 10);
		printf("%s %010d %010d ", grpbuf, ne, nb);
		bcopy(flags, grpbuf, flen);
		grpbuf[flen] = 0;
		printf("%s\n", grpbuf);
	    }
	}
    }
    return(NULL);
}

/*
 * LISTGROUPHASH -
 */

const char *
GrpCtlListGroupHash(char *match, char *hash, int dir)
{
    int recLen;
    int recOff;
    int wild = 1;

    if (hash != NULL)
	SetGroupHashMethod(hash,  &DOpts.ReaderGroupHashMethod);

    if (match != NULL && index(match, '?') == NULL && index(match, '*') == NULL)
	wild = 0;
    for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
	wild && recOff;
	recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
    ) {
	int groupLen;
	char grpbuf[MAXGNAME];
	const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
	const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
	if (group) {
	    if (groupLen >= MAXGNAME)
		groupLen = MAXGNAME - 1;
	    bcopy(group, grpbuf, groupLen);
	    grpbuf[groupLen] = 0;
	    if (match == NULL || WildCmp(match, grpbuf) == 0) {
		if (dir)
		    printf("%s %s\n",
			GFName(grpbuf, GRPFTYPE_OVER, 0, 1, 0,
				&DOpts.ReaderGroupHashMethod),
			grpbuf
		    );
		else
		    printf("%s %s\n",
			GFHash(grpbuf, &DOpts.ReaderGroupHashMethod), grpbuf);
	    }
	}
    }
    if (!wild) {
	if (dir)
	    printf("%s %s\n",
			GFName(match, GRPFTYPE_OVER, 0, 1, 0,
						&DOpts.ReaderGroupHashMethod),
			match
	    );
	else
	    printf("%s %s\n",
			GFHash(match, &DOpts.ReaderGroupHashMethod),
			match);
    }
    return(NULL);
}

/*
 * LISTNEWSGROUPS -
 */

const char *
GrpCtlListNewsgroups(char *match)
{
    int recLen;
    int recOff;

    for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
	recOff;
	recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
    ) {
	int groupLen;
	char grpbuf[MAXGNAME];
	const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
	const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
	if (group) {
	    if (groupLen >= MAXGNAME)
		groupLen = MAXGNAME - 1;
	    bcopy(group, grpbuf, groupLen);
	    grpbuf[groupLen] = 0;
	    if (match == NULL || WildCmp(match, grpbuf) == 0) {
		const char *desc;

		desc  = KPDBGetFieldDecode(rec, recLen, "GD", NULL, NULL);
		printf("%s\t%s\n", grpbuf, (desc != NULL) ? desc : "?");
	    }
	}
    }
    return(NULL);
}

/*
 * NEWGROUP -
 */

const char *
GrpCtlNewGroup(int ac, char **av, int pos)
{
    char *flags;
    char *moderator = NULL;
    char description[256];
    char *group = NULL;
    int isNew = 0;
    int hasCts = 0;
    const char *rec;
    int recLen;

    description[0] = 0;
    group = av[pos++];
    if ((flags = av[pos++]) == NULL)
	flags = "y";
    else {
	if (av[pos]) {
	    moderator = av[pos++];
	    if (strcmp(moderator, "\"\"") == 0)
		moderator = NULL;
	}
	if (av[pos])
	    strcpy(description, av[pos]);
    }

    if (ValidGroupName(group) < 0) {
	printf(">> Newgroup: Illegal newsgroup name\n");
	return(NULL);
    }

    if (flags && strlen(flags) != 1) {
	printf(">> Newgroup: Illegal flag: %s \n", flags);
	return(NULL);
    }
    /*
     * Read and lock the record.  If the record does not exist, create a new
     * record (and lock that).
     */

    if ((rec = KPDBReadRecord(KDBActive, group, KP_LOCK, &recLen)) == NULL) {
	KPDBWrite(KDBActive, group, "NB", "0000000001", KP_LOCK);
	KPDBWrite(KDBActive, group, "NE", "0000000000", KP_LOCK_CONTINUE);
	isNew = 1;
    } else {
	if (KPDBGetField(rec, recLen, "CTS", NULL, NULL) != NULL)
	    hasCts = 1;
    }

    if (moderator) {
	SanitizeString(moderator);
	KPDBWriteEncode(KDBActive, group, "M", moderator, KP_LOCK_CONTINUE);
    }
    if (description) {
	SanitizeDescString(description);
	KPDBWriteEncode(KDBActive, group, "GD", description, KP_LOCK_CONTINUE);
    }

    {
	/*
	 * add creation-time-stamp and last-modified-time-stamp
	 * for group.
	 */
	char tsBuf[64];

	sprintf(tsBuf, "%08x", (int)time(NULL));
	if (hasCts == 0)
	    KPDBWrite(KDBActive, group, "CTS", tsBuf, KP_LOCK_CONTINUE);
	KPDBWrite(KDBActive, group, "LMTS", tsBuf, KP_LOCK_CONTINUE);
    }

    KPDBWrite(KDBActive, group, "S", flags, KP_UNLOCK);

    if (isNew) {
	printf(">> Newgroup: Created new group %s flags=%s\n", group, flags );
    } else
	printf(">> Newgroup: updated group %s flags=%s moderator=%s description=%s\n",
		group,
		flags,
		(moderator ? moderator : "no chg"),
		(description ? description : "no chg")
	    );
    return(NULL);
}

const char *
GrpCtlRmGroup(char *options)
{
    const char *rec;
    int recLen;
    char *group;

    group = options;

    if (ValidGroupName(group) < 0) {
	printf(">> Rmgroup: Illegal newsgroup name\n");
	return(NULL);
    }

    /*
     * Note that the record isn't locked in this case.
     */

    if ((rec = KPDBReadRecord(KDBActive, group, 0, &recLen)) != NULL) {
	KPDBDelete(KDBActive, group);
	printf(">> RmGroup: Deleted group %s\n", group);
    } else {
	printf(">> RmGroup: No group %s\n", group);
    }

    return(NULL);
}

/*
 * ADJUST -
 */

const char *
GrpCtlAdjust(int ac, char **av, int pos)
{
    char *field;
    char *group = NULL;
    int adjust = 0;
    int oldval = 0;
    const char *rec;
    int recLen;

    if ((group = av[pos++]) == NULL) {
	printf(">> adjust: missing newsgroup\n");
	return(NULL);
    }
    if ((field = av[pos++]) == NULL) {
	printf(">> adjust: missing field to adjust\n");
	return(NULL);
    }
    if ((av[pos]) == NULL) {
	printf(">> adjust: missing adjust value\n");
	return(NULL);
    }
    adjust = strtol(av[pos++], NULL, 0);

    if (ValidGroupName(group) < 0) {
	printf(">> adjust: Illegal newsgroup name\n");
	return(NULL);
    }

    if (strcmp(field, "NB") != 0 && strcmp(field, "NE") != 0 &&
			strcmp(field, "NX") != 0) {
	printf(">> adjust: Illegal field: %s \n", field);
	return(NULL);
    }
    /*
     * Read and lock the record.  If the record does not exist, create a new
     * record (and lock that).
     */

    if ((rec = KPDBReadRecord(KDBActive, group, KP_LOCK, &recLen)) != NULL) {
	char buf[32];
	oldval = strtol(KPDBGetField(rec, recLen, field, NULL, "0"), NULL, 10);
	sprintf(buf, "%010d", oldval + adjust);
	KPDBWrite(KDBActive, group, field, buf, KP_LOCK);
    } else {
	printf(">> adjust: group '%s' not found\n", group);
	return(NULL);
    }

    printf(">> adjust: %s for %s adjusted from %d to %d\n", field, group,
						oldval, oldval + adjust);
    return(NULL);
}

/*
 * CHECKGROUPS -
 */

const char *
GrpCtlCheckGroups(int ac, char **av, int pos)
{
    char *filename;
    int exec = 0;
    FILE *f;
    char buf[128];
    const char *rec;
    int recLen;
    GrpList *grpList = NULL;
    char *hierarchy = NULL;

    if (av[pos] != NULL && strcmp(av[pos], "exec") == 0) {
	pos++;
	exec = 1;
    }

    if ((filename = av[pos++]) == NULL) {
	printf(">> checkgroups: missing filename\n");
	return(NULL);
    }
    if (strcmp(filename, "-") == 0) {
	f = stdin;
    } else {
	f = fopen(filename, "r");
	if (f == NULL) {
	    printf(">> checkgroups: cannot open %s\n", filename);
	    return(NULL);
	}
    }
    while (fgets(buf, sizeof(buf), f) != NULL) {
	char *newsgroup;
	char *description = NULL;
	char *flags = "y";

	if ((newsgroup = strtok(buf, " \t\r\n")) == NULL)
	    continue;
	if (ValidGroupName(newsgroup) < 0)              /* valid group name */
	    continue;
	if (strchr(newsgroup, '.') == NULL)             /* sanity check */
	    continue;
	if ((description = strtok(NULL, "\r\n")) == NULL)
	    continue;
     
	if (hierarchy == NULL) {
	    char *p;
	    hierarchy = (char *)malloc(strlen(newsgroup) + 2);
	    strcpy(hierarchy, newsgroup);
	    p = strchr(hierarchy, '.');
	    if (p != NULL)
		*p = 0;
	    strcat(hierarchy, ".*");
	}

	/*
	 * clean up the description
	 */
	SanitizeDescString(description);
	{
	    int l = strlen(description);
	    while (*description == ' ' || *description == '\t') {
		++description;
		--l;
	    }
	    while (l > 0 && (description[l-1] == ' ' || description[l-1] == '\t')) {
		description[--l] = 0;
	    }
	}

	if (strstr(description, "(Moderated)") != 0)
	    flags = "m";
	else
	    flags = "y";

	/*
	 * Keep a list of all the newsgroups to check for deleted groups
	 */
	{
	    GrpList *gl = (GrpList *)malloc(sizeof(GrpList));
	    gl->next = grpList;
	    gl->group = strdup(newsgroup);
	    grpList = gl;
	}

	if ((rec = KPDBReadRecord(KDBActive, newsgroup, KP_LOCK, &recLen)) == NULL) {
	    if (exec) {
		KPDBWrite(KDBActive, newsgroup, "NB", "0000000001", KP_LOCK);
		KPDBWrite(KDBActive, newsgroup, "NE", "0000000000",
							KP_LOCK_CONTINUE);
		printf(">> Checkgroups: Added group %s\n", newsgroup);
	    } else {
		printf("dgrpctl newgroup %s %s \"\" \"%s\"\n",
						newsgroup,
						flags,
						description);
	    }
	} else {
	    const char *p;
	    char buf[MAXGNAME];
	    int plen;
	    int needflags = 0;
	    int needdesc = 0;

	    p = KPDBGetField(rec, recLen, "S", &plen, "n");
	    bcopy(p, buf, plen);
	    buf[plen] = 0;
	    if (strcmp(buf, flags) != 0)
		needflags = 1;

	    p = KPDBGetFieldDecode(rec, recLen, "GD", NULL, NULL);
	    if (p != NULL)
		strcpy(buf, p);
	    else
		buf[0] = 0;
	    if (p == NULL || strcmp(buf, description) != 0)
		needdesc = 1;

	    if (needflags || needdesc) {
		if (exec) {
		    KPDBWrite(KDBActive, newsgroup, "S", flags, KP_UNLOCK);
		    KPDBWriteEncode(KDBActive, newsgroup, "GD", description,
							KP_LOCK_CONTINUE);
		    printf(">> Checkgroups: Modified group %s\n", newsgroup);
		} else {
		    printf("dgrpctl modgroup %s %s \"\" \"%s\"\n",
						newsgroup,
						flags,
						description);
		}
	    }
	}
    }
	{
	    int recLen;
	    int recOff;

	    for (recOff = KPDBScanFirst(KDBActive, 0, &recLen);
		recOff;
		recOff = KPDBScanNext(KDBActive, recOff, 0, &recLen)
	    ) {
		int groupLen;
		char grpbuf[MAXGNAME];
		const char *rec = KPDBReadRecordAt(KDBActive, recOff, 0, NULL);
		const char *group = KPDBGetField(rec, recLen, NULL, &groupLen, NULL);
		if (group) {
		    if (groupLen >= MAXGNAME)
			groupLen = MAXGNAME - 1;
		    bcopy(group, grpbuf, groupLen);
		    grpbuf[groupLen] = 0;
		    if (WildCmp(hierarchy, grpbuf) == 0) {
			GrpList *g;
			int found = 0;
			for (g = grpList; g != NULL; g = g->next) {
			    if (strcmp(grpbuf, g->group) == 0) {
				found = 1;
				break;
			    }
			}
			if (!found) {
			    if (exec) {
				const char *rec;
				if ((rec = KPDBReadRecord(KDBActive, grpbuf, 0, &recLen)) != NULL) {
				    KPDBDelete(KDBActive, grpbuf);
				    printf(">> Checkgroups: Deleted group %s\n", grpbuf);
				}
			    } else {
				printf("dgrpctl rmgroup %s\n", grpbuf);
			    }
			}
		    }
		}
	    }
	}
    return(NULL);
}



syntax highlighted by Code2HTML, v. 0.9.1