static char rcsid[] = "$Id: enum.c,v 2.2 1997/08/27 18:06:28 sxw 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
 *  AUTHOR: Harvest derived
 *
 *  Harvest Indexer http://harvest.sourceforge.net/
 *  -----------------------------------------------
 *
 *  The Harvest Indexer is a continued development of code developed by
 *  the Harvest Project. Development is carried out by numerous individuals
 *  in the Internet community, and is not officially connected with the
 *  original Harvest Project or its funding sources.
 *
 *  Please mail lee@arco.de if you are interested in participating
 *  in the development effort.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 *  ----------------------------------------------------------------------
 *  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 <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_breadth;	/* the pgm for breadth first searches */
	char *url_enum_depth;	/* the pgm for depth first searches */
	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", "fileenum", "", T_TIMESTAMP, Arg_URL}, {
	URL_FTP, "ftpenum", "ftpenum", "", T_TIMESTAMP, Arg_URL}, {
	URL_GOPHER, "gopherenum-breadth", "gopherenum-depth", "", T_MD5,
		    Arg_URL}, {
	URL_HTTP, "httpenum-breadth", "httpenum-depth", "", T_MD5, Arg_URL},
	{
	URL_NEWS, "newsenum", "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 */
static char search_type[BUFSIZ];	/* Option 9 */

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);

	/*
	 *  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); */
		crremove (tmpdbfile);
		newdb = gdbm_open (tmpdbfile, 0, GDBM_NEWDB, 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 false (CMD_FALSE) */
	if (strcmp (user_enum_pgm, CMD_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); */
		crremove (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;
	char *enumerator;

	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 %s\n",
			    url, &url_max, url_filter, &host_max,
			    host_filter, &delay, depth_str, access_types,
			    user_enum_pgm, search_type) == 10) {
			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\tSearch-Type: %s\n",
				url, url_max, url_filter, host_max, host_filter,
				delay, depth_str, access_types, user_enum_pgm,
				search_type));

			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;
			enumerator = NULL;

			if (strcasecmp (search_type, "Depth") == 0)
				enumerator = enums[enumix].url_enum_depth;
			if (strcasecmp (search_type, "Breadth") == 0)
				enumerator = enums[enumix].url_enum_breadth;
			if (enumerator == NULL) {
				errorlog ("Bad Search-Type supplied\n");
				url_close (up);
				goto while_done;
			}
			switch (enums[enumix].arg_types) {
			case Arg_Path:
				sprintf (cmd, "%s \"%s\"", enumerator,
					 up->pathname);
				break;
			case Arg_URL:
				sprintf (cmd, "%s \"%s\"", enumerator, up->url);
				break;
			case Arg_Host_File:
				sprintf (cmd, "%s \"%s\" \"%s\"", enumerator,
					 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);
		} else if (ns->buf[0] == '#') {
			/*
			 * Pass comments
			 */
			printf ("%s\n", ns->buf);
		}
	      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);
	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;
}
