/*
 * CONTROL.C
 *
 * Control: cancel <message-id>
 *
 * Control: newgroup groupname [y/m[oderated]
 *	For your newsgroups file:
 *	groupname		comment
 *
 *	Group submission address: moderator-email
 *
 * Control: rmgroup groupname
 *
 * Control: checkgroups
 *	(message body contains)
 *	groupname		comment
 *	(missing groups are deleted?)
 *
 * Approved: 	(is a required header)
 * X-PGP-Sig: 	(is a required header for pgp-authentication)
 * X-DGPSig:	(is a required header for dgp-authentication)
 *
 * (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"

#define MAXCMDARGS	8

Prototype void ExecuteControl(Connection *conn, const char *ctl, const char *art, int artLen);
Prototype void FreeControl(Connection *conn);
Prototype void ExecuteSupersedes(Connection *conn, const char *supers, const char *art, int artLen);

Control *ParseControl(Connection *conn, const char *cmd, const char *from, const char *newsgroup);
void SendMail(FILE *logf, long lpos, char *hostname, const char *cmd, const char *arg);
const char *PerformControl(Connection *conn, Control *ctl, const char **cmd, const char *art, const char *body, int bodyLen, char *groups);
const char *CtlCancel(Connection *conn, Control *ctl, const char **cmd, const char *art, const char *body, int bodyLen, char *groups);
const char *CtlNewGroup(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen);
const char *CtlRmGroup(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen);
const char *CtlCheckGroups(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen);
const char *locateXTrace(const char *art, int headLen, int *pxtLen);

void
ExecuteControl(Connection *conn, const char *ctlMsg, const char *art, int artLen)
{
    const char *body;
    const char *newsgroup = NULL;
    char *from = NULL;
    char *groups = NULL;
    char *cmdBuf;
    const char *cmd[MAXCMDARGS];
    int nargs = 0;
    int cmdBufLen;
#if DIABLO_PGP_SUPPORT
    int hasXPGPSig = 0;		/* PGP signature	*/
#endif
#if DIABLO_DGP_SUPPORT    
    int hasXDGPSig = 0;
#endif
    const char *badCmd = NULL;
    Control *ctl = NULL;

    /*
     * control command and arguments
     */

    cmdBufLen = strlen(ctlMsg) + 1;
    cmdBuf = zalloc(&conn->co_MemPool, cmdBufLen);
    strcpy(cmdBuf, ctlMsg);

    {
	char *ptr = cmdBuf;

	while (nargs < MAXCMDARGS && (cmd[nargs] = parseword(&ptr, " \t")) != NULL)
	    ++nargs;
	while (nargs < MAXCMDARGS)
	    cmd[nargs++] = NULL;
    }

    if (cmd[0] == NULL) {
	if (DebugOpt)
	    printf("NULL control\n");
	zfree(&conn->co_MemPool, cmdBuf, cmdBufLen);
	return;
    }

    /*
     * PGP authenticate
     */

    if (DebugOpt)
	printf("Control Message received: %s %s\n", cmd[0], cmd[1]);

    /*
     * Scan headers for lines we need to locate the correct control.ctl line.
     *
     * Note that the Control: header stored in cmd[] may have been synthesized
     * (see ExecuteSupersedes), and thus not match the Control: header in the
     * article, or there may not even *be* a Control: header in the article.
     */

    {
	const char *line;
	int l = 0;

	for (line = art; line < art + artLen; line += l + 1) {
	    for (l = line - art; l < artLen; ++l) {
		if (art[l] == '\n') {
		    if (l + 1 >= artLen ||		/* end of article */
			l == line - art ||		/* blank line	  */
			(art[l+1] != ' ' && art[l+1] != '\t') /* !header ext */
		    ) {
			break;
		    }
		}
	    }
	    l -= line - art;

	    if (l == 0 || (l == 1 && line[0] == '\r')) {
		/* out of headers */
		break;
	    } else if (strncasecmp(line, "From:", 5) == 0) {
		from = zallocStrTrim(&conn->co_MemPool, line + 5, l - 5);
	    } else if (strncasecmp(line, "Newsgroups:", 11) == 0) {
		groups = zallocStrTrim(&conn->co_MemPool, line + 11, l - 11);
#if DIABLO_PGP_SUPPORT
	    } else if (strncasecmp(line, "X-PGP-Sig:", 10) == 0) {
		hasXPGPSig = 1;
#endif
#if DIABLO_DGP_SUPPORT    
	    } else if (strncasecmp(line, "X-DGPSig:", 9) == 0) {
		hasXDGPSig = 1;
#endif

	    }

	}
	body = line;
    }

    /*
     * newsgroup - newgroup and rmgroup only
     * messageid - cancel only
     */

    if (strcasecmp(cmd[0], "newgroup") == 0) {
	if (cmd[1] == NULL)
	    badCmd = "no argument to newgroup\n";
	else
	    newsgroup = cmd[1];
    } else if (strcasecmp(cmd[0], "rmgroup") == 0) {
	if (cmd[1] == NULL)
	    badCmd = "no argument to rmgroup\n";
	else
	    newsgroup = cmd[1];
    } else if (strcasecmp(cmd[0], "cancel") == 0) {
	if (cmd[1] == NULL) {
	    badCmd = "no argument to cancel\n";
	} else {
	    cmd[1] = MsgId(cmd[1], NULL);
	    if (strcmp(cmd[1], "<>") == 0)
		badCmd = "bad message-id in cancel\n";
	}
    } else if (strcasecmp(cmd[0], "checkgroups") == 0) {
	;
    }

    /*
     * Locate match in dcontrol.ctl
     */

    if (badCmd == NULL && 
	(ctl = ParseControl(conn, cmd[0], from, newsgroup)) != NULL
    ) {
	/*
	 * log control message content
	 */

	if (ctl->ct_LogFo) {
	    int i;
	    int phead = 1;
	    time_t t = time(NULL);
	    struct tm *tp = localtime(&t);
	    char tbuf[64];

	    strftime(tbuf, sizeof(tbuf), "%c", tp);
	    fprintf(ctl->ct_LogFo, "\nCONTROL %s\n", tbuf);

	    for (i = 0; i < artLen; ++i) {
		if (art[i] == '\r')
		    continue;
		if (phead) {
		    fprintf(ctl->ct_LogFo, "<< ");
		    phead = 0;
		}
		fputc((int)(uint8)art[i], ctl->ct_LogFo);
		if (art[i] == '\n')
		    phead = 1;
	    }
	    fprintf(ctl->ct_LogFo, "\n");
	}

	/*
	 * options
	 */

	/****************************************************************
	 *		GLUE FOR PGP/DGP VERIFICATION			*
	 ****************************************************************
	 *
	 */

	if (ctl->ct_Flags & CTFF_VERIFY) {
	    /*
	     * On success, verification routine will clear other CTFF_VERIFY
	     * flags.
	     */

	    if (badCmd == NULL && (ctl->ct_Flags & CTF_DGPVERIFY)) {
#if DIABLO_DGP_SUPPORT
		if (hasXDGPSig)
		    badCmd = DGPVerify(ctl, art, artLen);
		else
		    badCmd = "dgp verification required, signature missing\n";
		if (badCmd == NULL && ctl->ct_LogFo)
		    fprintf(ctl->ct_LogFo, ">> dgp verification required, validated ok\n");
#else
		badCmd = "dgp verification required, not compiled in\n";
#endif
	    }
	    if (badCmd == NULL && (ctl->ct_Flags & CTF_PGPVERIFY)) {
#if DIABLO_PGP_SUPPORT
		if (hasXPGPSig)
		    badCmd = PGPVerify(ctl, art, artLen);
		else
		    badCmd = "pgp verification required, signature missing\n";
		if (badCmd == NULL && ctl->ct_LogFo)
		    fprintf(ctl->ct_LogFo, ">> pgp verification required, validated ok\n");
#else
		badCmd = "pgp verification required, not compiled in\n";
#endif
	    }
	} else {
	    if (ctl->ct_LogFo)
		fprintf(ctl->ct_LogFo, ">> dgp/pgp verification not requested\n");
	}

	if ((ctl->ct_Flags & CTF_EXECUTE)) {
	    if (ctl->ct_LogFo) {
		if (badCmd) {
		    fprintf(ctl->ct_LogFo, ">> unable to execute control (%s)\n", badCmd);
		} else {
		    fprintf(ctl->ct_LogFo, ">> executing control:\n");
		}
	    }
	    if (badCmd == NULL)
		badCmd = PerformControl(conn, ctl, cmd, art, body, artLen - (body - art), groups);
	} else {
	    if (ctl->ct_LogFo)
		fprintf(ctl->ct_LogFo, ">> control message dropped\n");
	    
	}
    } else {
	if (badCmd == NULL)
	    badCmd = "Control %s not in dcontrol.ctl\n";
    }

    /*
     * Final logging
     */

    if (ctl && ctl->ct_LogFo) {
	if (badCmd) {
	    fprintf(ctl->ct_LogFo, ">>");
	    fprintf(ctl->ct_LogFo, "%s", (newsgroup ? newsgroup : "?"));
	    fprintf(ctl->ct_LogFo, ">> processing failed\n");
	} else {
	    fprintf(ctl->ct_LogFo, ">> processing completed ok\n");
	}
    }

    /*
     * Mail (extract information from logfile or temporary file)
     */

    if (ctl && ctl->ct_LogFo) {
	if (ctl->ct_Flags & CTF_MAIL) {
	    fprintf(ctl->ct_LogFo, ">> mail requested\n");
	    fflush(ctl->ct_LogFo);
	    SendMail(ctl->ct_LogFo, ctl->ct_LogSeekPos,
		conn->co_Auth.dr_VServerDef->vs_HostName, cmd[0], cmd[1]);
	} else {
	    fprintf(ctl->ct_LogFo, ">> mail not requested\n");
	}
    }

    /*
     * Debugging
     */

    if (badCmd && DebugOpt && ctl && ctl->ct_LogFo) {
	char tmp[128];

	fflush(ctl->ct_LogFo);
	fseek(ctl->ct_LogFo, ctl->ct_LogSeekPos, 0);
	while (fgets(tmp, sizeof(tmp), ctl->ct_LogFo) != NULL)
	    fputs(tmp, stdout);
	fseek(ctl->ct_LogFo, 0L, 2);
    } else if (badCmd && DebugOpt) {
	printf(badCmd, (newsgroup ? newsgroup : "?"));
    }

    /*
     * Done
     */

    zfreeStr(&conn->co_MemPool, &from);
    zfreeStr(&conn->co_MemPool, &groups);
    zfree(&conn->co_MemPool, cmdBuf, cmdBufLen);
    FreeControl(conn);
}

Control *
ParseControl(Connection *conn, const char *cmd, const char *from, const char *newsgroup)
{
    FILE *fi = fopen(PatLibExpand(DControlCtlPat), "r");
    char buf[256];
    Control *ctl = NULL;

    if (DebugOpt) {
	printf(
	    "ParseControl '%s' '%s' '%s'\n",
	    ((cmd) ? cmd : "?"),
	    ((from) ? from : "?"),
	    ((newsgroup) ? newsgroup : "?")
	);
    }

    FreeControl(conn);

    if (fi == NULL)
	return(NULL);
    if (from == NULL)
	return(NULL);

    while (fgets(buf, sizeof(buf), fi) != NULL) {
	char *smsg;
	char *sfrom;
	char *sgroups;
	char *saction;

	if (buf[0] == '\n' || buf[0] == '#' ||
	    (smsg = strtok(buf, ":\n")) == NULL ||
	    smsg[0] == '#'
	) {
	    continue;
	}
	if ((sfrom = strtok(NULL, ":\n")) == NULL)
	    continue;
	if ((sgroups = strtok(NULL, ":\n")) == NULL)
	    continue;
	if ((saction = strtok(NULL, ":\n")) == NULL)
	    continue;

	/*
	 * control message type must match
	 */

	if (strcmp(smsg, "all") != 0 && strcasecmp(cmd, smsg) != 0)
	    continue;

	/*
	 * From: line must match.  Be a little fuzzy here
	 */
	if (WildOrCmp(sfrom, from) != 0) {
	    char tmp[512];
	    if (strlen(sfrom) > 128)
		continue;
	    sprintf(tmp, "*<%s>*|* %s|%s *", sfrom, sfrom, sfrom);
	    if (WildOrCmp(tmp, from) != 0)
		continue;
	}

	/*
	 * Newsgroups must match
	 */
	if (newsgroup && WildOrCmp(sgroups, newsgroup) != 0)
	    continue;

	if (DebugOpt)
	    printf("Control authenticated with %s:%s:%s:%s\n", smsg, sfrom, sgroups, saction);

	if (ctl) {
	    zfreeStr(&conn->co_MemPool, &ctl->ct_Msg);
	    zfreeStr(&conn->co_MemPool, &ctl->ct_From);
	    zfreeStr(&conn->co_MemPool, &ctl->ct_Groups);
	    ctl->ct_Flags &= ~(CTF_EXECUTE|CTF_MAIL|CTF_DGPVERIFY|CTF_PGPVERIFY|CTF_DROP); /* XXX */
	} else {
	    ctl = zalloc(&conn->co_MemPool, sizeof(Control));
	}
	ctl->ct_Msg = zallocStr(&conn->co_MemPool, smsg);
	ctl->ct_From = zallocStr(&conn->co_MemPool, sfrom);
	ctl->ct_Groups = zallocStr(&conn->co_MemPool, sgroups);

	for (saction = strtok(saction, ",\n"); saction; saction = strtok(NULL, ",\n")) {
	    char *logFile;

	    if ((logFile = strchr(saction, '=')) != NULL)
		*logFile++ = 0;

	    if (strcmp(saction, "doit") == 0) {
		ctl->ct_Flags |= CTF_EXECUTE;
	    } else if (strcmp(saction, "break") == 0) {
		ctl->ct_Flags |= CTF_BREAK;
	    } else if (strcmp(saction, "drop") == 0) {
		ctl->ct_Flags &= ~CTF_EXECUTE;
		ctl->ct_Flags |= CTF_DROP;
	    } else if (strcmp(saction, "log") == 0) {
		;
	    } else if (strcmp(saction, "mail") == 0) {
		ctl->ct_Flags |= CTF_MAIL;
	    } else if (strncmp(saction, "verify-", 7) == 0) {
		/*
		 * require PGP authentication
		 */
		ctl->ct_Flags |= CTF_PGPVERIFY;
		if ((ctl->ct_Flags & CTF_DROP) == 0)
		    ctl->ct_Flags |= CTF_EXECUTE;
		ctl->ct_Verify = zallocStr(&conn->co_MemPool, saction + 7);
	    } else if (strcmp(saction, "dgpverify") == 0) {
		/*
		 * require DGP authentication
		 */
		ctl->ct_Flags |= CTF_DGPVERIFY;
		if ((ctl->ct_Flags & CTF_DROP) == 0)
		    ctl->ct_Flags |= CTF_EXECUTE;
	    }
	    if (logFile) {
		int fd;
		if (ctl->ct_LogFo) {
		    fflush(ctl->ct_LogFo);
		    hflock(fileno(ctl->ct_LogFo), 0, XLOCK_UN);
		    fclose(ctl->ct_LogFo);
		}

		if (logFile[0] == '/')
		    fd = xopen(O_RDWR|O_CREAT, 0644, "%s", logFile);
		else
		    fd = xopen(O_RDWR|O_CREAT, 0644, "%s/%s", PatExpand(LogHomePat), logFile);

		if (DebugOpt)
		    printf("creating log file %s fd = %d\n", logFile, fd);

		if (fd >= 0) {
		    ctl->ct_LogFo = fdopen(fd, "r+");
		    hflock(fileno(ctl->ct_LogFo), 0, XLOCK_EX);
		    fseek(ctl->ct_LogFo, 0L, 2);
		    ctl->ct_LogSeekPos = ftell(ctl->ct_LogFo);
		    ctl->ct_Flags |= CTF_LOG;
		}
	    }
	}
	if (ctl && ctl->ct_Flags & CTF_BREAK)
	    break;
    }
    fclose(fi);

    /*
     * temporary file
     */

    if (ctl && ctl->ct_LogFo == NULL) {
	if (DebugOpt || (ctl->ct_Flags & (CTF_MAIL|CTF_LOG))) {
	    char path[256];
	    int fd;

	    sprintf(path, "/tmp/dreaderd.ctl%d", (int)getpid());
	    remove(path);

	    if (DebugOpt)
		printf("creating temporary log/copy file %s\n", path);

	    if ((fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644)) >= 0) {
		ctl->ct_LogFo = fdopen(fd, "r+");
		ctl->ct_TmpFileName = zallocStr(&conn->co_MemPool, path);
		ctl->ct_LogSeekPos = 0;
	    }
	}
    }
    conn->co_Ctl = ctl;

    return(ctl);
}

void
FreeControl(Connection *conn)
{
    Control *ctl;

    if ((ctl = conn->co_Ctl) != NULL) {
	if (ctl->ct_LogFo) {
	    fflush(ctl->ct_LogFo);
	    hflock(fileno(ctl->ct_LogFo), 0, XLOCK_UN);
	    fclose(ctl->ct_LogFo);
	}
	if (ctl->ct_TmpFileName)
	    remove(ctl->ct_TmpFileName);

	zfreeStr(&conn->co_MemPool, &ctl->ct_Msg);
	zfreeStr(&conn->co_MemPool, &ctl->ct_From);
	zfreeStr(&conn->co_MemPool, &ctl->ct_Verify);
	zfreeStr(&conn->co_MemPool, &ctl->ct_Groups);
	zfreeStr(&conn->co_MemPool, &ctl->ct_TmpFileName);

	zfree(&conn->co_MemPool, ctl, sizeof(Control));
	conn->co_Ctl = NULL;
    }
}

/*
 * SendMail() - send mail to administrator
 *
 * Mail is sent to MAIL_ADMIN_ADDR.  This cannot block, so queue the mail with -odq
 */

void
SendMail(FILE *logf, long lpos, char *hostname, const char *cmd, const char *arg)
{
    pid_t pid;
    char *argv[] = { SENDMAIL_PATH, SENDMAIL_ARG0, "-t", "-odq", NULL };
    int fds[3];

    if ((pid = RunProgramPipe(fds, RPF_STDOUT, argv, NULL)) > 0) {
	FILE *fo = fdopen(fds[1], "w");
	char buf[128];

	fprintf(fo, "To: %s\n", safestr(DOpts.NewsMaster, "news"));
	fprintf(fo, "From: news@%s\n", hostname);
	fprintf(fo, "Subject: Control message %s %s\n", cmd, (arg ? arg : ""));
	fprintf(fo, "\n");
	fseek(logf, lpos, 0);
	while (fgets(buf, sizeof(buf), logf) != NULL)
	    fputs(buf, fo);
	fclose(fo);
	waitpid(pid, NULL, 0);
    } else {
	logit(LOG_ERR, "Unable to run sendmail (trying to email control message)");
    }
}

/*
 * PerformControl() - perform control function
 */

const char *
PerformControl(Connection *conn, Control *ctl, const char **cmd, const char *art, const char *body, int bodyLen, char *groups)
{
    const char *badCmd = NULL;

    if (strcasecmp(cmd[0], "cancel") == 0 && cmd[1]) {
#ifndef	DIRTYSPOOL
	badCmd = CtlCancel(conn, ctl, cmd, art, body, bodyLen, groups);
#endif
    } else if (strcasecmp(cmd[0], "newgroup") == 0 && cmd[1]) {
	badCmd = CtlNewGroup(conn, ctl, cmd, body, bodyLen);
    } else if (strcasecmp(cmd[0], "rmgroup") == 0 && cmd[1]) {
	badCmd = CtlRmGroup(conn, ctl, cmd, body, bodyLen);
    } else if (strcasecmp(cmd[0], "checkgroups") == 0) {
	badCmd = CtlCheckGroups(conn, ctl, cmd, body, bodyLen);
    } else {
	badCmd = "Unknown control\n";
    }
    return(badCmd);
}

/*
 * CANCEL - cancel an article.  cmd[1] is the "<message-id>" of the article.
 *
 * Cancels are messy because dreaderd is oriented towards group/article-number
 * combinations.  message-id's are used to lookup articles in remote spools, but
 * dreaderd does not index overview information by message-id!
 *
 * What we do is take the newsgroup list and scan the overview data for the
 * message-id.
 *
 * 1) Find OverInfo (index) for group (no locking)
 * 2) Lock index
 * 3) Lookup article info (OverArt)
 * 4) Write cancelled article
 * 5) Unlock index
 */

const char *
CtlCancel(Connection *conn, Control *ctl, const char **cmd, const char *art, const char *body, int bodyLen, char *groups)
{
    char *ptr;
    int isValidCancel = 0;
    int validGroups = 0;
    const char *xtrace;
    int xtLen = 0;
    int count = 0;

    /*
     * Locate X-Trace: header.  Set xtrace & xtLen to point to first element 
     * if found.
     */

    xtrace = locateXTrace(art, body - art, &xtLen);

    /*
     * Find a valid overview records, check headers on first, and cancel
     */

    ptr = groups;

    while (*ptr) {
	int l;
	char c;
	OverInfo *ov;
	int artNo;

	for (l = 0; ptr[l] && ptr[l] != ','; ++l)
	    ;
	c = ptr[l];
	ptr[l] = 0;
	ov = FindCanceledMsg(ptr, cmd[1], &artNo, &validGroups);

	if (ov != NULL) {
	    ++count;

	    if (isValidCancel == 0) {
		isValidCancel = 1;
#ifdef NOTDEF
		/*
		 * Get article contents, set isValidCancel to 1 or -1
		 */
		int hlen;
		const char *data = GetOverRecord(ov, artNo, &hlen, NULL, NULL, NULL);
		if (data != NULL) {
		    int cxLen;
		    const char *cxtrace = locateXTrace(data, hlen, &cxLen);

		    if (xtrace && cxtrace) {
			if (cxLen==xtLen && bcmp(xtrace, cxtrace, cxLen) == 0)
			    isValidCancel = 1;
			else
			    isValidCancel = -1;
		    } else if (xtrace == NULL && cxtrace == NULL) {
			isValidCancel = 1;
		    } else {
			isValidCancel = -1;
		    }
		}
#endif
	    }
	    if (isValidCancel > 0)
		CancelOverArt(ov, artNo);
	    PutOverInfo(ov);
	}
	ptr += l;
	if ((*ptr = c) != 0)
	    ++ptr;
    }

    /*
     * If we could not find the article anywhere and if at 
     * least one of the groups is valid, we allow the cancel and put
     * it in the pre-cancel cache.  The article will be canceled if it
     * comes in later on.  Note that when this case occurs, no X-Trace
     * comparing is done... we assume that anyone able to cancel an article
     * that quickly is either reasonably close to official, or can't 
     * generate any significant damage anyway.
     */

    if (isValidCancel == 0) {
	if (validGroups)
	    EnterCancelCache(cmd[1]);
	if (DebugOpt)
	    printf("CtlCancel unverified, canceled %d items: %s %s\n", count, cmd[1], groups);
    } else if (isValidCancel > 0) {
	if (DebugOpt)
	    printf("CtlCancel verified, canceled %d items: %s %s\n", count, cmd[1], groups);
    } else {
	if (DebugOpt)
	    printf("CtlCancel failed verify, did NOT cancel: %s %s\n", cmd[1], groups);
    }
    if (ctl->ct_LogFo) {
	fprintf(ctl->ct_LogFo, ">> control cancel, %d items found, %s\n", 
	    count,
	    ((isValidCancel) ?
		((isValidCancel < 0) ? 
		    "trace mismatch, nothing canceled" :
		    "trace match ok") 
		: 
		(validGroups ? 
		    "unverified, pre-cancel cached" : 
		    "no valid groups, nothing canceled" 
		)
	    )
	);
    }
    return(NULL);
}

/*
 * NEWGROUP -
 */

const char *
CtlNewGroup(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen)
{
    const char *flags = "y";
    const char *rec;
    int recLen;
    char *moderator = NULL;
    char *description = NULL;
    int cmd1Len = strlen(cmd[1]);
    int isNew = 0;
    int hasCts = 0;

    if (ValidGroupName(cmd[1]) < 0) {
	if (ctl->ct_LogFo)
	    fprintf(ctl->ct_LogFo, ">> Newgroup: Illegal newsgroup name\n");
	return(NULL);
    }
    /*
     * cmd[2] may be NULL, 'y', or 'm'.
     */
    if (cmd[2] && cmd[2][0] == 'm')
	flags = "m";

    /*
     * Read and lock the record.  If the record does not exist, create a new
     * record (and lock that).
     */

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

    /*
     * Look for:  'For your newsgroups file:', next line: 'groupname comment'
     * Look for:  'Group submission address: moderator_email'
     */

    {
	int i;
	int l = 0;
	int lookFor = 0;

	for (i = 0; i < bodyLen; i = l) {
	    for (l = i; l < bodyLen && body[l] != '\n'; ++l)
		;
	    if (l < bodyLen)
		++l;
	    if (strncmp(body + i, "For your newsgroups file:", 25) == 0) 
		lookFor = 5;
	    if (moderator == NULL && strncmp(body + i, "Group submission address:", 25) == 0)
		moderator = zallocStrTrim(&conn->co_MemPool, body + i + 25, l - i - 25);
	    if (lookFor && description == NULL) {
		if (strncmp(body + i, cmd[1], cmd1Len) == 0) {
		    description = zallocStrTrim(&conn->co_MemPool, body + i + cmd1Len, l - i - cmd1Len);
		    lookFor = 0;
		} else {
		    --lookFor;
		}
	    }
	}
    }

    if (moderator) {
	SanitizeString(moderator);
	KPDBWriteEncode(KDBActive, cmd[1], "M", moderator, KP_LOCK_CONTINUE);
	zfreeStr(&conn->co_MemPool, &moderator);
    }
    if (description) {
	SanitizeDescString(description);
	KPDBWriteEncode(KDBActive, cmd[1], "GD", description, KP_LOCK_CONTINUE);
	zfreeStr(&conn->co_MemPool, &description);
    }

    {
	/*
	 * 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, cmd[1], "CTS", tsBuf, KP_LOCK_CONTINUE);
	KPDBWrite(KDBActive, cmd[1], "LMTS", tsBuf, KP_LOCK_CONTINUE);
    }

    KPDBWrite(KDBActive, cmd[1], "S", flags, KP_UNLOCK);

    if (ctl->ct_LogFo) {
	if (isNew) {
	    fprintf(ctl->ct_LogFo, ">> Newgroup: Created new group %s flags=%s\n",
		cmd[1], 
		flags
	    );
	} else {
	    fprintf(ctl->ct_LogFo, 
		">> Newgroup: updated group %s flags=%s moderator=%s description=%s\n",
		cmd[1], 
		flags,
		(moderator ? moderator : "no chg"),
		(description ? description : "no chg")
	    );
	}
    }
    return(NULL);
}

const char *
CtlRmGroup(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen)
{
    const char *rec;
    int recLen;

    if (ValidGroupName(cmd[1]) < 0) {
	if (ctl->ct_LogFo)
	    fprintf(ctl->ct_LogFo, ">> Rmgroup: Illegal newsgroup name\n");
	return(NULL);
    }

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

    if ((rec = KPDBReadRecord(KDBActive, cmd[1], 0, &recLen)) != NULL) {
	KPDBDelete(KDBActive, cmd[1]);
	if (ctl->ct_LogFo)
	    fprintf(ctl->ct_LogFo, ">> RmGroup: Deleted group %s\n", cmd[1]);
    }
    return(NULL);
}

/*
 * checkgroups
 *
 *	Message body contains several 'groupname comment' lines.  Update the active file
 *	as appropriate.
 *
 *	Each group in the sublist must be verified against the Control newsgroup wildcard.
 */

const char *
CtlCheckGroups(Connection *conn, Control *ctl, const char **cmd, const char *body, int bodyLen)
{
    int i;
    int l = 0;

    for (i = 0; i < bodyLen; i = l) {
	char tmp[128];
	char *newsgroup;
	char *description = NULL;

	for (l = i; l < bodyLen && body[l] != '\n'; ++l)
	    ;
	if (l < bodyLen)
	    ++l;
	if (l - i >= sizeof(tmp))
	    continue;
	memcpy(tmp, body + i, l - i);
	tmp[l-i] = 0;

	if ((newsgroup = strtok(tmp, " \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;

	/*
	 * 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;
	    }
	}

	/*
	 * The group must match the ct_Groups field from the dcontrol.ctl 
	 * file.
	 */

	if (WildOrCmp(ctl->ct_Groups, newsgroup) == 0) {
	    KPDBWriteEncode(KDBActive, newsgroup, "GD", description, 0);
	}
    }
    return(NULL);
}

/*
 * ExecuteSupersedes(): cancel the article pointed to by the supersedes.
 * We dummy-up a cancel control.
 */

void
ExecuteSupersedes(Connection *conn, const char *supers, const char *art, int artLen)
{
    char *cmd = zalloc(&conn->co_MemPool, strlen(supers) + 32);

    sprintf(cmd, "cancel %s", supers);
    ExecuteControl(conn, cmd, art, artLen);
    zfree(&conn->co_MemPool, cmd, strlen(supers) + 32);
}

const char *
locateXTrace(const char *art, int headLen, int *pxtLen)
{
    while (headLen > 0) {
	int len;

	for (len = 0; len < headLen && art[len] != '\n'; ++len)
	    ;
	if (len < headLen)
	    ++len;
	if (len > 8 && strncasecmp(art, "X-Trace:", 8) == 0) {
	    art += 8;
	    len -= 8;
	    while (len && (*art == ' ' || *art == '\t')) {
		++art;
		--len;
	    }
	    for (headLen = 0; headLen < len; ++headLen) {
		if (art[headLen] == ' ' ||
		    art[headLen] == '\t' ||
		    art[headLen] == '\r' ||
		    art[headLen] == '\n'
		) {
		    break;
		}
	    }
	    break;
	}
	headLen -= len;
	art += len;
    }
    if (headLen <= 0) {
	headLen = 0;
	art = NULL;
    }
    *pxtLen = headLen;
    return(art);
}



syntax highlighted by Code2HTML, v. 0.9.1