static char rcsid[] =
    "$Id: httpenum-breadth.c,v 2.9 2000/02/03 12:45:56 sxw Exp $";
/*
 *  httpenum-breadth.c - Breadth first RootNode URL enumerator for HTTP URLs
 *
 *  Usage: httpenum-breadth http-URL
 *
 *  Outputs the following format:
 *
 *      URL of tree root
 *      URL <tab> md5
 *      ...
 *      URL <tab> md5
 *
 *  DEBUG: section  42, level 1, 5, 9   Gatherer enumeration for HTTP
 *  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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <string.h>
#include <signal.h>
#include <gdbm.h>
#include "util.h"
#include "url.h"
#define PUBLIC extern
#include "filter.h"

typedef struct _list_t {
	void *ptr;
	int depth;
	struct _list_t *next;
} list_t;

list_t *head = NULL;
list_t **Tail = NULL;

/* List of candidate URL's */
char *filename_candidates;
int fd_candidates_read = -1, fd_candidates_write = -1;

/*
 *  define HOST_COUNT_IP to 'count' visited hosts based on IP, not the
 *  given hostname.  This way aliased machines will be properly
 *  enumerated
 *  If you define HOST_COUNT_IP then support for HTTP/1.1 virtual hosts
 *  and for hosts that have more than one IP address (for round-robin
 *  load balancing) may not work
 */
#undef HOST_COUNT_IP

/* Global variables */
int max_depth = 0;
int cur_depth = 0;
int depth_hist[100];

/* Local variables */
static int url_max = 0;
static int nurls = 0;
static int host_max = 0;
static int nhosts = 0;
static char *tree_root = NULL;
static char *urldb_filename = NULL;
static char *hostdb_filename = NULL;
static char *md5db_filename = NULL;
static GDBM_FILE urldbf = NULL;
static GDBM_FILE hostdbf = NULL;
static GDBM_FILE md5dbf = NULL;

static FILE *not_visited = NULL;

/* Local functions */
static void usage ();
static void mark_failed ();
static void mark_retrieved ();
static void sigdie ();
static int url_in_db ();
static int md5_in_db ();
static int http_enum ();

extern int RobotsTxtCheck _PARAMS ((URL *));

list_t *
free_from_list (l)
list_t *l;
{
	list_t *r = NULL;
	int len;

	if (filename_candidates) {
		if (fd_candidates_read == -1)
			fd_candidates_read =
			    open (filename_candidates, O_RDONLY);
		if (fd_candidates_read > -1) {
			r = (list_t *) xmalloc (sizeof (list_t));
			if (read
			    (fd_candidates_read, &(r->depth), sizeof (r->depth))
			    <= 0) {
				xfree (r);
				return NULL;
			}
			read (fd_candidates_read, &(len), sizeof (len));
			r->ptr = (void *) xmalloc (len + 1);
			read (fd_candidates_read, r->ptr, len);
			((char *) r->ptr)[len] = '\0';
		}
	} else
		r = l->next;

	if (l) {
		xfree (l->ptr);
		xfree (l);
	}
	return r;
}

void
add_to_list (url, depth)
char *url;
int depth;
{
	list_t *l = NULL;
	int len;

	if (filename_candidates) {
		if (fd_candidates_write == -1)
			fd_candidates_write = open (filename_candidates,
						    O_WRONLY | O_CREAT |
						    O_TRUNC, 0600);
		if (fd_candidates_write > -1) {
			len = strlen (url);
			write (fd_candidates_write, &depth, sizeof (depth));
			write (fd_candidates_write, &len, sizeof (len));
			write (fd_candidates_write, url, len);
		}
		if (!head)
			head = free_from_list (0);
		return;
	}

	/* No candidates file - keep list in memory. */
	l = (list_t *) xmalloc (sizeof (list_t));
	l->ptr = (void *) xstrdup (url);
	l->next = (list_t *) NULL;
	l->depth = depth;
	*Tail = l;
	Tail = &(l->next);
	return;
}

/* ---------------------------------------------------------------------- */

/*
 *  mark_failed() - Mark that a URL failed to be retrieved, so that the
 *  enumerator doesn't try it again. This option may not be wanted by
 *  some users and so should be configurable.
 */
static void
mark_failed (URL * up)
{
	datum k, d;

	Debug (42, 9, ("mark_failed: url='%s'", up->url));

	k.dptr = xstrdup (up->url);
	k.dsize = strlen (k.dptr) + 1;
	d.dptr = xstrdup ("FailedAccess");
	d.dsize = strlen (d.dptr) + 1;

	if (!gdbm_exists (urldbf, k) && gdbm_store (urldbf, k, d, GDBM_INSERT))
		fatal ("GDBM URLDB: %s: %s", k.dptr,
		       gdbm_strerror (gdbm_errno));

	xfree (k.dptr);
	xfree (d.dptr);
}

/*
 *  mark_retrieved() - Mark that the given URL was successfully retrieved,
 *  so that the URL is not retrieved again.  This prevents cycles in the
 *  enumeration.
 */
static void
mark_retrieved (up)
URL *up;
{
	datum k, d;

	Debug (42, 9,
	       ("mark_retrieved: url='%s', md5='%s'\n", up->url, up->md5));

	k.dptr = xstrdup (up->url);
	k.dsize = strlen (k.dptr) + 1;
	d.dptr = xstrdup (up->md5);
	d.dsize = strlen (d.dptr) + 1;

	if (!gdbm_exists (urldbf, k) && gdbm_store (urldbf, k, d, GDBM_INSERT))
		fatal ("GDBM URLDB: %s: %s", k.dptr,
		       gdbm_strerror (gdbm_errno));
	if (!gdbm_exists (md5dbf, d) && gdbm_store (md5dbf, d, k, GDBM_INSERT))
		fatal ("GDBM MD5DB: %s: %s", k.dptr,
		       gdbm_strerror (gdbm_errno));

	xfree (k.dptr);
	xfree (d.dptr);

	if (up->redir_from_url != (char *) NULL) {
		Debug (42, 9, ("mark_retrieved: url='%s', md5='%s'\n",
			       up->redir_from_url, up->md5));

		k.dptr = xstrdup (up->redir_from_url);
		k.dsize = strlen (k.dptr) + 1;
		d.dptr = xstrdup (up->md5);
		d.dsize = strlen (d.dptr) + 1;

		if (!gdbm_exists (urldbf, k)
		    && gdbm_store (urldbf, k, d, GDBM_INSERT))
			fatal ("GDBM URLDB: %s: %s", k.dptr,
			       gdbm_strerror (gdbm_errno));

		xfree (k.dptr);
		xfree (d.dptr);
	}

	if (nurls++ >= url_max) {
		Log ("Truncating RootNode %s at %d LeafNode URLs\n",
		     tree_root, url_max);
		url_close (up);
		up = NULL;
		sigdie (0);
	}
}

/*
 *  url_in_db() - check to see if the URL is in the database
 */
static int
url_in_db (url)
char *url;
{
	datum k;
	int r;

	Debug (42, 9, ("url_in_db: checking for url='%s'\n", url));

	k.dptr = xstrdup (url);
	k.dsize = strlen (k.dptr) + 1;
	r = gdbm_exists (urldbf, k);
	xfree (k.dptr);
	return (r);
}

/*
 *  md5_in_db() - check to see if the MD5 is in the database
 */
static int
md5_in_db (md5)
char *md5;
{
	datum k;
	int r;

	k.dptr = xstrdup (md5);
	k.dsize = strlen (k.dptr) + 1;
	r = gdbm_exists (md5dbf, k);
	xfree (k.dptr);
	return (r);
}

/*
 *  host_in_db() - check to see if the host is in the database
 */
static int
host_in_db (host)
char *host;
{
	datum k;
	int r;
#ifdef HOST_COUNT_IP
	Host *h;

	h = get_host (host);
	if (!h)
		return 0;
	k.dptr = xstrdup (h->dotaddr);
#else
	k.dptr = xstrdup (host);
#endif
	k.dsize = strlen (k.dptr) + 1;
	r = gdbm_exists (hostdbf, k);
	xfree (k.dptr);
	return (r);
}

/*
 *  visit_server() - Determine if we should visit the server.  Return
 *  zero if we should not process the URL; otherwise, return non-zero.
 */
static int
visit_server (up)
URL *up;
{
	datum k, d;
#ifdef HOST_COUNT_IP
	Host *h = NULL;
#endif

	if (host_in_db (up->host))	/* Host is already in the db */
		return (1);
	if (++nhosts > host_max)
		return (0);

#ifdef HOST_COUNT_IP
	h = get_host (up->host);
	if (!h)
		return (0);
	k.dptr = xstrdup (h->dotaddr);
#else
	k.dptr = xstrdup (up->host);
#endif
	k.dsize = strlen (k.dptr) + 1;
	d.dptr = xstrdup (up->url);
	d.dsize = strlen (d.dptr) + 1;

	if (gdbm_store (hostdbf, k, d, GDBM_INSERT))
		fatal ("GDBM HOSTDB: %s: %s", k.dptr,
		       gdbm_strerror (gdbm_errno));
	xfree (k.dptr);
	xfree (d.dptr);
	return (1);
}

int
url_is_allowed (url)
char *url;
{
	URL *tup = NULL;
	int y;

	if ((tup = url_open (url)) == NULL)
		return 0;;
	if (url_in_db (tup->url)) {	/* Have we been here? */
		Debug (42, 1, ("Already Visited URL: %s\n", tup->url));
		url_close (tup);
		return 0;
	}
	if ((y = filter_selection (tup))) {
		Debug (42, 1, ("Removing Candidate: [%s] %s\n",
			       Filter_Type_Name[y], tup->url));
		if (not_visited)
			fprintf (not_visited, "[FILTER] %s\n", tup->url);
		url_close (tup);
		return 0;
	}
	if (!visit_server (tup)) {
		Debug (42, 1, ("Server count exceeded: %s\n", tup->url));
		if (not_visited)
			fprintf (not_visited, "[SERVER] %s\n", tup->url);
		url_close (tup);
		return 0;
	}
	if (!RobotsTxtCheck (tup)) {
		Debug (42, 1, ("Disallowed by robots.txt: %s\n", tup->url));
		if (not_visited)
			fprintf (not_visited, "[ROBOTS.TXT] %s\n", tup->url);
		url_close (tup);
		return 0;
	}
	url_close (tup);
	return 1;
}

/*
 *  http_enum() - Builds a linked list of all the URLs in this object,
 *  returns -1 on error.  Checks for "text/html" and "text/x-soif" in MIME
 *  headers and then runs "HTMLurls" on the file or urldb_getrefs() on the URL.
 */
static int
http_enum (up, depth)
URL *up;
int depth;
{
	FILE *fp = NULL;
#if 0 /* kjl/7mar2002 */
	char *enum_url = NULL;
#endif
	char *s = NULL;
	char *t0 = NULL;
	char *t1 = NULL;
	char *t2 = NULL;
	int err;
	int nurls = 0;
	int pid;
	int pipefds[2];
	int status;
	int count = 0;
	char *argv[5];
	static char buf[BUFSIZ];

	if (url_in_db (up->url)) {	/* Have we been here? */
		Debug (42, 1, ("Already Visited URL: %s\n", up->url));
		return 0;
	}
	/*
	 *  Ack.  Check for symbolic link loops in server generated HTML listings
	 *  Do this by comparing the last two pathname components.  If they are
	 *  the same then guess its a loop.
	 */
	s = xstrdup (up->pathname);
	t0 = t1 = t2 = NULL;
	for (t0 = strtok (s, "/"); t0; t0 = strtok (NULL, "/")) {
		t2 = t1;
		t1 = t0;
	}
	if (t1 != NULL && t2 != NULL) {
		if (strcmp (t1, t2) == 0) {
			Debug (42, 1, ("Possible symlink loop: %s\n", up->url));
			xfree (s);
			s = NULL;
			return 0;
		}
	}
	xfree (s);
	s = NULL;

	while (((status = url_retrieve (up)) == -1)
	       && count < HTTP_MAX_REDIRECTS) {
		count++;
		if (!url_is_allowed (up->url))
			return 0;
	}

	if (status) {		/* Grab the URL; success? */
		Debug (42, 1, ("Cannot Retrieve URL: %s\n", up->url));

#ifdef DONT_RETRY_FAILS
		mark_failed (up);
#endif

		return 0;
	}
	if (up->md5 && md5_in_db (up->md5)) {	/* Have we been here? */
		Debug (42, 1, ("Already Visited MD5: %s\n", up->url));
		return 0;
	}
	/* Remember that we've been here before */
	if (up->md5)
		mark_retrieved (up);

	argv[0] = argv[1] = argv[2] = argv[3] = argv[4] = NULL;
	/* Are we dealing with HTML or SOIF, if not we can't get href links */
	if (up->http_mime_hdr != NULL) {
		if (strstr (up->http_mime_hdr, "text/html") != NULL) {
			argv[0] = "HTMLurls";
			argv[1] = "--base-url";
			argv[2] = up->url;
			argv[3] = up->filename;
		} else if (strstr (up->http_mime_hdr, "text/x-soif") != NULL) {
			char *refs;
			char *ptr;
			char *abs_ref;

			Log ("%s\tNot Modified\n", up->url);
			refs = urldb_getrefs (up->url);
			if (!refs)
				return -1;

			for (ptr = strtok (refs, "\n"); ptr; ptr = strtok (NULL, "\n")) {
				abs_ref = url_parse_relative (ptr, up->url);
				if (url_is_allowed (abs_ref)) {
					add_to_list (abs_ref, depth);
				}
				xfree (abs_ref);
			}
			xfree (refs);
			return 1;
		} else {
			fprintf (stdout, "%s\t%s\n", up->url, up->md5);	/* URL <tab> MD5 */
			fflush (stdout);
			return -1;
		}
	}

	/* Extract the HREF's */
	if (pipe (pipefds) < 0) {
		log_errno ("pipe");
		return -1;
	}
	if ((pid = fork ()) < 0) {
		log_errno ("fork");
		close (pipefds[0]);
		close (pipefds[1]);
		return -1;
	}
	if (pid == 0) {		/* child: HTMLurls */
#if 0 /* kjl/7mar2002 */
		enum_url = (char *) xmalloc (strlen (up->url) + 20);
		sprintf (enum_url, "ENUMERATOR_URL=%s", up->url);
		putenv (enum_url);
#endif

		close (pipefds[0]);	/* child wont read from pipe */
		dup2 (pipefds[1], 1);	/* stdout -> write:pipe */
		close (pipefds[1]);	/* close pipe, its now stdout */

		execvp (argv[0], argv);
		sprintf (buf, "execvp: %s", argv[0]);
		log_errno (buf);
		_exit (1);
	}
	close (pipefds[1]);	/* parent wont write */
	if ((fp = fdopen (pipefds[0], "r")) == NULL) {
		log_errno ("fdopen");
		return -1;
	}

	/*
	 *  For each HREF pointer, convert it to a URL, and add it to
	 *  the global list of URLs to process.
	 */
	while (fgets (buf, BUFSIZ, fp) != NULL) {
		if ((s = strrchr (buf, '\n')) != NULL)
			*s = '\0';	/* strip newline */
		Debug (42, 1, ("Input: %s\n", buf));
		if (url_is_allowed (buf)) {
			add_to_list (buf, depth);
			nurls++;
		}
	}
	fclose (fp);
	close (pipefds[0]);

	/*
	 *  Wait for HTMLurls to finish and decide on whether to index the page
	 *  according to its exit code
	 */
	if ((err = waitpid (pid, &status, 0)) != pid) {
		Debug (42, 1, ("WARNING: waiting for child %d got %d...\n",
			       pid, err));
	}

	/* If we had an exit code of 0 then index it, otherwise don't */
	if (WEXITSTATUS (status) == 0) {
		fprintf (stdout, "%s\t%s\n", up->url, up->md5);
		fflush (stdout);
	}

	Debug (42, 1, ("Adding %d URLs from %s to workload\n", nurls, up->url));
	return 1;
}

/* ---------------------------------------------------------------------- */

/*
 *  initialize() - Basic init routines
 */
static void
initialize ()
{
	char *s = NULL;
	extern int liburl_conform_rfc1738;
	FILE *logfp = NULL;

#ifdef USE_HOST_CACHE
	host_cache_init ();
#endif

	cur_depth = max_depth = url_max = host_max = 0;
	if ((s = getenv ("HARVEST_URL_MAX")) != NULL)
		url_max = atoi (s);
	if ((s = getenv ("HARVEST_HOST_MAX")) != NULL)
		host_max = atoi (s);
	if ((s = getenv ("HARVEST_DEPTH_MAX")) != NULL)
		max_depth = atoi (s);
	if ((s = getenv ("HARVEST_DEPTH_CUR")) != NULL)
		cur_depth = atoi (s);
	if (url_max < 1)
		url_max = 250;	/* hard-coded maximum */
	if (host_max < 1)
		host_max = 1;	/* hard-coded maximum */
	if (max_depth < 1)
		max_depth = 0;	/* hard-coded maximum */
	host_filterfile = getenv ("HARVEST_HOST_FILTER");
	url_filterfile = getenv ("HARVEST_URL_FILTER");
	access_types = getenv ("HARVEST_ACCESS_TYPES");
	filename_candidates = getenv ("HARVEST_CANDIDATES");

	if ((s = getenv ("HARVEST_GATHERER_LOGFILE")) != (char *) NULL)
		logfp = fopen (s, "a+");
	if (logfp == (FILE *) NULL)
		logfp = stderr;
	init_log3 ("httpenum-breadth", logfp, stderr);
	init_url ();
	liburl_conform_rfc1738 = 1;
	filter_initialize ();
	Debug (42, 5, ("access_mask: %#02X\n", access_mask));

	/* Open GDBM databases to keep track of where we've been */
	urldb_filename = xstrdup (tempnam (NULL, "Hurl"));
	urldbf = gdbm_open (urldb_filename, 0, GDBM_NEWDB, 0644, NULL);
	if (urldbf == NULL) {
		log_errno (urldb_filename);
		fatal ("gdbm_open: %s: %s", urldb_filename,
		       gdbm_strerror (gdbm_errno));
	}
	hostdb_filename = xstrdup (tempnam (NULL, "Hhost"));
	hostdbf = gdbm_open (hostdb_filename, 0, GDBM_NEWDB, 0644, NULL);
	if (hostdbf == NULL) {
		log_errno (hostdb_filename);
		fatal ("gdbm_open: %s: %s", hostdb_filename,
		       gdbm_strerror (gdbm_errno));
	}
	md5db_filename = xstrdup (tempnam (NULL, "Hmd5"));
	md5dbf = gdbm_open (md5db_filename, 0, GDBM_NEWDB, 0644, NULL);
	if (md5dbf == NULL) {
		log_errno (md5db_filename);
		fatal ("gdbm_open: %s: %s", md5db_filename,
		       gdbm_strerror (gdbm_errno));
	}
	/* open not-visited file */
	if ((s = getenv ("HARVEST_NOT_VISITED_LOG")) != NULL)
		not_visited = fopen (s, "a+");
	if (not_visited)
		setbuf (not_visited, NULL);
}

/* Die gracefully */
static void
sigdie (x)
int x;
{
	int i, sum;
#ifdef USE_HOST_CACHE
	dump_host_cache (42, 9);
#endif
	if (urldbf != NULL)
		gdbm_close (urldbf);
	if (hostdbf != NULL)
		gdbm_close (hostdbf);
	if (md5dbf != NULL)
		gdbm_close (md5dbf);
	if (not_visited)
		fclose (not_visited);

	/* (void) unlink (urldb_filename); */
	crremove (urldb_filename);
	xfree (urldb_filename);
	/* (void) unlink (hostdb_filename); */
	crremove (hostdb_filename);
	xfree (hostdb_filename);
	/* (void) unlink (md5db_filename); */
	crremove (md5db_filename);
	xfree (md5db_filename);

	Log ("Statistics for URL '%s':\n", tree_root);
	while (depth_hist[max_depth] == 0)
		max_depth--;
	for (i = 0, sum = 0; i < 100; i++) {
		if (i > max_depth && depth_hist[i] == 0)
			break;
		Log ("    Found %8d objects at depth %d\n", depth_hist[i], i);
		sum += depth_hist[i];
	}
	Log ("Total for URL '%s': %8d objects\n", tree_root, sum);

	Debug (42, 1, ("httpenum-breadth: exiting (signal %d)\n", x));
	exit (0);
}

/* ---------------------------------------------------------------------- */

static void
usage ()
{
	fprintf (stderr, "Usage: httpenum-breadth http-URL\n");
	exit (1);
}

int
main (argc, argv)
int argc;
char **argv;
{
	URL *up = NULL;
	list_t *l = NULL;
	char *url = NULL;
	int depth = 0;

	debug_init ();		/* from $HARVEST_DEBUG */
	for (argc--, argv++; argc > 0 && **argv == '-'; argc--, argv++) {
		if (!strncmp (*argv, "-D", 2)) {
			debug_flag (*argv);
		}
	}

	if (argc != 1)
		usage ();

	for (depth = 0; depth < 100; depth++)
		depth_hist[depth] = 0;

	signal (SIGTERM, sigdie);	/* Die gracefully */
	signal (SIGINT, sigdie);
	signal (SIGPIPE, sigdie);	/* Quickly clean up on broken pipe */

	initialize ();		/* Initialize */

	Debug (42, 1, ("httpenum-breadth: Starting...\n"));

	/* Grab the RootNode URL from the command line */
	if ((up = url_open (*argv)) == NULL || up->type != URL_HTTP) {
		usage ();
	}
	/* Mark the RootNode */
	tree_root = xstrdup (up->url);
	Tail = &head;

	Log ("Processing URL: '%s'\n", tree_root);

	printf ("%s\n", up->url);	/* Print tree root */
	/*
	 *  helpdesk@ecs.soton.ac.uk -- Gatherer visits too many hosts
	 *  6/3/96. Make sure the first URL we start with is added
	 *  to the list of servers visited.
	 */
	(void) visit_server (up);

	add_to_list (up->url, cur_depth);	/* start at depth = 0 */
	url_close (up);

	for (l = head; l; l = free_from_list (l)) {
		url = (char *) l->ptr;
		depth = l->depth;

		if (max_depth > 0 && depth > max_depth) {
			if (not_visited)
				fprintf (not_visited, "[DEPTH] %s\n", url);
			Debug (42, 1, ("Maximum Depth of %d Reached: %s\n",
				       max_depth, url));
			continue;
		}
		Debug (42, 1, ("Processing: [%2d] %s\n", depth, url));

		if ((up = url_open (url)) == NULL)
			continue;

		if ((up->type != URL_HTTP)) {
			Debug (42, 1,
			       ("Examining: [%d:%d] %s\n", depth, max_depth,
				up->url));
			/* filter_selection() checks access_mask */
			if (!filter_selection (up) && (depth <= max_depth)) {
				/*
				 *  Print URL with bogus MD5 to enumerate;
				 *  flush to keep pipe moving
				 *  URL <tab> MD5
				 */
				fprintf (stdout, "%s\tDepth=%d:%d\n", up->url,
					 depth, max_depth);
				fflush (stdout);
				Debug (42, 1,
				       ("HTTPENUM Re-enumeration: %s\tDepth=%d:%d\n",
					up->url, depth, max_depth));
			}
			url_close (up);
			continue;
		}
		/* search for more links from this one */
		if (http_enum (up, depth + 1) != 0 && depth < 100)
			/* Increase retrieved count only for new URLs - HrS */
			depth_hist[depth]++;
		url_close (up);
	}

	finish_url ();
	sigdie (0);
	/* NOTREACHED */
}
