static char rcsid[] = "enum.c,v 1.86 1996/01/08 09:08:20 duane Exp";
/* 
 *  enum.c - Enumerate RootNode URLs into LeafNode URLs.  Also produce
 *  Timestamps or MD5s for each LeafNode URL to support incremental
 *  Gatherering (using the dbcheck program).
 *
 *  Input:
 *    URL Option1 ... Option N
 *
 *  Output:
 *    URL\tMD5:xxx
 *    URL\tLast-Modification-Time:xxx
 *
 *  Usage:  enum [(-db | -del | -log | -tmpdb) file] [-Ds,l] [-delete] < url_enum_list
 *
 *
 *  DEBUG: section  40, level 1, 5, 9   Gatherer URL enumeration
 *
 *  Jim Guyton, Darren Hardy, Duane Wessels, & Mike Schwartz, July 1994
 *
 *  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <signal.h>
#include <gdbm.h>
#include "util.h"
#include "url.h"
#include "template.h"

/* Local variables */

/* Easily define how to enumerate with the various access methods */
#define Arg_URL         1	/* just give the url as the arg */
#define Arg_Host_File   2	/* 'host path' are the args */
#define Arg_Path        3	/* 'path' is the only arg */

struct enumerators {		/* table is currently ignored */
    int url_type;		/* type this enumerator handles */
    char *url_enum;		/* the pgm name */
    char *special;		/* special flags if any */
    char *stamp_kind;		/* proddb label for the stamp */
    int arg_types;		/* sigh, need until all conform */
} enums[] = {

    {
	URL_FILE, "fileenum", "", T_TIMESTAMP, Arg_URL
    },
    {
	URL_FTP, "ftpenum", "", T_TIMESTAMP, Arg_URL
    },
    {
	URL_GOPHER, "gopherenum", "", T_MD5, Arg_URL
    },
    {
	URL_HTTP, "httpenum", "", T_MD5, Arg_URL
    },
    {
	URL_NEWS, "newsenum", "", T_MD5, Arg_URL
    }
};


#define DBFILE  	"PRODUCTION.gdbm"	/* default proddb */
#define DBNEWFILE 	"tmpdb.gdbm"	/* tmp log of all urls enumed */
#define DELFILE 	"url.del"	/* Stale URLs in proddb */
#define LOGFILE   	"enum.log"	/* default debug logfile */

static char *tree_root = NULL;
static char *addfile = NULL;
static char *delfile = DELFILE;
static char *proddbfile = DBFILE;
static char *logfile = LOGFILE;
static char *tmpdbfile = DBNEWFILE;
static char *cur_attr;		/* set to md5 or time's label */
static char *root;		/* set to root url of enum space */
static int find_deleted = 0;	/* find any deleted objects */

static char url[BUFSIZ];	/* URL */
static int url_max;		/* Option 1 */
static char url_filter[BUFSIZ];	/* Option 2 */
static int host_max;		/* Option 3 */
static char host_filter[BUFSIZ];	/* Option 4 */
static int delay;		/* Option 5 */
static int max_depth;		/* Option 6 */
static int cur_depth;		/* Option 6 */
static char access_types[64];	/* Option 7 */
static char user_enum_pgm[BUFSIZ];	/* Option 8 */

FILE *fadd;			/* the add filehandles (stdout) */
FILE *fdel;			/* the deleted urls */
FILE *flog;			/* log output */

GDBM_FILE proddb;		/* The Gatherer's production database */
GDBM_FILE newdb;		/* A Temporary db to store timestamps */

typedef struct _nodespec {
    char *buf;
    struct _nodespec *next;
} nodespec;

static nodespec *ns_head = 0, *ns_tail = 0;

typedef struct _cpi {
    int childpid;
    char *result_file;
    FILE *wfp;
} cpi;

/* Local functions */
static int do_an_enumeration();
static int get_data();
static int do_kid();
static void usage();
static void init();
static void finish();
static void process_url_stamp();
static void do_add();
static void add_to_newdb();
static void add_to_todo();
static void enum_to_get_deleted();
static void set_envs();
static cpi *create_user_enum_pgm_pipes();

/*
 *  init - Initializes the overall Enumeration process
 */
static void init(argc, argv)
     int argc;
     char *argv[];
{
    debug_init();
    for (argc--, argv++; argc > 0 && **argv == '-'; argc--, argv++) {
	if (strncmp(*argv, "-D", 2) == 0) {
	    debug_flag(*argv);
	} else if (strcmp(*argv, "-delete") == 0) {
	    find_deleted = 1;
	} else if (strcmp(*argv, "-db") == 0) {
	    if (--argc < 1)
		usage();
	    proddbfile = xstrdup(*++argv);
	} else if (strcmp(*argv, "-add") == 0) {
	    if (--argc < 1)
		usage();
	    addfile = xstrdup(*++argv);
	} else if (strcmp(*argv, "-tmpdb") == 0) {
	    if (--argc < 1)
		usage();
	    tmpdbfile = xstrdup(*++argv);
	} else if (strcmp(*argv, "-del") == 0) {
	    if (--argc < 1)
		usage();
	    delfile = xstrdup(*++argv);
	} else if (strcmp(*argv, "-log") == 0) {
	    if (--argc < 1)
		usage();
	    logfile = xstrdup(*++argv);
	} else {
	    usage();
	}
    }

    if ((flog = fopen(logfile, "a+")) == NULL) {
	if (getenv("HARVEST_GATHERER_LOGFILE") != (char *) NULL)
	    flog = fopen(getenv("HARVEST_GATHERER_LOGFILE"), "a+");
	if (flog == (FILE *) NULL)
	    flog = stderr;
	init_log3("enum", flog, stderr);
	fatal_errno(logfile);
    }
    init_log3("enum", flog, stderr);
    setbuf(flog, NULL);
    setbuf(stderr, NULL);

    /* 
     *  open the main proddb and output files; If proddb file is not found, 
     *  we assume a new proddb
     */
    Debug(40, 9, ("using: %s: for production db\n", proddbfile));
    proddb = gdbm_open(proddbfile, 0, GDBM_READER, 0644, 0);

    fadd = stdout;
    if (addfile != NULL) {
	Debug(40, 9, ("using: %s: for addfile\n", addfile));
	if ((fadd = fopen(addfile, "w")) == NULL) {
	    log_errno(addfile);
	    fatal("Internal enum error 2.\n");
	}
    }
    fdel = NULL;
    if (find_deleted) {
	Debug(40, 9, ("using: %s: for delfile\n", delfile));
	if ((fdel = fopen(delfile, "w")) == NULL) {
	    log_errno(delfile);
	    fatal("Internal enum error 3.\n");
	}
    }
}

static void finish()
{
    if (proddb != NULL)
	gdbm_close(proddb);
    if (fdel != NULL)
	fclose(fdel);
    fclose(fadd);
    fclose(flog);
}

/*
 *  process_url_stamp - Check in the database for the URL name with
 *  timestamp/md5 stamp.  If the URL is registered in the database
 *  and the timestamps are the same, then ignore.  If they're different
 *  then add the name to the todo list.
 */
static void process_url_stamp(name, stamp)
     char *name, *stamp;
{
    datum key, d;
    Template *template;
    AVPair *avp;

    key.dptr = name;
    key.dsize = strlen(name);

    d = gdbm_fetch(proddb, key);
    if (d.dptr != NULL) {
	init_parse_template_string(d.dptr, d.dsize);
	if ((template = parse_template()) == NULL)
	    fatal("Cannot parse template with %s\n", key.dptr);
	finish_parse_template();
	free(d.dptr);

	avp = extract_AVPair(template->list, cur_attr);
	if ((avp != NULL) && !strcmp(stamp, avp->value)) {
	    free_template(template);
	    return;
	}
	free_template(template);
    }
    do_add(name, stamp);
}


/* 
 *  do_add() - save the URL/timestamp/md5 data in the to-add pile
 */
static void do_add(name, stamp)
     char *name;		/* the url to 'add' */
     char *stamp;		/* the md5/timestamp from enum */
{
    Debug(40, 9, ("do_add: Saving %s, %s\n", name, stamp));
    fprintf(fadd, "%s\t%s:%s\n", name, cur_attr, stamp);
    fflush(fadd);		/* MUST flush */
}

static void add_to_newdb(name, timestamp)	/* save to my proddb */
     char *name, *timestamp;
{
    datum k;			/* the key  */
    datum d;			/* the data */

    k.dptr = name;		/* the key is url */
    k.dsize = strlen(name) + 1;	/* save the null too */

    d.dptr = timestamp;
    d.dsize = strlen(timestamp) + 1;

    Debug(40, 9, ("add_to_newdb: Marking %s\n", name));
    if (gdbm_store(newdb, k, d, GDBM_INSERT))
	Log("Warning: Ignoring re-visited URL: %s\n", name);
}

static void enum_to_get_deleted()
{				/* get urls to delete */
    datum k, nk;		/* current key in both proddb's */
    datum d;			/* data (if any) in new proddb */
    int rootlen;

    rootlen = strlen(root);	/* don't include null here */

    k = gdbm_firstkey(proddb);	/* get first key in ess proddb */
    while (k.dptr != NULL) {	/* while there are keys */
	if (strncmp(k.dptr, root, rootlen) == 0) {
	    /* if in root url */
	    d = gdbm_fetch(newdb, k);
	    /* see if I've seen this url */
	    if (d.dptr == NULL && fdel != NULL)
		/* if not, then it's old! */
		fprintf(fdel, "%s\n", k.dptr);
	    if (d.dptr != NULL)
		free(d.dptr);
	}
	nk = gdbm_nextkey(proddb, k);
	free(k.dptr);
	k = nk;
    }
}

static cpi *create_user_enum_pgm_pipes(enum_arg)
     char *enum_arg;		/* the URL from which we are doing this enumeration */
{
    int pfd[2], pid, fd;
    char *argv[64], buf[BUFSIZ], *tmpfile;
    static cpi *cpinfo;		/* need static to return var */

    tmpfile = xstrdup(tempnam(NULL, "uep"));
    Debug(40, 1, ("RUNNING enumerator: %s %s > %s\n", user_enum_pgm,
	    enum_arg, tmpfile));

    cpinfo = NULL;
    if (pipe(pfd) < 0) {
	fatal_errno("pipe");
    }
    /* need to use fork() since parse_argv() will cause mem leak */
    if ((pid = fork()) < 0) {
	fatal_errno("fork");
    }
    if (pid == 0) {
	/* child */
	if ((fd = creat(tmpfile, 0664)) < 0) {
	    fatal_errno(tmpfile);
	}
	if ((dup2(pfd[0], 0) < 0) ||	/* read:pipe -> stdin */
	    (dup2(fd, 1) < 0)) {	/* stdout -> tmpfile */
	    fatal_errno("dup2");
	}
	/* Clean up all file descriptors except std{in,out,err} */
	close_all_fds(3);

	/* Need our own copy */
	argv[0] = xstrdup(user_enum_pgm);
	argv[1] = xstrdup(enum_arg);
	argv[2] = NULL;
	set_envs();		/* pass per-enum ENV vars */
	execvp(argv[0], argv);	/* run the script */
	sprintf(buf, "execvp: %s", argv[0]);
	log_errno(buf);
	_exit(1);
    }
    /* parent */
    (void) close(pfd[0]);

    cpinfo = (cpi *) xmalloc(sizeof(cpi));
    cpinfo->childpid = pid;
    cpinfo->result_file = tmpfile;
    cpinfo->wfp = fdopen(pfd[1], "w");
    return (cpinfo);
}

static void finish_user_enum_pgm_pipes(cpinfo)
     cpi *cpinfo;
{
    FILE *fp;
    char buf[BUFSIZ];

    fclose(cpinfo->wfp);	/* give child EOF */
    Debug(40, 9, ("waiting for user enum %d\n", cpinfo->childpid));
    (void) waitpid(cpinfo->childpid, NULL, 0);	/* blocking wait */

    /* results are ready */
    Debug(40, 9, ("Reading User Enum results: %s\n", cpinfo->result_file));
    if ((fp = fopen(cpinfo->result_file, "r")) != NULL) {
	/* Collect results and put them into the todo list */
	while (fgets(buf, sizeof(buf), fp) != NULL) {
	    Debug(40, 1, ("user_enum: Carryover URL: %s\n", buf));
	    add_to_todo(buf);
	}
	fclose(fp);
    }
    /* clean up */
    (void) unlink(cpinfo->result_file);
    xfree(cpinfo->result_file);
    xfree(cpinfo);
}

/*
 *  get_data() - Processes the Enumeration results.  Returns the
 *  number of LeafNode URLs generated.  Reads results from in,
 *  sends generated URLs to out.
 */
static int get_data(in, pid, out)
     FILE *in;
     int pid;
     FILE *out;
{
    char buf[BUFSIZ], *name, *stamp, *t, buf2[BUFSIZ];
    int nleafs = 0;

    Debug(40, 9, ("get_data: %p %d %p\n", in, pid, out));
    /* Grab the Root from the first line */
    if (fgets(buf, sizeof(buf), in) == NULL) {
	Log("WARNING: Enumeration did not produce any results: %s.\n",
	    tree_root);
	return (nleafs);
    }
    if ((t = strchr(buf, '\n')) != NULL)
	*t = '\0';
    root = xstrdup(buf);

    while (fgets(buf, sizeof(buf), in) != NULL) {
	if ((t = strchr(buf, '\n')) != NULL)
	    *t = '\0';
	Debug(40, 9, ("get_data: Read '%s'\n", buf));

	/* Parse the URL,Timestamp pair */
	if ((t = strchr(buf, '\t')) == NULL)
	    continue;
	*t = '\0';
	name = buf;
	stamp = ++t;
	nleafs++;

	if (out != NULL) {
	    Debug(40, 9, ("get_data: Writing result: %s\n", name));
	    fprintf(out, "%s\n", name);
	    (void) fflush(out);	/* ignore SIGPIPE */
	}
	if (!strncmp(stamp, "Depth=", strlen("Depth="))) {
	    Debug(40, 1, ("enum: Carryover URL: %s, %s\n",
		    name, stamp));
	    sprintf(buf2, "%s %d %s %d %s %d %s %s %s\n",
		name, url_max, url_filter,
		host_max, host_filter, delay,
		stamp + strlen("Depth="),
		access_types, user_enum_pgm);
	    add_to_todo(buf2);
	} else if (proddb != NULL) {
	    process_url_stamp(name, stamp);
	    add_to_newdb(name, stamp);
	} else {
	    do_add(name, stamp);
	}
    }
    if (nleafs == 0)
	Log("WARNING: Enumeration did not produce any URLs: %s.\n",
	    tree_root);
    Debug(40, 9, ("get_data: returning %d\n", nleafs));
    return (nleafs);
}

/*
 *  do_kid - Runs the command using fd0 as stdin and fd1 as stdout.
 */
static int do_kid(fd0, fd1, cmd)
     int fd0, fd1;
     char *cmd;
{
    int pid;
    char *argv[64], buf[BUFSIZ];

    Debug(40, 5, ("Running Enumeration Command: %s\n", cmd));

    /* need to use fork() since parse_argv() will cause mem leak */
    if ((pid = fork()) < 0) {
	fatal_errno("fork");
    }
    if (pid > 0)
	return pid;		/* parent leaves now */

    /* child */
    /* Clean up all file descriptors except std{in,out,err} */
    if ((dup2(fd0, 0) < 0) ||	/* fd0 -> stdin      */
	(dup2(fd1, 1) < 0)) {	/* fd1 -> stdout     */
	fatal_errno("dup2");
    }
    close_all_fds(3);
    memset(argv, '\0', sizeof(argv));
    parse_argv(argv, cmd);	/* parse the command */
    set_envs();			/* pass per-enum ENV vars */
    execvp(argv[0], argv);	/* Run the enumeration command */
    sprintf(buf, "execvp: %s", argv[0]);
    log_errno(buf);
    _exit(1);
    return (0);
}

/*
 *  do_an_enumeration() - Performs an enumeration for the given command,
 *  and URL.
 */
static int do_an_enumeration(up, cmd)
     URL *up;
     char *cmd;
{
    cpi *cpinfo = NULL;
    FILE *in = NULL;
    int pfd[2], pid, r;

    /* 
     *  If the production database is not available, then we need a
     *  temporary database to store the Timestamps/MD5s.
     */
    if (proddb != NULL) {
	unlink(tmpdbfile);
	newdb = gdbm_open(tmpdbfile, 0, GDBM_NEWDB | GDBM_FAST, 0644, 0);
	if (newdb == NULL) {
	    errorlog("gdbm_open: %s: %s\n", tmpdbfile,
		gdbm_strerror(gdbm_errno));
	    log_errno(tmpdbfile);
	    fatal("Internal enum error 4.\n");
	}
    }
    /*
     *  Create a one-way pipe from the enumeration process to ourselves,
     *  then run the enumeration, and process the results.
     */
    if (pipe(pfd) < 0) {
	fatal_errno("pipe");
    }
    pid = do_kid(0, pfd[1], cmd);	/* Run the enumeration */
    close(pfd[1]);		/* Close write side */
    in = fdopen(pfd[0], "r");	/* Fluff up read side */

    /*      open pipe to user_enum_pgm unless it is /bin/false      */
    if (strcmp(user_enum_pgm, "/bin/false"))
	cpinfo = create_user_enum_pgm_pipes(up->url);

    /*      process the results from <url>enum and pass the         */
    /*      new URLs to the user_enum_pgm if set                    */
    r = get_data(in, pid, cpinfo ? cpinfo->wfp : (FILE *) NULL);

    /*      cleanup user_enum_pgm if set                            */
    if (cpinfo)
	finish_user_enum_pgm_pipes(cpinfo);

    Debug(40, 9, ("waiting for enumeration %d\n", pid));
    (void) waitpid(pid, NULL, 0);	/* Wait for enumeration to finish */
    fclose(in);			/* Close read side */
    (void) close(pfd[0]);	/* just in case */

    /* Clean up */
    if (proddb != NULL) {
	if (find_deleted)
	    enum_to_get_deleted();
	gdbm_close(newdb);	/* don't need this anymore */
	(void) unlink(tmpdbfile);
    }
    return (r);
}

/* ---------------------------------------------------------------------- */
static void usage()
{
    fprintf(stderr, "\
Usage:  enum [-db database] [-del deleted-file] [-log logfile]\n\
             [-delete] [-tmpdb database] < url_enum_list\n");
    exit(1);
}

int main(argc, argv)
     int argc;
     char *argv[];
{
    URL *up = NULL;
    static char buf[BUFSIZ];
    static char cmd[BUFSIZ];
    static char depth_str[BUFSIZ];
    char *t = NULL;
    int i;
    int enumix;
    nodespec *ns = NULL;

    signal(SIGPIPE, SIG_IGN);	/* ignore ALL broken pipes */

#ifdef USE_HOST_CACHE
    host_cache_init();
#endif
    init(argc, argv);		/* parse cmd line and opens */
    init_url();			/* must call to initialize the url cache */

    Log("Starting RootNode enumeration.\n");
    /* Read the entire workload into the todo list */
    while (fgets(buf, sizeof(buf), stdin) != NULL) {
	if ((t = strchr(buf, '\n')))
	    *t = '\0';
	Debug(40, 9, ("reading stdin: %s\n", buf));
	add_to_todo(buf);
    }

/* Use a linked list to hold the rootnodes to enumerate.  New nodespec's  */
/* are appended at the tail of the list (ns_tail) in get_data().  As the  */
/* list is traversed, the structures are free'd and ns_head always points */
/* to the current node in the list.                                       */

    while ((ns = ns_head) != NULL) {
	if (ns->buf == (char *) NULL)
	    continue;
	Debug(40, 1, ("Processing RootNode: %s\n", ns->buf));
	if (sscanf(ns->buf, "%s %d %s %d %s %d %s %s %s\n",
		url, &url_max, url_filter, &host_max,
		host_filter, &delay, depth_str, access_types,
		user_enum_pgm) == 9) {
	    Debug(40, 9, ("Enum Received:\n\tURL: %s\n\tURL-Max: %d\n\tURL-Filter: %s\n\tHost-Max: %d\n\tHost-Filter: %s\n\tDelay: %d\n\tDepth: %s\n\tAccess-Types: %s\n\tUser-Enumerate-Program: %s\n", url, url_max, url_filter, host_max, host_filter, delay, depth_str, access_types, user_enum_pgm));

	    cur_depth = max_depth = 0;
	    if (strchr(depth_str, ':'))
		sscanf(depth_str, "%d:%d", &cur_depth, &max_depth);
	    else
		sscanf(depth_str, "%d", &max_depth);

	    Debug(40, 5, ("Processing: [%d:%d] %s\n", cur_depth, max_depth, url));
	    if ((up = url_open(url)) == NULL) {
		errorlog("Invalid URL: %s\n", url);
		goto while_done;
	    }
	    /* find the enumerator for this type */
	    for (i = 0, enumix = -1; i < sizeof(enums) / sizeof(enums[0]); i++) {
		if (up->type == enums[i].url_type) {
		    enumix = i;
		    break;
		}
	    }
	    if (enumix < 0) {
		errorlog("No Enumerator available for: %s\n", up->url);
		url_close(up);
		goto while_done;
	    }
	    /* Prepare to run the enumerator */
	    cur_attr = enums[enumix].stamp_kind;
	    switch (enums[enumix].arg_types) {
	    case Arg_Path:
		sprintf(cmd, "%s \"%s\"", enums[enumix].url_enum,
		    up->pathname);
		break;
	    case Arg_URL:
		sprintf(cmd, "%s \"%s\"", enums[enumix].url_enum,
		    up->url);
		break;
	    case Arg_Host_File:
		sprintf(cmd, "%s \"%s\" \"%s\"", enums[enumix].url_enum,
		    up->host, up->pathname);
		break;
	    default:
		fatal("Internal enum error 1.\n");
	    }
	    tree_root = xstrdup(up->url);
	    (void) do_an_enumeration(up, cmd);
	    url_close(up);
	    xfree(tree_root);
	}
      while_done:
	xfree(ns->buf);
	ns_head = ns->next;
	xfree(ns);
    }
    Debug(40, 1, ("enum: exited normally.\n"));
    finish_url();
    finish();
    exit(0);
}

static void set_envs()
{
    static char buf[BUFSIZ];

    /* Pass the parameters via the environment */
    sprintf(buf, "HARVEST_URL_MAX=%d", url_max);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_URL_FILTER=%s", url_filter);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_HOST_MAX=%d", host_max);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_HOST_FILTER=%s", host_filter);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_URL_DELAY=%d", delay);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_DEPTH_MAX=%d", max_depth);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_DEPTH_CUR=%d", cur_depth);
    Debug(40, 1, ("putenv %s\n", buf));
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_ACCESS_TYPES=%s", access_types);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
    sprintf(buf, "HARVEST_ENUMERATE_PROGRAM=%s", user_enum_pgm);
    if (putenv(xstrdup(buf))) {
	errorlog("Cannot pass the parameters: %s\n", buf);
    }
}

static void add_to_todo(s)
     char *s;
{
    nodespec *ns = NULL;

    Debug(40, 9, ("add_to_todo: %s\n", s));
    ns = (nodespec *) xmalloc(sizeof(nodespec));
    ns->buf = xstrdup(s);
    ns->next = NULL;
    if (ns_tail)
	ns_tail->next = ns;
    ns_tail = ns;
    if (!ns_head)
	ns_head = ns;
}
