/*
 * A triv program to parse a mail and accept simple commands.
 *
 * Date		Who	What
 * -------	----	----------------------------------------
 * 19 Jul 93	MarkD	Initial coding.
 * 20 Jul 93	MarkD	Incorporated AHerb's marvellous changes
 *
 * Mail comes in on stdin.
 *
 * Valid commands are:
 *
 *	site		name		passwd
 *	include		<group-pattern>
 *	exclude 	<group-pattern>
 *	list
 *	newsgroups	<group-pattern>
 *	delete		<group-pattern>
 *	quit
 *
 * Case is irrelevant.
 * group can be a wildmat pattern.
 *
 * If any command has changed the current grouplists for a given site
 * then the list is pushed through prune.c which does a fairly thorough
 * test to ensure that the list makes sense. Anything non-sensical is
 * removed.
 *
 * The delete group is checked against the currently subscribed list
 * rather than the active.
 */

#define	MAIN

#include "gup.h"
#include "rfc822.h"


char	*progname = "gup";		/* GC */
FILE	*log_fp = stderr;

static	char	*usage =
"\n\
Usage: %s\t[-hvP] -a active_path [-d home_directory] [-l log_path]\n\
\t\t[-m reply_headers] [-n newsgroups_path] [-s sites_directory]\n\
[-M Mail_command]\n\
\n\
Group Update Program: mail-server for remote newfeed changes.\n\
\n\
-a\tThe news system's 'active' file - used to validate newsgroups\n\
-d\tgup's home directory (default: .)\n\
-h\tPrint usage then exit\n\
-l\tLog file for writing significant events (default: ./log)\n\
-m\tfile containing rfc822 headers to send to the recipient\n\n\
-n\tThe newsgroups file - used for newsgroups descriptions\n\
-M\tResultant mail piped to this command\n\
-P\tDo not prune newsgroup selections (prune is CP intensive)\n\
-s\tLocation of the site directories (default: ./sites)\n\
-v\tPrint version and build options, then exit.\n\
\n";


typedef	struct	command_s	COMMAND;

struct	command_s {
    char	*name;
    int		(*handler)();		/* function to handle it */
    int		args;
    int		needs_site;
    int		sets_quit;
};


static	void	parse_options();
static	void	parse_headers();
static	int	parse_commands();
static	void	load_active();
static	void	write_groups();
static	void	print_version();
static	void	log_mail_headers();
static	void	item_print __P((int indent, int *column, const char *str));

static	int	getoneline();
static	int	tokenize();
static	int	command_cmp();
static	int	site();
static	int	include();
static	int	exclude();
static	int	insert_group();
static	int	delete();
static	int	list();
static	int	quit();
static	int	newsgroups();


/* Here's the command list from the body of the mail */

static	COMMAND	C_list[] = {
    { "inc*lude", include, 2, TRUE, FALSE },
    { "+", include, 2, TRUE, FALSE},
    { "exc*lude", exclude, 2, TRUE, FALSE },
    { "-", exclude, 2, TRUE, FALSE },
    { "l*ist", list, 0, TRUE, FALSE },
    { "he*lp", help, 0, FALSE, TRUE },
    { "del*ete", delete, 2, TRUE, FALSE },
    { "s*ite", site, 3, FALSE, FALSE },
    { "o*pen", site, 3, FALSE, FALSE },
    { "host", site, 3, FALSE, FALSE },
    { "news*groups", newsgroups, 2, TRUE, FALSE },
    { "q*uit", quit, 0, TRUE, TRUE },
    { "c*lose", quit, 0, TRUE, TRUE },
    { NULL }
};


static	int	seen_site = FALSE;	/* If site command accepted */
static	int	do_prune = TRUE;
static	char	*sitename;

static	char	*site_directory = SITE_DIRECTORY;

static	char	curr_line[MAX_LINE_SIZE];

static	int	quit_flag;

/* Variables used to control mailed results */

static	char	*h_from = NULL;
static	char	*h_reply_to = NULL;
static	char	*h_date = NULL;
static	char	*h_subject = NULL;
static	char	*h_message_id = NULL;
static	char	*h_return_path = NULL;

/* Externals */

extern	char	*optarg;
extern	int	optind;


/*
 * The major note for this program is that calls to logit() should be
 * avoided as much as possible prior to the 'site' command. The reason
 * for this is that we have a "less-than-ideal" mail recipient before
 * that time.
 *
 * You see the mail recipient starts out as the backstop mailid
 * (typically news or root or some local admin name) as the inbound
 * headers are cracked this changes to the FROM: address then the
 * REPLY-TO: address. However, it's not until a valid site command is
 * parsed do we know the mailid of the 'defined' admin.
 *
 * So, the net effect is that to earlier you put in logging calls the
 * more change you have of irritating the person on the end of the
 * admin address un-necessarily ;>
 */

void
main(argc, argv)

    int		argc;
    char	**argv;

{

    int		changed;

    umask(UMASK);

    parse_options(argc, argv);		/* Decode our command line options */

    quit_flag = FALSE;
    parse_headers();			/* Crack the inbound mail headers */
    changed = parse_commands();		/* Process the body of the mail */

    if (!seen_site)
	gupout(1, "No 'site' command found");

    if (changed) {			/* Only prune and check if changed */
	if (do_prune) {
	    putc('\n', mail_fp);
	    logit(L_BOTH | L_TIMESTAMP, "", "Checking new group list");
	    prune(active_list, group_list);
	}
	logit(L_BOTH | L_TIMESTAMP, "", "Writing updated group list");
	write_groups();
    }

    gupout(0, (char *) NULL);

    /* NOTREACHED */
}


/* Parse the command line options */

static	void
parse_options(argc, argv)

    int		argc;
    char	**argv;

{
    int		cc;

    log_filename = NULL;
    active_path = ACTIVE_PATH;
    newsgroups_path = NEWSGROUPS_PATH;
    mail_fp = mail_open(FALSE, BACKSTOP_MAILID, MAIL_COMMAND, (char *) NULL);

    while ((cc = getopt(argc, argv, "a:d:hs:l:n:m:M:Pv")) != EOF) {
	switch (cc) {
	  case 'a':	active_path = optarg; break;
	  case 'd':	if (optarg) {
			    if (chdir(optarg)) {
				sprintf(msg, "Could not chdir to %s", optarg);
				gupout(1, msg);
			    }
	  		}
			break;

	  case 'h':	fprintf(stderr, usage, progname);
	    		exit(0);

	  case 's':	site_directory = optarg; break;
	  case 'l':	if (optarg) {
			    log_fp = fopen(optarg, "a");
			    if (!log_fp) {
				sprintf(msg, "%s: Could not open log file: %s\n",
					progname, log_filename);
				gupout(1, msg);
			    }
			    if (dup2(fileno(log_fp), fileno(stderr)) < 0)
				gupout(1, "dup2() failed");
			}
			break;

	  case 'm':	mail_fp = mail_open(FALSE, (char *) NULL,
					    (char *) NULL, optarg);
	    		break;

	  case 'M':	mail_fp = mail_open(FALSE, (char *) NULL,
					    optarg, (char *) NULL);
	    		break;

	  case 'P':	do_prune = FALSE; break;

	  case 'n':	newsgroups_path = optarg; break;

	  case 'v':	print_version();
	    		exit(0);

	  default:	gupout(1,"Installation problem. Invalid option used");
	}
    }

/* Must have an active file that can be opened */

    if (!active_path || !*active_path) {
	fprintf(stderr, "%s: Must nominate the active file with -a\n",
		progname);
	fprintf(stderr, usage, progname);
	gupout(1, (char *) NULL);
    }
}

static	void
print_version()

{
    fprintf(stderr, "%s: Version: %s. Patchlevel: %d\n",
	    progname, VERSION, PATCHLEVEL);

    fprintf(stderr, "CONFIG:           '%s'\n", CONFIG);
    fprintf(stderr, "ACTIVE_PATH:      '%s'\n", ACTIVE_PATH);
    fprintf(stderr, "NEWSGROUPS_PATH:  '%s'\n", NEWSGROUPS_PATH);
    fprintf(stderr, "MAIL_COMMAND:     '%s'\n", MAIL_COMMAND);
    fprintf(stderr, "BACKSTOP_MAILID:  '%s'\n", BACKSTOP_MAILID);
    fprintf(stderr, "UMASK:            %03o\n", UMASK);

#ifndef	NO_FLOCK
    fprintf(stderr, "LOCKING uses:     FLOCK\n");
#endif
#ifdef	FILE_LOCK
    fprintf(stderr, "LOCKING uses:     FILE_LOCK\n");
#endif
}


/*
 * Parse the mail headers and note the interesting ones. Note that we
 * stash all of the interesting headers for later logging and use.
 * Note that this routine avoids calling logit() because we are so
 * early on in the process that our mailid is bound to be the backstop
 * mailid.
 */

static	void
parse_headers()

{
    while (getoneline(stdin)) {
	if (!curr_line[0]) return;

	if (strncasecmp(curr_line, "FROM:", 5) == 0) {
	    char	buf1[LG_SIZE], buf2[LG_SIZE];

	    CrackFrom(buf1, buf2, curr_line+5);
	    h_from = xstrdup(buf1);
	    if (!h_reply_to) {
		mail_fp = mail_open(FALSE, h_from,
				    (char *) NULL, (char *) NULL);
	    }
	    continue;
	}
	if (strncasecmp(curr_line, "REPLY-TO:", 9) == 0) {
	    char	buf1[LG_SIZE], buf2[LG_SIZE];

	    CrackFrom(buf1, buf2, curr_line+9);
	    h_reply_to = xstrdup(buf1);
	    mail_fp = mail_open(FALSE, h_reply_to,
				(char *) NULL, (char *) NULL);
	    continue;
	}
	if (strncasecmp(curr_line, "DATE:", 5) == 0) {
	    h_date = xstrdup(curr_line);
	    continue;
	}
	if (strncasecmp(curr_line, "SUBJECT:", 8) == 0) {
	    h_subject = xstrdup(curr_line);
	    continue;
	}
	if (strncasecmp(curr_line, "MESSAGE-ID:", 11) == 0) {
	    h_message_id = xstrdup(curr_line);
	    continue;
	}
	if (strncasecmp(curr_line, "RETURN-PATH:", 12) == 0) {
	    h_return_path = xstrdup(curr_line);
	    continue;
	}
    }
}

/* Process the commands in the body of the mail */

static	int
parse_commands()

{
    char	*command;
    COMMAND	*cmdp;
    char	*tokens[10];
    int		tcount;
    int		changed;

    changed = FALSE;

    while (!quit_flag && getoneline(stdin)) {

/* Need a copy of the command for logging as tokenize fills it with \0 */

	strncpy(msg, curr_line, sizeof(msg));
	msg[sizeof(msg)-1] = '\0';

	tcount = tokenize(curr_line, tokens, 10);
	if (tcount == 0) continue;

/* Search for a matching command */

	command = tokens[0];
	for (cmdp=C_list; cmdp->name; cmdp++) {
	    if (command_cmp(command, cmdp->name) == 0) break;
	}

/* Find it? */

	if (!cmdp->name) {
	    sprintf(msg, "Unrecognized command '%s' - try 'help'", command);
	    gupout(1, msg);
	}

/* Does this command require a preceeding site command? */

	if (cmdp->needs_site) {
	    if (!seen_site) {
		sprintf(msg, "'site' command must preceed the '%s' command",
			cmdp->name);
		gupout(1, msg);
	    }
	}

/* It's safe to log commands after we've seen the site as mail is right */

	if (seen_site) {
	    logit(L_MAIL, "", "");
	    logit(L_BOTH, "", msg);
	}

/* Only check # of args if defined as > 0 */

	if ((cmdp->args > 0) && (tcount != cmdp->args)) {
	    sprintf(msg, "%s required %d parameters, not %d\n",
		    command, cmdp->args, tcount);
	    logit(L_BOTH, "WARNING", msg);
	    continue;
	}

/* Dispatch to handler */

	if (cmdp->handler(tokens+1)) changed = TRUE;

/* Auto quit? */

	if (cmdp->sets_quit) quit_flag = TRUE;
    }

    return changed;
}



/* 	site	sitename	passwd */

static	int
site(tokens)

    char	**tokens;

{
    FILE	*fp;
    int		fd;
    char	*hf_tokens[4];
    int		found_site;
    int		tcount;
    LIST	*exclusion_list = NULL;
    char	lbuf[MAX_LINE_SIZE];

    if (seen_site)
	gupout(1, "Multiple 'site' commands not allowed");

/* Search the config file for a valid sitename */

    fp = fopen(CONFIG_FILENAME, "r");
    if (!fp) {
	sprintf(msg, "Install error. Open of '%s' failed", CONFIG_FILENAME);
	gupout(1, msg);
    }

/* Search for matching site */

    found_site = FALSE;
    while (fgets(lbuf, sizeof(lbuf), fp)) {
	tcount = tokenize(lbuf, hf_tokens, 4);
	if (tcount == 0) continue;
	if (tcount != 3) {
	    sprintf(msg, "%s format incorrect for %s. Need 3 tokens",
		    CONFIG_FILENAME, tokens[0]);
	    gupout(1, msg);
	}

	if (strcasecmp(tokens[0], hf_tokens[0]) == 0) {
	    found_site = TRUE;
	    break;
	}
    }

    fclose(fp);

    if (!found_site) {
	sprintf(msg, "Unknown site '%s'", tokens[0]);
	gupout(1, msg);
    }

/*
 * Ok. We now have the *correct* admin mail ID. Start the mail output.
 * We set up even before completing the site command so that password
 * cracking attempts go to the site admin, not the sender.
 */

    mail_fp = mail_open(TRUE, hf_tokens[2], (char *) NULL, (char *) NULL);

/*
 * Right.  Now mail_fp is guaranteed to be open, and stdout points there as
 * well.  Let's catch up on all that good log stuff we want to tell them.
 */

    logit(L_LOG, "", "");
    sprintf(msg, "%s version: %s", progname, VERSION);
    logit(L_BOTH | L_TIMESTAMP, "BEGIN", msg);
    logit(L_BOTH, "", "");

    log_mail_headers();

    sprintf(msg, "site %s", tokens[0]);
    logit(L_BOTH, "", msg);

/* Found the site how does the password look? */

    if (strcmp(tokens[1], hf_tokens[1]) != 0)
	gupout(1, "Password incorrect");

    sitename = xstrdup(hf_tokens[0]);
    seen_site = TRUE;

/* It's all good, accept this site as the directory name and cd to it */

    while (chdir(site_directory)) {
	if (errno == ENOENT) {
	    logit(L_LOG, "MKDIR", site_directory);
	    if (mkdir(site_directory, 0777)) {
		sprintf(msg,
		    "Install error: could not MKDIR %s (%s)", site_directory,
		    strerror(errno));
		gupout(1, msg);
	    }
	}
	else {
	    sprintf(msg,
		"Install error: could not CHDIR to %s (%s)", site_directory,
		strerror(errno));
	    gupout(1, msg);
	}
    }

    while (chdir(sitename)) {
	if (errno == ENOENT) {
	    logit(L_LOG, "MKDIR", sitename);
	    if (mkdir(sitename, 0777)) {
		sprintf(msg,
		    "Install error: could not MKDIR %s (%s)", sitename,
		    strerror(errno));
		gupout(1, msg);
	    }
	}
	else {
	    sprintf(msg, "Install error: could not CHDIR to %s (%s)", sitename,
		strerror(errno));
	    gupout(1, msg);
	}
    }

    if (!lockit()) {
	sprintf(msg, "Failed to take lock in %s", lbuf);
	gupout(1, msg);
    }

    fd = open(EXCLUSIONS_FILENAME, O_RDONLY);
    if (fd >= 0) {
	exclusion_list = read_groups(fd, (LIST *) NULL);
	close(fd);
    }

    load_active(exclusion_list);

/* Open the sites' groups file - it's ok if it's not there btw */

    fd = open(BODY_FILENAME, O_RDONLY);
    if (fd >= 0) {
	group_list = read_groups(fd, (LIST *) NULL);
	close(fd);
    }
    else {
	group_list = create_list();	/* we need something... */
	sprintf(msg, "File '%s' not found for %s", BODY_FILENAME, sitename);
	logit(L_LOG, "WARNING", msg);
    }

    return FALSE;
}


static	int
quit(tokens)

    char	**tokens;

{
    return 0;
}


/* Check and load the active file */

static	void
load_active(exclusion_list)

    LIST	*exclusion_list;

{
    int	fd;

    fd = open(active_path, O_RDONLY);
    if (fd < 0) {
	sprintf(msg, "Could not open active file '%s'", active_path);
	gupout(1, msg);
    }

    active_list = read_groups(fd, exclusion_list);
    close(fd);

    if (!active_list->length) {
	sprintf(msg, "No groups found in active file '%s'", active_path);
	gupout(1, msg);
    }
}


/*
 * Writes the new group list back to disk moderately safely.
 */

static	void
write_groups()

{

    FILE	*fp;
    GROUP	*gp;
    int		write_count;

    fp = fopen(NEWBODY_FILENAME, "w");
    if (!fp) {
	sprintf(msg, "Could not open %s for writing", NEWBODY_FILENAME);
	gupout(1, msg);
    }

    write_count = 0;
    TRAVERSE(group_list, gp) {
	write_count++;
	if (gp->u.not) putc('!', fp);
	fputs(gp->name, fp);
	putc('\n', fp);
    }

    fclose(fp);

    sprintf(msg, "%s: group patterns: %d", sitename, write_count);
    logit(L_BOTH|L_TIMESTAMP, progname, msg);

/*
 * In the directory is:	groups, groups.new and groups.old
 *
 * rename(groups,old)
 * rename(new,groups)
 */

    if ((rename(BODY_FILENAME, OLDBODY_FILENAME) == -1) && (errno != ENOENT)) {
	sprintf(msg, "rename(%s,%s) failed for %s. Errno=%d",
		BODY_FILENAME, OLDBODY_FILENAME, sitename, errno);
	gupout(1, msg);
    }

    if (rename(NEWBODY_FILENAME, BODY_FILENAME) == -1) {
	sprintf(msg, "rename(%s,%s) failed for %s. Errno=%d",
		NEWBODY_FILENAME, BODY_FILENAME, sitename, errno);
	gupout(1, msg);
    }
}



/* Include a pattern into the site's group list */

static	int
include(tokens)

    char	**tokens;

{
    return insert_group(FALSE, tokens[0]);
}

static	int
exclude(tokens)

    char	**tokens;

{
    return insert_group(TRUE, tokens[0]);
}

static	int
insert_group(not_flag, gname)

    int		not_flag;
    char	*gname;

{

    GROUP	*gp, *new_gp;
    int		match_count;
    int		column, indent;

/* Be optimistic, create the group now */

    new_gp = create_group(not_flag, gname);

/* See if the add pattern matches anything in active */

    fputs("  => ", mail_fp);
    column = indent = 5;

    match_count = 0;
    TRAVERSE(active_list, gp) {
	if (wildmat(gp->name, new_gp->name)) {
	    match_count++;
	    if (match_count <= LOG_MATCH_LIMIT) {
		item_print(indent, &column, gp->name);
		if (match_count == LOG_MATCH_LIMIT)
		    item_print(indent, &column, "...etc...");
	    }
	}
    }

    if (match_count == 0) {
	destroy_group(new_gp);
	sprintf(msg, "%s pattern not in active - ignored", gname);
	logit(L_MAIL, "WARNING", msg);
	return FALSE;
    }

    putc('\n', mail_fp);
    if (match_count > 1) {
	fprintf(mail_fp, "  [ %d groups ]\n", match_count);
    }

/* Link the new add pattern into the end */

    add_group(group_list, new_gp);		

    return TRUE;
}


/* Delete matches on the group list, not the active list */

static	int
delete(tokens)

    char	**tokens;

{
    char	*gname;
    GROUP	*gp;
    int		match_count;

    gname = tokens[0];

/* See if the delete pattern matches anything in the current groups */

    match_count = 0;
    TRAVERSE(group_list, gp) {
	if (wildmat(gp->name, gname)) {
	    match_count++;
	    if (match_count <= LOG_MATCH_LIMIT) {
		fprintf(mail_fp, "\tMatches: %s %s",
			gp->u.not ? "exclude" : "include", gp->name);
	    }
	    if (match_count == LOG_MATCH_LIMIT) {
		fputs("\tMatches: ...etc...", mail_fp);
	    }
	    remove_group(group_list, gp);
	}
    }

    sprintf(msg, "Match count %d", match_count);
    logit(L_BOTH, " delete", msg);
    if (match_count == 0) {
	sprintf(msg, "%s pattern not in %s - ignored", gname, BODY_FILENAME);
	logit(L_MAIL, "WARNING", msg);
    }

    return (match_count > 0);
}


static void
item_print(indent, column, str)

    int		indent;
    int		*column;
    const char	*str;

{

#define MAX_COL	77	/* number of columns in a line, with room for a ", " */
    int		len = strlen(str);

    if (*column + len > MAX_COL) {
	int	col = indent;
	char	ch;

	putc(',', mail_fp);
	putc('\n', mail_fp);

	if (col % 8)
	    ch = ' ';
	else {
	    ch = '\t';
	    col /= 8;
	}
	while (col--)
	    putc(ch, mail_fp);

	*column = indent;
    }

    if (*column != indent) {
	putc(',', mail_fp);
	putc(' ', mail_fp);
    }
    fputs(str, mail_fp);

    *column += len + 2;
}


/* List out the currently selected groups */

static	int
list(tokens)

    char	**tokens;

{
    GROUP	*gp;

    TRAVERSE(group_list, gp) {
	fprintf(mail_fp, "  %s  %s\n", gp->u.not ? "  exclude" : "include",
	    gp->name);
    }

    return 0;
}


/* List out the active file entries that match the pattern */

static	int
newsgroups(tokens)

    char	**tokens;

{
    print_newsgroups(mail_fp, tokens[0]);

    return 0;
}


/*
 * Read the next line into curr_line. Trim trailing whitespace.
 * Return TRUE if a line was read, otherwise return FALSE.
 */

static	int
getoneline(fp)

    FILE	*fp;

{
    int		len;

    if (!fgets(curr_line, sizeof(curr_line)-1, fp)) {
	quit_flag = TRUE;
	return FALSE;
    }

    curr_line[sizeof(curr_line)-1] = '\0';

/* Trim trailing whitespace */

    len = strlen(curr_line);
    while ((len > 0) && isspace(curr_line[len-1])) len--;
    curr_line[len] = '\0';

    return TRUE;
}


/*
 * A simple whitespace tokenizer - no continutation, no quotes, no
 * nuthin' Does accept comments '#' and empty lines.
 */

static	int
tokenize(cp, tokens, max_tokens)

    char	*cp;
    char	**tokens;
    int		max_tokens;

{
    int		tix;
    char	*com_cp;

/* Find comment */

    for(com_cp=cp; *com_cp; com_cp++) {
	if (*com_cp == '#') {
	    *com_cp = '\0';
	    break;
	}
    }

/* Why don't I use scanf? Good question... */

    for (tix=0; tix < max_tokens; tix++) tokens[tix] = "";

    for (tix=0; tix < max_tokens; tix++) {

	while (*cp && isspace(*cp)) cp++;	/* Skip leading whitespace */
	tokens[tix] = cp;

	while (*cp && !isspace(*cp)) cp++;	/* Skip data */

	if (cp == tokens[tix]) break;		/* End of data stream */
	if (*cp) *cp++ = '\0';
    }

    return tix;
}


static	int
command_cmp(str, cmd)

    char	*str, *cmd;
{
    int		base = 0;	/* have we matched the base of the cmd? */

    while (*str && *cmd) {
	if (tolower(*str) != *cmd)
	    return TRUE;	/* match failed */
	str++;
	cmd++;
	if (*cmd == '*') {
	    base = TRUE;
	    cmd++;
	}
    }

    return !(!*str && (base || !*cmd));
}



void
gupout(val, out_msg)

    int		val;
    const char	*out_msg;

{
    /* release our lock */
    unlockit();

    if (out_msg)
	logit(L_BOTH, "ERROR", out_msg);

    log_mail_headers();
    logit(L_BOTH, "", "");
    logit(L_BOTH | L_TIMESTAMP, "END", progname);

    if (log_fp) fclose(log_fp);
    if (mail_fp) mail_close(mail_fp);

/* exit(val) */
/* keep the MTA happy - "don't worry, it's all under control  - honest!" */

    exit(0);
}

/*
 * The mail headers are stashed up by the parse because we don't really
 * want to log them until we absolutely have to. This routine logs them
 * if they haven't already been logged. I don't know that freeing is
 * doing anything much for anyone, but...
 */

static	void
log_mail_headers()

{
    int		log_count = 0;

    if (h_from) {
	logit(L_BOTH, "Mail Header: FROM", h_from);
	free(h_from);
	h_from = NULL;
	log_count++;
    }
    if (h_reply_to) {
	logit(L_BOTH, "Mail Header: REPLY-TO", h_reply_to);
	free(h_reply_to);
	h_reply_to = NULL;
	log_count++;
    }
    if (h_subject) {
	logit(L_BOTH, "Mail Header", h_subject);
	free(h_subject);
	h_subject = NULL;
	log_count++;
    }
    if (h_date) {
	logit(L_BOTH, "Mail Header", h_date);
	free(h_date);
	h_date = NULL;
	log_count++;
    }
    if (h_message_id) {
	logit(L_BOTH, "Mail Header", h_message_id);
	free(h_message_id);
	h_message_id = NULL;
	log_count++;
    }
    if (h_return_path) {
	logit(L_BOTH, "Mail Header", h_return_path);
	free(h_return_path);
	h_return_path = NULL;
	log_count++;
    }

    if (log_count) logit(L_BOTH, "", "");
}
