/*(( "Header" */
/*
 * $Id: httpproxy.c,v 0.17 1996/06/06 23:01:23 mshopf Exp mshopf $
 *
 * (c) 1995-96 Matthias Hopf
 *
 * A small little Http Proxy.
 * It can serv as a ProxyProxy, too (i.e. it can perform only caching and will
 * get its data from another proxy).
 * That way it can be used for other protocol types than html, too.
 *
 * Run it standalone at high priority. It won't need much computing time as
 * it does no busy wait at all.
 * If you really want to re-get an already cached page, just reload it immedeately.
 * (no other request inbetween and no more than ReloadTime seconds delay).
 * If you browse offline, you'll get a note that the cache is invalid. Reload the
 * page if you want to queue the page and get the old cache.
 */

/*
 * $Log: httpproxy.c,v $
 * Revision 0.17  1996/06/06  23:01:23  mshopf
 * cosmetic changes (httpproxy support page).
 * added AlwaysReload.
 *
 * Revision 0.16  1996/06/03  04:08:19  mshopf
 * changed strerror handling.
 * added timeouts.
 * url parsing bug fixes.
 * connection close bug fix.
 * shutdown url consistency bug fix.
 *
 * Revision 0.15  1996/04/26  05:14:03  mshopf
 * added ExitAll, service scans.
 * V0.13 alpha 5 fix.
 *
 * Revision 0.14  1996/04/24  17:37:14  mshopf
 * small bug fixes for as225.
 *
 * Revision 0.13  1996/04/24  03:20:13  mshopf
 * encapsulated network module.
 *
 * Revision 0.12  1996/02/12  19:23:36  mshopf
 * assert() to logfile and debugfile and stderr.
 * MaxRequests now variable.
 * ReadCacheList()/SaveCacheList().
 * lots of bug fixes and cosmetic changes.
 *
 * Revision 0.11  1996/01/15  22:27:58  mshopf
 * fixed AmiTCP4.0 broken time() / stat() times (fix by Fionn Behrens).
 * another couple of bug fixes.
 * added dynamic cache table allocation.
 * error replies still getting better.
 * removed host realname lookup.
 *
 * Revision 0.10  1996/01/09  17:20:35  mshopf
 * Three major and some other minor bug fixes.
 * Added UrlBuffer for POST method and non-proxyproxy support.
 * More and more comformant error messages.
 * basic POST support (only proxying, no caching, no queuing).
 * Sending data even when request is not completed yet.
 * Recreating URL even from nonconformant requests.
 * Long requests supported now.
 * Some cosmetic changes.
 *
 * Revision 0.9  1995/12/06  19:53:55  mshopf
 * added fixes for unix machines and AmiTCP4.0 compilation.
 * some bug fixes.
 * more html conform now.
 * lots of printf type fixes.
 *
 * Revision 0.8  1995/12/03  14:20:23  mshopf
 * Made auto requests and proxy messages more http conform.
 *
 * Revision 0.7  1995/11/19  17:57:50  mshopf
 * added revbump compatible version string.
 * fixed ftp offline proxyproxy std port bug.
 *
 * Revision 0.6  1995/11/04  11:26:13  mshopf
 * better shutdown (requeueing of current transmissions). small bug fixes.
 *
 * Revision 0.5  1995/11/02  18:26:13  mshopf
 * queueing system implemented.
 *
 * Revision 0.4  1995/10/21  21:28:37  mshopf
 * small bug fix.
 *
 * Revision 0.3  1995/10/17  19:23:09  mshopf
 * cleaned up messy logging system.
 * new option 'log'.
 *
 * Revision 0.2  1995/10/13  18:09:17  mshopf
 * everything works so far, exceptions are noted in the header.
 * :-)
 *
 * Revision 0.1  1995/10/13  10:44:27  mshopf
 * no caching at all right now, but it works fine as a proxyproxy.
 *
 */


/*)) */
/*(( "Includes" */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>

#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "httpproxy.h"
#include "net.h"
#include "logging.h"
#include "httpproxy_rev.h"

#ifdef _AMIGA
#  define strcasecmp stricmp
#  define CURRENTDIR ""

#  ifdef FIXTIME
/* Fix broken AmiTCP4.0 (or SasC+AmiTCP4.0) time() / stat() */
#    include <proto/dos.h>

time_t time (time_t *TimePtr)
{
    struct DateStamp ds;
    time_t t;
    DateStamp (&ds);

	/* Sekunden + Minuten*60 +
	 * (Tage + offset zu Unix time()[2922 Tage]) * 60 * 60 * 24  */
    t = ds.ds_Tick / 50+ds.ds_Minute*60+(ds.ds_Days+2922)*86400;
    if (TimePtr)
	*TimePtr = t;
    return (t);
}
#  endif

#else
#  define CURRENTDIR "."  /* rudimentary unix support (would need work...) */
#endif

/*)) */
/*(( "Global Variables" */

extern long __oslibversion = 37;    /* Minimum: Kick 2.0 */
request_t    *Requests;
int       OffLine = 0;                              /* 0: get cache files again, when they are too old */
						    /* 1: keep cache files and queue them */
int       GetQueued = 0;                            /* 1: get queued data from remote hosts or proxyproxy */
int       AlwaysReload = 0;                         /* 1: reload *all* requests */
struct DateStamp RequestStamp;                      /* Current time while request is processed */

/* Global variables for httpproxy.c */

static    netmethods_t *Net       = NULL;

static    char      *VVersion     = VERSTAG;
static    char      *Version      = VERSTRING;
static    char      *PrgName;
static    int       ServerPort    = DEFAULT_PROXYPORT;
static    int       ProxyProxy    = FALSE;                    /* TRUE, when all requests should be
							       * forwarded to another proxy. */
static    const char *ProxyHost;
static    int       ProxyPort;
struct    DateStamp NextCheckStamp;                           /* wait stamp for next Timeout check */

static    cache_t   *LastCache    = NULL;
static    time_t    ThisRequestTime = 0;
static    time_t    StartUpTime;
static    int       MaxRequests   = DEFAULT_MAX_REQUESTS;
static    int       RequestsFree  = -1;
static    int       CachesFree    = MAX_CACHES;
static    long      CacheNr       = 0;
static    u_long    DelCacheTime  = DEFAULT_DELTIME;
static    u_long    ExpireCacheTime = DEFAULT_EXPIRETIME;
static    u_long    ReloadCacheTime = DEFAULT_RELOADTIME;
static    int       CacheUnreadRequests = FALSE;              /* 1: keep cache data on data connection close with data in url send buffer */
static    cachearray_t *Caches;
static    int       TimeoutTime   = DEFAULT_TIMEOUT;          /* seconds to timeout */


void DeleteConnect (request_t *Req, int ok);
void SaveCacheUrl (cache_t *c);

/*)) */

/*(( "Init()/ExitAll()" */

/* Init all global variables, open server port */

void Init (char *ProxyProxyHost, int ProxyProxyPort, char *LogName, char **Argv)
{
#ifdef DEBUG
    char Buffer [1024];          /* Buffer for startup parameters */
    char *Buf;
#endif

    ProxyHost = ProxyProxyHost;
    ProxyPort = ProxyProxyPort;

	/* Standard setups */
    LogOpenFile (LogName);
#ifdef DEBUG
    assert (*Argv);              /* print out startup arguments */
    Buf = Buffer;
    while (*Argv)
    {
	sprintf (Buf, " \"%s\"", *Argv++);
	Buf += strlen (Buf);
    }
    LogSpecial  ("Startup: %s\n", Buffer);
#endif

    if (! (Requests = calloc (MaxRequests, sizeof (request_t))) )
    {
	fprintf (stderr, "not enough memory (need %d Kbyte)\n", NEED_MEM);
	ExitAll (20);
    }
    RequestsFree = MaxRequests;

    if (! (Caches = calloc (1, sizeof (cachearray_t))) )
    {
	fprintf (stderr, "not enough memory (need %d Kbyte)\n", NEED_MEM);
	ExitAll (20);
    }
    assert (Caches->Next == NULL);

    ThisRequestTime = StartUpTime = time (NULL);

    DateStamp (&NextCheckStamp);

	/* Create Serversocket */
    if (! Net->server (ServerPort))
    {
	fprintf (stderr, "%s\n", Net->strerror (errno));
	ExitAll (10);
    }

    if (ProxyProxyHost)
	ProxyProxy = TRUE;

    if (OffLine)
	LogSpecial ("%s: starting type %s offline\n", Version, Net->Descr);
    else if (ProxyProxyHost)
	LogSpecial ("%s: starting type %s online with proxyproxy host '%s', port %d\n",
		    Version, Net->Descr, ProxyProxyHost, ProxyProxyPort);
    else
	LogSpecial ("%s: starting type %s online without proxyproxy\n", Version, Net->Descr);
}


/* Close network if necessary, exit with return code */

void ExitAll (int Ret)
{
    if (Net)
	Net->exit ();
    Net = NULL;
    LogCloseFile ();
    exit (Ret);
    /* NOTREACHED */
}

/*)) */
/*(( "InitCacheSlot()/GetFreeCacheSlot()/ErrToReq()" */

/* Initialize cache slot */

void InitCacheSlot (cache_t *c, cache_t *Template, char Init, char Separator)
{
    if (! Template)
    {
	c->Flags  = 0;
	c->Url[0] = '\0';
	sprintf (c->File, "%c%08lx%c%08lx", Init, StartUpTime, Separator, CacheNr++);
	debug (("new cache allocated: '%s'\n", c->File));
    }
    else
    {
	c->Flags  = Template->Flags & ~CACHE_VALID;
	strcpy  (c->Url, Template->Url);
	sprintf (c->File, "%c%8.8s%c%8.8s", Init, & Template->File [1], Separator, & Template->File [10]);
	debug (("cache %s from template %s\n", c->File, Template->File));
    }
}


/* Scan for a free cache slot */

cache_t *GetFreeCacheSlot (void)
{
    cache_t *c;
    cachearray_t *a = Caches;
    int i;

    debug (("new cache requested.\n"));
    if (! CachesFree)
    {
	while (a->Next)
	    a = a->Next;

	if (! (a->Next = calloc (1, sizeof (cachearray_t))) )
	{
	    LogErr  (NULL, L_ERRMSG, NULL, 0, "no more memory for cache arrays");
	    return (NULL);
	}
	assert (a->Next->Next == NULL);
	debug (("allocated %d new cache slots at 0x%x\n", MAX_CACHES, a->Next));
	a = a->Next;
	CachesFree += MAX_CACHES - 1;
	c = & a->Caches [0];
	InitCacheSlot (c, NULL, '_', '.');
	return (& a->Caches [0]);
    }

    while (a)
    {
	for (c = a->Caches, i=0; i < MAX_CACHES; c++, i++)
	    if (c->Url[0] == '\0' && c->File[0] == '\0')
	    {
		CachesFree--;
		InitCacheSlot (c, NULL, '_', '.');
		return (c);
	    }
	a = a->Next;
    }
    assert (0);      /*NOTREACHED*/
}


/* Type an error to a data buffer of a pending request and set everything up.
 * The error is printed into the Logstream, too. When Errno < 0, a information
 * message is sent (Short is not sent but used for logging when != NULL). */

void   ErrToReq (request_t *Req, int Number, int ErrNo, const char *Short, const char *Descr, const char *Url)
{
    static const char *Author  = "<A HREF=\"http://wwwcip.informatik.uni-erlangen.de/user/mshopf/\">Matthias Hopf</A>";
    static const char *Support = "<A HREF=\"http://wwwcip.informatik.uni-erlangen.de/user/mshopf/httpproxy.html\">";
    Req->DataSent = 0;

    if (Req->Flags & REQ_HTTP1X0)
    {
	if (ErrNo >= 0)
	{
	    LogErr  (Req, L_ERRMSG, Url, ErrNo, "%s", Short);
	    sprintf (Req->DataBuffer, "HTTP/1.0 %03d %s\r\n"
		     "Server: %s\r\n"
		     "Content-Type: text/html\r\n\r\n"
		     "<HTML><HEAD><TITLE>Proxy Error</TITLE></HEAD>\n"
		     "<BODY><H1>Proxy Error: %s</H1><P>\n"
		     "%s%s%s"
		     "%s<P>\n"
		     "<HR><ADDRESS>%s%s</A> by %s</ADDRESS>\n"
		     "</BODY></HTML>\n",
		     Number, Short, VERSHTTP, Short,
		     (ErrNo > 0 ? "<H3>Cause: " : ""),
		     (ErrNo > 0 ? Net->strerror (ErrNo) : ""),
		     (ErrNo > 0 ? "</H3>\n" : ""),
		     Descr, Support, Version, Author);
	}
	else
	{
	    if (Short)
		LogErr  (Req, L_MSG, Url, 0, "%s", Short);
	    sprintf (Req->DataBuffer, "HTTP/1.0 %03d Proxy Message\r\n"
		     "Server: %s\r\n"
		     "Content-Type: text/html\r\n\r\n"
		     "<HTML><HEAD><TITLE>Proxy Message</TITLE></HEAD>\n"
		     "<BODY><H3>Proxy Message:</H3><P>\n"
		     "%s<P>\n"
		     "<HR><ADDRESS>%s%s</A> by %s</ADDRESS>\n"
		     "</BODY></HTML>\n",
		     Number, VERSHTTP, Descr, Support, Version, Author);
	}
    }
    else
    {
	if (ErrNo >= 0)
	{
	    LogErr  (Req, L_ERRMSG, Url, ErrNo, "%s", Short);
	    sprintf (Req->DataBuffer, "<HTML><HEAD><TITLE>Proxy Error</TITLE></HEAD>\n"
		     "<BODY><H1>Proxy Error: %s</H1><P>\n"
		     "%s%s%s"
		     "%s<P>\n"
		     "<HR><ADDRESS>%s%s</A> by %s</ADDRESS>\n"
		     "</BODY></HTML>\n",
		     Short,
		     (ErrNo > 0 ? "<H3>Cause: " : ""),
		     (ErrNo > 0 ? Net->strerror (ErrNo) : ""),
		     (ErrNo > 0 ? "</H3>\n" : ""),
		     Descr, Support, Version, Author);
	}
	else
	{
	    if (Short)
		LogErr  (Req, L_MSG, Url, 0, "%s", Short);
	    sprintf (Req->DataBuffer, "<HTML><HEAD><TITLE>Proxy Message</TITLE></HEAD>\n"
		     "<BODY><H3>Proxy Message:</H3><P>\n"
		     "%s<P>\n"
		     "<HR><ADDRESS>%s%s</A> by %s</ADDRESS>\n"
		     "</BODY></HTML>\n",
		     Descr, Support, Version, Author);
	}
    }

    Req->DataRecv = strlen (Req->DataBuffer);
    Req->Flags |= REQ_DONE;
    if (Req->Flags & REQ_CONNSOCKET)
	DeleteConnect (Req, FALSE);
}


/*)) */
/*(( "RemCacheEntry()/CheckCacheTime()" */

/* Remove a cache entry and its according files */

void RemCacheEntry (cache_t *c)
{
    cache_t tmp;

    debug (("deleting cache file '%s', additional url: %s\n", c->File, c->Url[0] ? "yes" : "no"));
    if (c->File[0] && c->File[0] != '.')                     /* It's no special message file */
    {
	if (remove (c->File))
	    LogErr  (NULL, L_WARN, NULL, errno, "cannot delete invalid cache file '%s'", c->File);
	if (c->Url[0])
	{
	    c->File[0] = '@';
	    if (remove (c->File))
		LogErr  (NULL, L_WARN, NULL, errno, "cannot delete invalid cache file '%s'", c->File);
	}
	if (c->Flags & CACHE_DELETETMP)
	{
	    InitCacheSlot (&tmp, c, '@', '@');
	    debug (("removing temporary url file '%s'\n", tmp.File));
	    if (remove (tmp.File))
		LogErr  (NULL, L_ERROR, NULL, errno, "error while removing temporary url file '%s'", tmp.File);
	}
    }

    c->File[0] = c->Url[0] = '\0';
    c->Flags = 0;
    CachesFree++;
}


/* Check the modification time and date of a cache entry; set Time to 0 to force expire. */
/* Returns -1, when the cache entry is expired or to be reloaded or not existing, the entry is removed.
 * Returns -2, when the cache entry is expired and queued. */

int CheckCacheTime (cache_t *c, u_long Time)
{
    time_t FileT = -1;
    struct stat s;

    if (stat (c->File, &s) >= 0)
    {
	FileT = s.st_mtime;
	debug (("local 0x%lx, access 0x%lx, diff1 %ld <?> %s\n", ThisRequestTime, FileT,
		difftime (ThisRequestTime, FileT), difftime (ThisRequestTime, FileT) > Time ? "expired" : "valid"));
	if ((! Time) || difftime (ThisRequestTime, FileT) > Time)
	{
	    if (OffLine)
	    {
		if ((c->Flags & CACHE_VALID) && ! (c->Flags & CACHE_QUEUED))
		{                                   /* When CACHE_VALID is not set, the routine was called from buildcache() or similar */
		    cache_t tmp;
		    c->Flags |= CACHE_QUEUED;
		    InitCacheSlot (&tmp, c, '@', '@'); /* Queue Url for cache entry and return -2 */
		    SaveCacheUrl (&tmp);
		}
		return (-2);
	    }
	    else if (c->Flags & CACHE_QUEUED)       /* Is the entry queued and not yet sent? */
		return (OffLine ? -2 : -1);
	    else
		FileT = -1;
	}
    }

    if (FileT == -1)
    {
	if (c->Flags & CACHE_QUEUED)
	{
	    debug (("cache entry '%s' not yet there (queued) - This should not happen...\n", c->File));
	    fprintf (stderr, "%s: Warning! Data consistency failure, line %d\n", PrgName, __LINE__);
	    return (-2);
	}
	else
	{
	    debug (("cache entry '%s' expired / to be reloaded, removing\n", c->File));
	    RemCacheEntry (c);
	    return (-1);
	}
    }
    return (c->Flags & CACHE_QUEUED ? -2 : 0);
}

/*)) */
/*(( "ReadCacheUrl()/SaveCacheUrl()" */

/* Read the Url from a specific url file */

void ReadCacheUrl (cache_t *c, char *Name)
{
    FILE *f;

    c->Url[0] = ' ';              /* RemCacheEntry shall remove the url file, too, in case it is called */
    switch (CheckCacheTime (c, DelCacheTime)) {
    case 0:
    case -3:
	break;

    case -1:
	return;
    default:
	RemCacheEntry (c);
	return;
    }

    if (! (f = fopen (Name, "r")) )
    {
	LogErr  (NULL, L_WARN, NULL, errno, "cannot open url file '%s', removing cache entry", Name);
	RemCacheEntry (c);
	return;
    }
    if (fgets (c->Url, MAX_URLSAVE, f))
    {
	switch (c->Url [strlen (c->Url) - 1]) {
	case '\n':
	case '\r':
	    break;

	default:                      /* Cache entry is valid -> return */
	    fclose (f);
	    c->Flags = CACHE_VALID;
	    debug (("valid cache entry: url '%s', data '%s'\n", c->Url, c->File));
	    return;
	}
    }
    fclose (f);
    LogErr  (NULL, L_WARN, NULL, errno, "corrupt cache entry for url file '%s', removing", Name);
    RemCacheEntry (c);
}


/* Save the cache's Url to its url file */

void SaveCacheUrl (cache_t *c)
{
    FILE *UrlStream;
    cache_t tmp;

    c->File[0] = '@';
    debug (("writing url file '%s'\n", c->File));
    if ( (UrlStream = fopen (c->File, "w")) )
    {
	fprintf (UrlStream, "%s", c->Url);  /* no error checking - well... */
	fclose (UrlStream);
	c->File[0] = '_';
	if (! (c->Flags & CACHE_QUEUED))
	    c->Flags |= CACHE_VALID;
	    /* fprintf (LogStream, "URL '%s' is now cached\n",
	     * c->Url);*/
	if (c->Flags & CACHE_DELETETMP)
	{
	    InitCacheSlot (&tmp, c, '@', '@');
	    debug (("removing temporary url file '%s'\n", tmp.File));
	    if (remove (tmp.File))
		LogErr  (NULL, L_ERROR, NULL, errno, "error while removing temporary url file '%s'", tmp.File);
	    c->Flags &= ~CACHE_DELETETMP;
	}
    }
    else
    {
	LogErr  (NULL, L_ERROR, NULL, errno, "cannot write url file '%s'", c->File);
	c->File[0] = '_';
	if (c->Flags & CACHE_QUEUED)
	    c->File[0] = '\0';
	RemCacheEntry (c);
    }
}


/*)) */
/*(( "ReadCacheList()/SaveCacheList()" */

/* Read the cache list file when it's there */
/* Format: Fields (max size):
 * Filename (MAX_FILENAME) " " Flags (1) Url (MAX_URLSAVE)
 * Terminated with \n. */
/* Flags:   v:Valid   q:Queued   r:Requeued (expired cache entry exists) */
/* <- -1: Failure   0: ok */

int ReadCacheList (void)
{
    cache_t *c;
    char Buffer [MAX_URLSAVE+MAX_FILENAME+3];
    FILE *Fd;
    char *p, *End;

    if (! (Fd = fopen (NAME_CACHETABLE, "r")))
	return -1;

    debug (("Cachetable valid.\n"));
    while (fgets (Buffer, MAX_URLSAVE+MAX_FILENAME+3, Fd))
    {
	End = Buffer + strlen (Buffer) - 1;
	*End = '\0';                                  /* Remove terminating '\n' */
	p = strchr (Buffer, ' ');
	if (p == NULL || p >= & Buffer [MAX_FILENAME])
	{
	    LogErr  (NULL, L_WARN, NULL, errno, "Invalid line in cachetable, skipping: '%s'", Buffer);
	    continue;
	}
	p[0] = '\0';
	if (p[1] != 'v' && p[1] != 'q' && p[1] != 'r')
	{
	    LogErr  (NULL, L_WARN, NULL, errno, "Unknown cache typ in cachetable, skipping: '%s'", Buffer);
	    continue;
	}
	if (p[2] == '\0')
	{
	    LogErr  (NULL, L_WARN, NULL, errno, "Invalid line in cachetable, skipping: '%s'", Buffer);
	    continue;
	}
	assert (&p[2] < End);
	assert (strlen (&p[2]) < MAX_URLSAVE);

	if (! (c = GetFreeCacheSlot ()))
	    break;

	strcpy (c->File, Buffer);
	strcpy (c->Url,  &p[2]);
	if (p[1] == 'v')
	    c->Flags = CACHE_VALID;
	else if (p[1] == 'q')
	    c->Flags = CACHE_QUEUED;
	else
	    c->Flags = CACHE_VALID | CACHE_QUEUED | CACHE_DELETETMP;
	debug (("read '%c' line from cachetable, file '%s', url '%s'\n", p[1], c->File, c->Url));
    }

    fclose (Fd);
    remove (NAME_CACHETABLE);
    return 0;
}


/* Save all cache entries in the cachetable file */

void SaveCacheList (void)
{
    cachearray_t *a;
    cache_t      *c;
    int          i;
    FILE         *Fd;

    if (! (Fd = fopen (NAME_CACHETABLE, "w")))
	return;

    for (a = Caches; a; a = a->Next)
	for (i = 0, c = a->Caches; i < MAX_CACHES; i++, c++)
	    if (c->File[0] && c->Url[0])
	    {
		if ((c->Flags & CACHE_VALID) && (c->Flags & CACHE_QUEUED))
		{
		    if (c->File[0] == '_')
			fprintf (Fd, "%s r%s\n", c->File, c->Url);
		    else
		    {
			LogErr  (NULL, L_WARN, NULL, errno, "Warning! Requeued cache consistency error, file '%s', url '%s'", c->File, c->Url);
			fprintf (stderr,    "Warning! Requeued cache consistency error, file '%s', url '%s'\n", c->File, c->Url);
		    }
		}
		else if (c->Flags & CACHE_VALID)
		{
		    if (c->File[0] == '_')
			fprintf (Fd, "%s v%s\n", c->File, c->Url);
		    else
		    {
			LogErr  (NULL, L_WARN, NULL, errno, "Warning! Valid cache consistency error, file '%s', url '%s'", c->File, c->Url);
			fprintf (stderr,    "Warning! Valid cache consistency error, file '%s', url '%s'\n", c->File, c->Url);
		    }
		}
		else if (c->Flags & CACHE_QUEUED)
		{
		    if (c->File[0] == '@')
			fprintf (Fd, "%s q%s\n", c->File, c->Url);
		    else
		    {
			LogErr  (NULL, L_WARN, NULL, errno, "Warning! Queued cache consistency error, file '%s', url '%s'", c->File, c->Url);
			fprintf (stderr,    "Warning! Queued cache consistency error, file '%s', url '%s'\n", c->File, c->Url);
		    }
		}
		else
		{
		    LogErr  (NULL, L_WARN, NULL, errno, "Warning! Unknown cache consistency error, file '%s', url '%s'", c->File, c->Url);
		    fprintf (stderr,    "Warning! Unknown cache consistency error, file '%s', url '%s'\n", c->File, c->Url);
		}
	    }

    fclose (Fd);
}

/*)) */
/*(( "BuildCache ()" */

/* Scan cache directory, build up cache and delete invalid cache entries */

void BuildCache (void)
{
    DIR *Dir;
    cachearray_t *aa, *aaa;
    cache_t *c, *cc, *ccc;
    struct dirent *d;

    if (! (Dir = opendir (CURRENTDIR)))
    {
	fprintf (stderr, "%s: cannot open cache directory: %s\n", PrgName, Net->strerror (errno));
	ExitAll (20);
    }

    errno = 0;
    c = GetFreeCacheSlot ();
    c->File [0] = '\0';
    assert (c != NULL);

    while ( (d = readdir (Dir)) )
    {
	if (strlen (d->d_name) > MAX_FILENAME-1)
	{
	    if (d->d_name[0] != '.')                 /* no special file... */
		fprintf (stderr, "unknown file '%s' in cache directory\n", d->d_name);
	}
	else
	{
	    strcpy (c->File, d->d_name);
	    if (c->File [0] == '_')                  /* found cache data - look for cache url */
	    {
		debug (("found data '%s'\n", c->File));
		aa = Caches;
		for (cc = aa->Caches; ; cc++)
		{
		    if (cc >= & aa->Caches [MAX_CACHES])
		    {
			aa = aa->Next;
			assert (aa != NULL);
			cc = aa->Caches;
		    }
		    if (cc == c)
			break;
		    if (strcmp (&c->File[1], &cc->File[1]) == 0 && cc->File[0] == '@')
		    {
			cc->File[0] = '_';
			c->File[0]  = '@';
			ReadCacheUrl (cc, c->File);  /* already read... */
			c->File[0] = '\0';
			break;
		    }
		}
		if (c == cc)                         /* not found... */
		{
		    if (! (c = GetFreeCacheSlot ()) )/* keep it, may be we'll get the url file, too */
		    {
			LogErr  (NULL, L_INFO, NULL, 0, "Cache table full while recaching");
			break;                       /* break from while - no room left... */
		    }
		    c->File [0] = '\0';
		}
	    }
	    else if (c->File [0] == '@')             /* found cache url - look for cache data */
	    {
		debug (("found url '%s'\n", c->File));
		aa = Caches;
		for (cc = aa->Caches; ; cc++)
		{
		    if (cc >= & aa->Caches [MAX_CACHES])
		    {
			aa = aa->Next;
			assert (aa != NULL);
			cc = aa->Caches;
		    }
		    if (cc == c)
			break;
		    if (strcmp (&c->File[1], &cc->File[1]) == 0 && cc->File[0] == '_')
		    {
			ReadCacheUrl (cc, c->File);  /* already read... */
			c->File[0] = '\0';
			break;
		    }
		}
		if (c == cc)                         /* not found... */
		{
		    if (! (c = GetFreeCacheSlot ())) /* keep it, may be we'll get the data file, too */
		    {
			LogErr  (NULL, L_INFO, NULL, 0, "Cache table full while recaching");
			break;                       /* break from while - no room left... */
		    }
		    c->File [0] = '\0';
		}
	    }
	    else
	    {
		if (c->File [0] != '.')             /* .files, .httpproxy-log or standard directories (unix) */
		    fprintf (stderr, "unknown file '%s' in cache directory\n", c->File);
		c->File[0] = '\0';
	    }
	}
    }
    if (errno)
	fprintf (stderr, "directory error while building cache table, continueing: %s\n", Net->strerror (errno));
    closedir (Dir);
    RemCacheEntry (c);

	/* now remove all incomplete cachentries (and files) */
    aa = Caches;
    for (cc = aa->Caches; ; cc++)
    {
	if (cc >= & aa->Caches [MAX_CACHES])
	{
	    aa = aa->Next;
	    assert (aa != NULL);
	    cc = aa->Caches;
	}
	if (cc == c)
	    break;
	if (cc->Url[0] == '\0' && cc->File [0] == '_')
	{
	    LogErr  (NULL, L_WARN, NULL, errno, "cache file '%s' invalid, removing.", cc->File);
	    RemCacheEntry (cc);
	}
    }

	/* mark all url only caches as queued */
    aa = Caches;
    for (cc = aa->Caches; ; cc++)
    {
	if (cc >= & aa->Caches [MAX_CACHES])
	{
	    aa = aa->Next;
	    assert (aa != NULL);
	    cc = aa->Caches;
	}
	if (cc == c)
	    break;
	if (cc->File [0] == '@')
	{
	    ReadCacheUrl (cc, cc->File);         /* the file name remains '@...' */
	    if (cc->File [0] == '@')
	    {
		cc->Flags = CACHE_QUEUED;        /* and ! CACHE_VAILD */
		aaa = Caches;
		for (ccc = aaa->Caches; ; ccc++)
		{
		    if (ccc >= & aaa->Caches [MAX_CACHES])
		    {
			aaa = aaa->Next;
			assert (aaa != NULL);
			ccc = aaa->Caches;
		    }
		    if (ccc == c)
			break;
		    if (ccc->File [0] == '_')
			if (cc != ccc && strcmp (ccc->Url, cc->Url) == 0)
			{
			    debug (("url '%s': queued url file %s associated with expired cache entry %s\n", cc->Url, cc->File, ccc->File));
			    assert (ccc->Flags & CACHE_VALID);
			    if (cc->File [9] != '@')           /* it has to be a special queued url file... */
			    {
				LogErr  (NULL, L_WARN, NULL, 0, "Warning! Double cache consistency error, file '%s', url '%s', file2 '%s'", ccc->File, ccc->Url, cc->File);
				fprintf (stderr,    "Warning! Double cache consistency error, file '%s', url '%s', file2 '%s'\n", ccc->File, ccc->Url, cc->File);
			    }

			    ccc->Flags |= CACHE_QUEUED | CACHE_DELETETMP;
			    cc->File[0] = '\0';
			    RemCacheEntry (cc);
			    ccc = c;         /* break all */
			    break;
			}
		}
		if (ccc == c)
		    debug (("queued url '%s'\n", cc->Url));
	    }
	}
    }
}


/*)) */
/*(( "ScanCache()" */

/* Scan whether a specific cache is already there, filled and ready to serve */
/* Returns 0: no, not there  1: yes, all set up  -1: Failure
 * 2: error message in buffer */
/* Sets up the request struct for correct filling, opens all streams, etc. */

int ScanCache (request_t *Req, char *Url)
{
    int i;
    cache_t *c;
    cachearray_t *a;
    static time_t LastRequestTime;

    debug (("Searching for cache for Url '%s'\n", Url));
    LastRequestTime = ThisRequestTime;
    ThisRequestTime = time (NULL);
    debug (("Last: %ld, This: %ld, Diff: %ld, reload: %s\n", LastRequestTime, ThisRequestTime,
	    difftime (ThisRequestTime, LastRequestTime),
	    difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime ? "yes" : "no"));

    for (a = Caches; a; a = a->Next)
	for (c = a->Caches, i=0; i < MAX_CACHES; c++, i++)
	    if (c->Url[0])
	    {
		if (strcmp (c->Url, Url) == 0)
		{
		    if (c->Flags & CACHE_VALID)
		    {
			switch (CheckCacheTime (c, ExpireCacheTime)) {
			case 0:
			    if (AlwaysReload || (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime))
			    {
				if (CheckCacheTime (c, 0) == -2)         /* force queueing / reloading */
				{
				    Req->Cache = c;
				    debug (("queued url on request\n"));
				    ErrToReq (Req, 202, -1, NULL, "Your request for reloading the document is queued.<BR>\n"
					      "You will get the new document next time you are online.<BR>\n"
					      "An expired cache entry exists and can be viewed by immedeately reloading this document.",
					      NULL);
				    return (2);
				}
				break;                                   /* forcing reload of url */
			    }

			    if (! (Req->Stream = fopen (c->File, "r")) )
			    {
				LogErr  (Req, L_WARN, c->Url, errno, "cannot open cache file '%s', removing", c->File);
				RemCacheEntry (c);
			    }
			    else
			    {
				debug (("Found cache entry, file '%s'\n", c->File));
				Req->Cache  = LastCache = c;             /* ok, found cache entry */
				Req->Flags |= REQ_DONE;
				return (1);
			    }
			    break;

				/* case -1: break; */
			case -2:
			    if (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime)
			    {
				debug (("Sending (invalid) cache file '%s' on request\n", c->File));
				if (! (Req->Stream = fopen (c->File, "r")) )
				{
				    LogErr  (Req, L_WARN, c->Url, errno, "cannot open cache file '%s', removing", c->File);
				    RemCacheEntry (c);
				    break;
				}
				else
				{
				    Req->Cache  = c;                         /* ok, send invalid cache entry */
				    Req->Flags |= REQ_DONE;
				    return (1);
				}
			    }
			    else
			    {
				if (OffLine)
				{
				    Req->Cache = LastCache = c;
				    debug (("Cache entry expired, queued\n"));
				    ErrToReq (Req, 202, -1, NULL, "Your request is queued.<BR>\n"
					      "You will get the document next time you are online.<BR>\n"
					      "An expired cache entry exists and can be viewed by immedeately reloading this document.",
					      NULL);
				    return (2);
				}
				else
				{
				    debug (("unqueueing and getting Cache entry\n"));
				    c->Flags   = 0;
				    c->File[0] = '_';
				    LastCache  = c;
				    goto HaveAlreadyCache;         /* sorry... */
				}
			    }
			}
		    }
		    else                                           /* c->Flags & CACHE_VALID */
		    {
			if (c->Flags & CACHE_QUEUED)
			{
			    if (OffLine)
			    {
				if (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime)
				{
				    Req->Cache = c;
				    ErrToReq (Req, 404, 0, "Already queued", "Your request is already queued.<BR>\n"
					      "You will get the document next time you are online.<BR>\n"
					      "You tried to get an expired cache entry, but this document is not cached right now.",
					      NULL);
				    return (2);
				}
				else
				{
				    Req->Cache = LastCache = c;
				    debug (("Cache entry already queued\n"));
				    ErrToReq (Req, 202, -1, NULL, "Your request is already queued.<BR>\n"
					      "You will get the document next time you are online.<BR>\n"
					      "There is no expired cache entry for this document.",
					      NULL);
				    return (2);
				}
			    }
			    else
			    {
				debug (("unqueueing and getting Cache entry\n"));
				c->Flags   = 0;
				c->File[0] = '_';
				LastCache  = c;
				goto HaveAlreadyCache;              /* sorry... */
			    }
			}
			else
			{
			    debug (("Second request while getting URL\n"));
			    ErrToReq (Req, 403, -1, "second request", "The same request was sent from another connection.\n"
				      "May be you requested a queued URL which is just being received.<BR>\n"
				      "Please try again in a few moments.",
				      c->Url);
			    LastCache = c;
			    return (2);
			}
		    }
		}
	    }


    if (! (c = GetFreeCacheSlot ()) )            /* not in cache so far */
    {
	if (OffLine)
	{
	    ErrToReq (Req, 500, 0, "Cache table full", "I'm sorry, but there's no memory left for the cache table.<BR>"
		      "It is not possible to queue up your request.",
		      Url);
	    return (2);
	}
	else
	{
	    LogErr  (Req, L_INFO, Url, 0, "Cache table full");
	    return (0);                          /* no more free entries - just proxy it */
	}
    }

    strcpy (c->Url, Url);

    if (OffLine)
    {
	Req->Cache = c;
	ErrToReq (Req, 202, -1, NULL, "Your new request is queued.<BR>\n"
		  "You will get the document next time you are online.", NULL);
	c->Flags = CACHE_QUEUED;
	LastCache = c;
	SaveCacheUrl (c);
	c->File[0] = '@';
	return (2);
    }

HaveAlreadyCache:

    if (! (Req->Stream = fopen (c->File, "w")) )
    {
	LogErr  (Req, L_ERROR, Url, errno, "cannot open new cache file '%s'", c->File);
	c->File[0] = '\0';
	RemCacheEntry (c);
	return (0);                              /* just proxy it */
    }

    debug (("New cache file '%s'\n", c->File));
    c->Flags = 0;
    Req->Cache = LastCache = c;
    return (0);
}


/*)) */
/*(( "GetCacheData ()" */

/* Fill request data space with data from cache
 * when a read error occures, no data will be received and ServWrite()
 * will automagically terminate the socket. */

void GetCacheData (request_t *Req)
{
    int Bytes;

    assert (Req->DataRecv < MAX_DATABUFFER);
    assert (Req->Stream);

    debug (("Getting more cache data - Url done: %s\n", Req->Flags & REQ_REQDONE ? "yes" : "no"));

    if ( (Bytes = fread (& Req->DataBuffer [Req->DataRecv], sizeof (char),
			MAX_DATABUFFER - Req->DataRecv, Req->Stream)) > 0)
	Req->DataRecv += Bytes;
    else
    if (! feof (Req->Stream))
	LogErr  (Req, L_ERROR, NULL, errno, "read error on cache file '%s'", Req->Cache->File);
    debug (("Read %d bytes\n", Bytes));
}


/*)) */
/*(( "BuildFdSets ()" */

/* Build fdsets (outstanding reads and writes) */

void BuildFdSets (void)
{
    int i;
    request_t *Req;

    assert (RequestsFree >= 0);
    debug (("Build: "));
    Net->initfd (RequestsFree > 0);

    for (i=0, Req=Requests; i < MaxRequests; i++, Req++)
    {
	if (Req->Flags & REQ_REQSOCKET)
	{
	    assert (Req->ReqSocket != 0);
	    if (Req->ReqRecv < MAX_REQBUFFER-1)
	    {
		debug ((", Read Req %d", Req-Requests));
		Net->setfdread (Req->ReqSocket);
	    }
	    if (Req->DataRecv > Req->DataSent)
	    {
		debug ((", Write Req %d", Req-Requests));
		Net->setfdwrite (Req->ReqSocket);
	    }
	}
	if (Req->Flags & REQ_CONNSOCKET)
	{
	    assert (Req->ConnSocket != 0);
	    if (Req->ReqRecv > Req->ReqSent || Req->UrlRecv > Req->UrlSent) /* Waiting for connect or sending request */
	    {
		debug ((", Write Con %d", Req-Requests));
		Net->setfdwrite (Req->ConnSocket);
	    }
	    if (Req->DataRecv < MAX_DATABUFFER)                /* Transfer Data */
	    {
		debug ((", Read Con %d", Req-Requests));
		Net->setfdread (Req->ConnSocket);
	    }
	}
    }
    debug (("\n"));
}


/*)) */
/*(( "CreateUrlRequest()/CheckUrl()" */

/* Create Url request according to specification */
/* Returns the length of the request. */
/* Prot and Host may be NULL, in that case Name is to be asumed to be a full URL.
 * No checks for Proxyproxy mode are done in this case. */

size_t CreateUrlRequest (const char *Method, const char *Prot, const char *Host, int Port, const char *Name, char *ReqSpace, int HttpOne, int ExpandProcent)
{
    char Buffer [MAX_URLBUFFER];
    char *p, *DokPrint;

    if (Prot && Host)
    {
	if (ProxyProxy)
	{
	    if (Port > -1)
		sprintf (ReqSpace, "%s %s://%s:%d/", Method, Prot, Host, Port);
	    else
		sprintf (ReqSpace, "%s %s://%s/", Method, Prot, Host);
	}
	else
	    sprintf (ReqSpace, "%s /", Method);
    }
    else
	sprintf (ReqSpace, "%s ", Method);

    DokPrint = ReqSpace + strlen (ReqSpace);

    for (p = Buffer; *Name; )
	if (isvalidhttp (*Name) || (*Name == '%' && ! ExpandProcent))
	    *p++ = *Name++;
	else
	{
	    sprintf (p, "%%%02x", *Name++);
	    p += 3;
	}
    *p = '\0';

    if (HttpOne)
	sprintf (DokPrint, "%s HTTP/1.0\r\n", Buffer);
    else
	sprintf (DokPrint, "%s\r\n", Buffer);
    debug (("Request '%s', len %d created.\n", ReqSpace, strlen (ReqSpace)));
    return (strlen (ReqSpace));
}


/* Check whether the URL is read completely. Sets REQ_REQDONE accordingly.
 * Attention! This routine relys on the fact, that ScanUrl was called already!
 * That means especially, that the first line was already read completely. */
/* The routine does not check for Url termination on Buffer boundaries. So
 * the ReqBuffer has to be shifted always (four bytes remaining always) and
 * never be cleared at all. */

void CheckUrl (request_t *Req)
{
    debug (("Checking Url\n"));
    if (Req->Flags & REQ_HTTP1X0)
    {
	if (strstr (Req->ReqBuffer, "\n\n") || strstr (Req->ReqBuffer, "\r\r") ||
	    strstr (Req->ReqBuffer, "\n\r\n\r") || strstr (Req->ReqBuffer, "\r\n\r\n"))
	    Req->Flags |= REQ_REQDONE;
    }
    else
	Req->Flags |= REQ_REQDONE;
#ifdef DEBUG
   if (Req->Flags & REQ_REQDONE)
	debug (("http request complete\n"));
#endif
}


/*)) */
/*(( "ScanUrl ()" */

/* Scan the URL and divide it into several parts. Understands http/0.9
 * and http/1.0 versions right now. Sets REQ_REQDONE when URL is complete. */
/* Req->UrlBuffer is valid after calling this routine and REQ_URLDONE set. */
/* Returns -1 in case of failure, 0 on correct termination or found cache,
 * 1: found cache slot, FileD is open, 2: failure, cache contains error. */
/* Port and Address are value-return arguments. */

int ScanUrl (request_t *Req, char *Address, int *Port, const char **SaveUrl)
{
    static char BufDOK [MAX_URLBUFFER +256];                                       /* a critical array... but that's enough */

    char BufMETH [12];
    char BufPROT [16];
    char BufURL [1024];
    char BufVERS [8];
    int  CharsRead, CharsRead2, i;
    char *DokPtr, *PortPtr;
    char *Host, *ObjectName;

    if (sscanf (Req->ReqBuffer, "%11s %15[a-zA-Z0-9]%n", BufMETH, BufPROT, &CharsRead) < 2)
	return (-1);
    debug (("METHOD '%s', PROT '%s'\n", BufMETH, BufPROT));
    if (CharsRead >= Req->ReqRecv)
	return (-1);
    if (strncmp (& Req->ReqBuffer [CharsRead], "://", 3) != 0)
	return (-1);
    if (sscanf (& Req->ReqBuffer [CharsRead+3], "%1022s%n", BufURL, &CharsRead2) < 1)
	return (-1);
    debug (("URL '%s'\n", BufURL));
    CharsRead += 3 + CharsRead2;

    if (! (DokPtr = strchr (BufURL, '/')) )
    {
	if (! (DokPtr = strchr (BufURL, ' ')) )                            /* no host... */
	    return (-1);
	*DokPtr = 0;                                                       /* empty Objectname (Root) */
    }
    else
	*DokPtr++ = 0;

    if (strcasecmp (BufPROT, "http") != 0)
	*Port = -1;
    else
	*Port = Net->StdHttpPort;
    if ( (PortPtr = strchr (BufURL, ':')) )
    {
	*PortPtr = 0;
	*Port = atoi (PortPtr + 1);
    }

    Host = BufURL;
    strncpy (Address, BufURL, MAX_HOSTNAMELEN-1);
    Address [MAX_HOSTNAMELEN-1] = '\0';

    BufVERS [2] = 0;                                                       /* BufVERS is missused here... */
    if (*Port > -1)
	sprintf (BufDOK, "%s://%s:%d/", BufPROT, Host, *Port);
    else
	sprintf (BufDOK, "%s://%s/", BufPROT, Host);
    ObjectName = BufDOK + strlen (BufDOK);
    for (PortPtr = ObjectName; *DokPtr; DokPtr++, PortPtr++)
    {
	if (*DokPtr == '%')
	{
	    if (! (BufVERS [0] = *++DokPtr) )
		break;
	    if (! (BufVERS [1] = *++DokPtr) )
		break;
	    if ( (i = strtol (BufVERS, NULL, 0x10)) )
		*PortPtr = i;
	    else
		*PortPtr = '?';
	}
	else
	    *PortPtr = *DokPtr;
    }
    *PortPtr = 0;

    *SaveUrl = BufDOK;
    debug (("Proto '%s', Host '%s', Port %d, Dok '%s'\n", BufPROT, Host, *Port, BufDOK));

    if (OffLine && strcasecmp (BufMETH, "get") != 0)                       /* no get command */
	return (-1);
    if (! ProxyProxy)
	if (strcasecmp (BufPROT, "http") != 0)                             /* we only support http urls so far */
	    return (-1);

    if (Req->ReqBuffer [CharsRead] == ' ' && CharsRead < Req->ReqRecv)
	if (sscanf (& Req->ReqBuffer [CharsRead], " %5s", BufVERS) == 1)
	    if (strcasecmp (BufVERS, "http/") == 0)
	    {
		debug (("got HTTP/1.0 or greater request\n"));
		Req->Flags |= REQ_HTTP1X0;
	    }

    CheckUrl (Req);
    if (strlen (BufDOK) > MAX_URLSAVE-1)                     /* That one cannot be cached */
    {
	LogErr  (Req, L_INFO, BufDOK, 0, "ReqBuffer size (%d chars) exceeded - document is not cached", MAX_URLSAVE-1);

	if (strlen (BufDOK) < MAX_URLBUFFER-1)
	{
	    strcpy (Req->UrlBuffer, BufDOK);                 /* Save URL for later logging purpose (missused UrlBuffer) */
	    Req->Flags |= REQ_URLINBUFFER;
	}
	return (0);
    }

	/* check for service URLs */
    if (strcasecmp (Host, "proxy...") == 0)
    {
	if (strcasecmp (Req->Address, "localhost") == 0 || strcmp (Req->Address, "127.0.0.1") == 0)
	    return (ScanService (Req, ObjectName));
	else
	{
	    ErrToReq (Req, 403, -1, "Service URLs are not allowed", "You are not allowed to request service URLs"
		      "from other hosts than the one httpproxy is started on.",
		      BufDOK);
	    LogErr  (Req, L_INFO, BufDOK, 0, "Service URL tried from '%s'", Req->Address);
	    return (2);
	}
    }

	/* create UrlBuffer entry */
    assert (Req->UrlSent == 0);
    Req->UrlRecv = CreateUrlRequest (BufMETH, BufPROT, Host, *Port, ObjectName, Req->UrlBuffer, Req->Flags & REQ_HTTP1X0, FALSE);
    PortPtr = strchr (& Req->ReqBuffer [CharsRead], '\n');   /* PortPtr und DokPtr are missused here */
    DokPtr  = strchr (& Req->ReqBuffer [CharsRead], '\r');
    assert (DokPtr == NULL || PortPtr == NULL || PortPtr == DokPtr + 1 || DokPtr == PortPtr + 1);
    if (DokPtr == PortPtr + 1 || PortPtr == NULL)
	PortPtr = DokPtr;
    assert (PortPtr != NULL);

    Req->ReqSent = PortPtr - Req->ReqBuffer + 1;
    Req->Flags  |= REQ_URLDONE;

    if (strcasecmp (BufMETH, "get") != 0)                                  /* only get's will be cached */
    {
	debug (("no get method - no caching\n"));
	return (0);
    }

    return (ScanCache (Req, BufDOK));
}


/*)) */
/*(( "DeleteRequest/Connect ()" */

/* Close the connection socket only. When a request socket is still open
 * and no data is in the cache, close all.
 * Delete cache entry in case of an error. Check the answer whether it is
 * an error message or a regular answer. Errors should not be cached! */
/* When Flags == 0 nothing is done at all */

void DeleteConnect (request_t *Req, int ok)
{
/*    cache_t tmp;*/

    debug (("DeleteCon %d\n", Req-Requests));
    if (! Req->Flags)
	return;

    if (Req->Flags & (REQ_CONNSOCKET | REQ_CONNERR))
    {
	if (Req->Flags & REQ_CONNSOCKET)
	    Net->close (Req->ConnSocket);
	Req->Flags &= ~(REQ_CONNSOCKET | REQ_CONNERR);
	if (Req->Stream)
	    fclose (Req->Stream);
	Req->Stream = NULL;
	if (ok)
	{
	    if (Req->Cache)
	    {
		if (! (Req->Cache->Flags & CACHE_QUEUED)) /* we've written into a cache file */
		{
		    LogStd (Req, "new");
		    SaveCacheUrl (Req->Cache);
		    Req->Cache = NULL;
		}
	    }
	    else
		LogStd (Req, "proxied");
	}
	else if (Req->Cache)                                        /* not ok - a error occured */
	{
	    if (! (Req->Cache->Flags & CACHE_QUEUED))
	    {
		LogStd (Req, NULL);                                 /* There really *should* be a log entry */
		Req->Cache->Url[0] = '\0';
		RemCacheEntry (Req->Cache);
		Req->Cache = NULL;
	    }
	    else
		LogStd (Req, NULL);
	}
    }
    LogStd (Req, NULL);
    if (Req->Flags & REQ_REQSOCKET)
    {
	if (Req->DataRecv <= Req->DataSent && (Req->Flags & REQ_REQDONE))
	{
	    Net->close (Req->ReqSocket);       /* All done. Hugh! */
	    Req->Flags = 0;
	}
	else
	    Req->Flags |= REQ_DONE;
    }
    else
	Req->Flags = 0;

    if (! (Req->Flags & REQ_PENDINGMASK))
    {
	Req->Flags = 0;
	RequestsFree++;
    }
}


/* Close the request socket only. When a connection socket is already up
 * (and enough data is already read) continue filling up the cache. When
 * everything is done, close all. The URL should be checked, too.
 * When it is not complete, all connections should be terminated
 * on CacheUnreadRequests false. */
/* Don't call DeleteConnect when Flags == 0... */

void DeleteRequest (request_t *Req, int ok)
{
    debug (("DeleteReq %d\n", Req-Requests));
    if (Req->Flags & REQ_REQSOCKET)
    {
	Net->close (Req->ReqSocket);
	Req->Flags &= ~REQ_REQSOCKET;
    }

    if (Req->Flags & REQ_DONE)                  /* Maybe we're getting data from the cache */
    {
	if (Req->Stream)
	{
	    fclose (Req->Stream);
	    LogStd (Req, "cached");
	}
	else
	    LogStd (Req, "queued");             /* perhaps there is already a logentry - then this is overridden*/
	Req->Stream = NULL;
	DeleteConnect (Req, ok);
	Req->Flags  = 0;                        /* All done. Hugh! */
	debug (("All done - deleterequest\n"));
    }
    else if (Req->Flags & REQ_REQDONE)
    {
	if (! (Req->Flags & REQ_CONNSOCKET))    /* Aborting cache sending */
	    DeleteConnect (Req, FALSE);
	else if (CacheUnreadRequests)
	    Req->DataSent = Req->DataRecv = 0;  /* Continue getting data into the cache */
	else
	    DeleteConnect (Req, FALSE);         /* Remove connection */
    }
    else
    {
	Req->Flags |= REQ_DONE;
	DeleteConnect (Req, FALSE);             /* No request known so far... */
    }
}


/*)) */
/*(( "ServConnect ()" */

/* The first line of the URL is already there, so we can connect to the
 * remote host. */

void ServConnect (request_t *Req)
{
    char Host [MAX_HOSTNAMELEN];
    int  Port;
    const char *Url = NULL;             /* only for logging */

    debug (("ServCon for Req %d; Flags %x\n", Req-Requests, Req->Flags));

    if (Req->Flags & REQ_REQSOCKET)
    {
	if (strchr (Req->ReqBuffer, '\n') == 0 &&
	    strchr (Req->ReqBuffer, '\r') == 0)         /* first line of URL is not there... */
	    return;

	if (Req->Flags & REQ_URLDONE)  /* already set up */
	{
	    CheckUrl (Req);
	    return;
	}
    }

    switch (ScanUrl (Req, Host, &Port, &Url)) {
    case 0:                                   /* need to get document from remote host */
	if (! Req->Cache)
	    debug  (("#%02d: serving uncacheable request '%s'\n",
		     Req-Requests, Req->ReqBuffer));
	break;

    case 1:                                   /* already cached - no need to connect to remote host */
	debug  (("#%02d: sending URL '%s' from cache '%s'\n",
		 Req-Requests, Req->Cache->Url, Req->Cache->File));
	GetCacheData (Req);
	return;

    case 2:                                   /* send error message from buffer */
	return;

    default:
	{
	    Req->Flags |= REQ_DONE | REQ_REQDONE;
	    ErrToReq (Req, 400, 0, "Invalid Request", "Your Request is not a valid URL.<BR>\n"
		      "Please check it and try again. Perhaps you tried to queue a page not using the 'GET' method\n"
		      "or you tried to get a non-http: URL without proxyproxy.",
		      Url);
	    return;
	}
    }

    if (OffLine)
    {
	ErrToReq (Req, 500, 0, "Unable to serv", "It is not possible to connect to a remote host in offline mode.<BR>\n"
		  "Either the requested URL is to long to be queued or an internal error has occured. When the\n"
		  "URL is rather short, please contact the author of " PRG_NAME,
		  Url);
	return;
    }

    if (ProxyProxy)
    {
	if ( (Req->ConnSocket = Net->open (ProxyHost, ProxyPort)) < 0)
	{
	    Req->Flags |= REQ_CONNERR;
	    ErrToReq (Req, 404, errno, "Proxyproxy Host Unreachable / read() failed",
		      "The host you specified with the 'proxy' option can't be contacted.<BR>\n"
		      "Please wait until the proxy is up again or start " PRG_NAME " in offline mode.",
		      NULL);
	    return;
	}
    }
    else
    {
	if ( (Req->ConnSocket = Net->open (Host, Port)) < 0)
	{
	    Req->Flags |= REQ_CONNERR;
	    debug (("Net->open failed\n"));
	    ErrToReq (Req, 404, errno, "Host Unreachable / read() failed",
		      "The specified host can't be contacted.<BR>\n"
		      "Please wait until it is up again or try any other URLs.",
		      NULL);
	    return;
	}
    }

    Req->Flags |= REQ_CONNSOCKET;

#if 0
    if (errno != EINPROGRESS)
    {
	if (ProxyProxy)
	    ErrToReq (Req, 404, errno, "Proxyproxy Host Unreachable",
		      "The host you specified with the 'proxy' option can't be contacted.<BR>\n"
		      "Please wait until the proxy is up again or start " PRG_NAME " in offline mode.",
		      Url);
	else
	    ErrToReq (Req, 404, errno, "Host Unreachable",
		      "The specified host can't be contacted.<BR>\n"
		      "Please wait until it is up again or try any other URLs.",
		      Url);
	return;
    }
#endif

    debug (("ServCon -> Conn %d\n", (int) Req->ConnSocket));
}


/*)) */
/*(( "ServServer ()" */

/* Serv the server port. Accept new connections, set up request database */

void ServServer (int Peer, const char *PeerName)
{
    request_t *Req = Requests;

    assert (RequestsFree > 0);
    DateStamp (&RequestStamp);

    while (Req->Flags)                        /* any Flags set -> occupied */
	Req++;
    debug (("accept Req %d\n", Req-Requests));
    assert (Req - Requests < MaxRequests);

    strncpy (Req->Address, PeerName, MAX_HOSTNAMELEN-1);
    Req->Address [MAX_HOSTNAMELEN-1] = '\0';
    debug  (("#%02d: new request from %s\n", Req-Requests, PeerName));

    Req->ReqSocket = Peer;
    Req->Flags    |= REQ_REQSOCKET;
    Req->ReqBuffer[0] = Req->UrlBuffer [0] = Req->DataBuffer [0] = '\0';
    Req->ReqSent   = Req->ReqRecv = Req->UrlSent = Req->UrlRecv =
		     Req->DataSent = Req->DataRecv = 0;
    Req->Cache     = NULL;
    Req->Stream    = NULL;
    Req->LastStamp = RequestStamp;
    RequestsFree--;
}


/*)) */
/*(( "ServRead ()" */

/* Server for all reads */

void ServRead (void)
{
    int i, Bytes;
    register request_t *Req;

    for (i=0, Req=Requests; i < MaxRequests; i++, Req++)
    {
	if ((Req->Flags & REQ_REQSOCKET) && Net->checkread (Req->ReqSocket))
	{
	    assert (Req->ReqRecv < MAX_REQBUFFER-1);
	    if ( (Bytes = Net->read (Req->ReqSocket, & Req->ReqBuffer [Req->ReqRecv],
				     MAX_REQBUFFER-1 - Req->ReqRecv)) < 0)
	    {
		LogErr  (Req, L_ERROR, NULL, errno, "on receiving request");
		DeleteRequest (Req, FALSE);
	    }
	    else
	    {
		Req->LastStamp = RequestStamp;
		Req->ReqRecv += Bytes;
		Req->ReqBuffer [Req->ReqRecv] = '\0';        /* needed for strchr() */

		debug (("Read %d bytes from Req %d to 0x%x containing:\n'%s'\n", Bytes,
			Req-Requests, Req->ReqRecv-Bytes, & Req->ReqBuffer [Req->ReqRecv - Bytes]));
#if defined (DEBUG) && ! defined (_M68020)
		if ((Req->ReqRecv - Bytes) & 0x0001)
		    debug (("(odd address...\n"));
		else
#endif
		debug (("= 0x %08lx %08lx %08lx %08lx\n", * (long *) (Req->ReqBuffer + Req->ReqRecv - Bytes),
			* (long *) (Req->ReqBuffer + Req->ReqRecv - Bytes + 4), * (long *) (Req->ReqBuffer + Req->ReqRecv - Bytes + 8),
			* (long *) (Req->ReqBuffer + Req->ReqRecv - Bytes + 12)));

		if (Bytes == 0)                              /* request socket gone away */
		{
		    DeleteRequest (Req, TRUE);
		    continue;
		}
		ServConnect (Req);                           /* Check if anything can be done already */
		if ((Req->Flags & REQ_DONE) && (Req->Flags & REQ_REQDONE) && (Req->DataRecv == 0))
		{
		    DeleteRequest (Req, TRUE);               /* all done, another time */
		    continue;
		}
		if ((Req->ReqRecv == MAX_REQBUFFER-1) && ! (Req->Flags & REQ_CONNSOCKET))
		{                                            /* buffer full and no connection yet */
		    if (Req->Flags & REQ_URLDONE)
			Req->ReqRecv = Req->ReqSent = 0;     /* Skip remaining data (we won't send it...) */
		    else
		    {
			LogErr  (Req, L_ERROR, NULL, 0, "URL buffer (size %d) overflow", MAX_REQBUFFER-1);
			DeleteRequest (Req, FALSE);
		    }
		}
	    }
	}
	if ((Req->Flags & REQ_CONNSOCKET) && Net->checkread (Req->ConnSocket))
	{
	    assert (Req->DataRecv < MAX_DATABUFFER);
	    if ( (Bytes = Net->read (Req->ConnSocket, & Req->DataBuffer [Req->DataRecv],
				     MAX_DATABUFFER - Req->DataRecv)) < 0)
	    {
		if (ProxyProxy)
		    ErrToReq (Req, 404, errno, "Proxyproxy Host Unreachable / read() failed",
			      "The host you specified with the 'proxy' option can't be contacted.<BR>\n"
			      "Please wait until the proxy is up again or start " PRG_NAME " in offline mode.",
			      NULL);
		else
		    ErrToReq (Req, 404, errno, "Host Unreachable / read() failed",
			      "The specified host can't be contacted.<BR>\n"
			      "Please wait until it is up again or try any other URLs.",
			      NULL);
		DeleteConnect (Req, FALSE);
	    }
	    else
	    {
		Req->LastStamp = RequestStamp;
		debug (("Read %d bytes from Con %d\n", Bytes, Req-Requests));
		if (Bytes == 0)                               /* We're done */
		{
		    DeleteConnect (Req, TRUE);
		    continue;
		}
		else if (Req->Stream)
		    fwrite (& Req->DataBuffer [Req->DataRecv], sizeof (char),      /* no error checking... */
			    Bytes, Req->Stream);
		if (Req->Flags & REQ_REQSOCKET)
		    Req->DataRecv += Bytes;                   /* else: only save cache data */
	    }
	}
    }
}


/*)) */
/*(( "ServWrite ()" */

/* Server for all writes */

void ServWrite (void)
{
    int i, Bytes;
    register request_t *Req;

    for (i=0, Req=Requests; i < MaxRequests; i++, Req++)
    {
	if ((Req->Flags & REQ_REQSOCKET) && Net->checkwrite (Req->ReqSocket))
	{
	    assert (Req->DataRecv > Req->DataSent);
	    if ( (Bytes = Net->write (Req->ReqSocket, & Req->DataBuffer [Req->DataSent],
				      Req->DataRecv - Req->DataSent)) < 0)
	    {
		LogErr  (Req, L_ERROR, NULL, errno, "on sending data");
		DeleteRequest (Req, FALSE);
		continue;
	    }
	    else
	    {
		Req->LastStamp = RequestStamp;
		debug (("Wrote %d bytes to Req %d\n", Bytes, Req-Requests));
		Req->DataSent += Bytes;
		assert (Req->DataRecv >= Req->DataSent);
		if (Bytes == 0)                            /* request socket is unable to receive data */
		{
		    LogErr  (Req, L_ERROR, NULL, 0, "request socket unable to receive data");
		    DeleteRequest (Req, FALSE);
		    continue;
		}

		if (Req->DataSent == Req->DataRecv)        /* clear / shift data buffer */
		    Req->DataSent = Req->DataRecv = 0;
		else if (Req->DataSent > SHIFT_DATABUFFER)
		{
		    debug (("shifting databuffer size %d by %d bytes\n", Req->DataRecv - Req->DataSent, Req->DataSent));
		    memmove (Req->DataBuffer, & Req->DataBuffer [Req->DataSent],
			     Req->DataRecv - Req->DataSent);
		    Req->DataRecv -= Req->DataSent;
		    Req->DataSent = 0;
		}
		if (Req->DataRecv < MAX_DATABUFFER && (Req->Flags & REQ_DONE) && Req->Stream)
		    GetCacheData (Req);
		if (Req->DataRecv == 0 && (Req->Flags & REQ_DONE) && (Req->Flags & REQ_REQDONE))   /* We're already done */
		    DeleteRequest (Req, TRUE);

	    }
	}
	if ((Req->Flags & REQ_CONNSOCKET) && Net->checkwrite (Req->ConnSocket))
	{
	    if (Req->UrlRecv > Req->UrlSent)
		Bytes = Net->write (Req->ConnSocket, &Req->UrlBuffer [Req->UrlSent],
				    Req->UrlRecv - Req->UrlSent);
	    else
	    {
		assert (Req->ReqRecv > Req->ReqSent);
		Bytes = Net->write (Req->ConnSocket, & Req->ReqBuffer [Req->ReqSent],
				    Req->ReqRecv - Req->ReqSent);
	    }
	    if (Bytes < 0)
	    {
		if (ProxyProxy)
		    LogErr  (Req, L_ERROR, NULL, errno, "proxyproxy host unreachable");
		else
		    LogErr  (Req, L_ERROR, NULL, errno, "on sending url");
		DeleteConnect (Req, FALSE);
	    }
	    else
	    {
		Req->LastStamp = RequestStamp;
		debug (("Wrote %d bytes to Conn %d from %s, 0x%x\n", Bytes, Req-Requests,
			Req->UrlRecv > Req->UrlSent ? "UrlBuffer" : "ReqBuffer", Req->UrlRecv > Req->UrlSent ? Req->UrlSent : Req->ReqSent));

		if (Req->UrlRecv > Req->UrlSent)
		    Req->UrlSent += Bytes;
		else
		    Req->ReqSent += Bytes;
		if (Bytes == 0)                               /* connection socket is unable to receive url */
		{
		    LogErr  (Req, L_ERROR, NULL, errno, "connection socket unable to receive request");
		    DeleteConnect (Req, FALSE);
		    continue;
		}

		if (Req->ReqSent > SHIFT_REQBUFFER)
		{
		    debug (("shifting reqbuffer size %d+4 by %d bytes\n", Req->ReqRecv - Req->ReqSent, Req->ReqSent-4));
		    memmove (Req->ReqBuffer, & Req->ReqBuffer [Req->ReqSent - 4],
			     Req->ReqRecv - Req->ReqSent + 4);
		    Req->ReqRecv -= Req->ReqSent - 4;
		    Req->ReqSent = 4;
		}
	    }
	}
    }
}


/*)) */
/*(( "CacheInUse()/RequestQueued()" */

/* Check wheather a cache entry is used in any other request */

request_t *CacheInUse (cache_t *c)
{
    request_t *Req = Requests;
    int i;

    for (i = 0; i < MaxRequests; i++, Req++)
	if (Req->Cache == c)
	    return (Req);
    return (NULL);
}


/* Initiate contact to remote host for a queued request */

void RequestQueued (cache_t *c)
{
    request_t *Req = Requests;
    int     Len;

    assert (MAX_REQBUFFER >= 4 * MAX_URLSAVE);  /* to be on the save side */

    while (Req->Flags)                        /* any Flags set -> occupied */
	Req++;
    assert (Req - Requests < MaxRequests);

    Req->Flags  = REQ_REQDONE;
    Req->ReqSent= Req->DataSent = Req->DataRecv = Req->UrlSent = Req->UrlRecv = 0;
    Req->Cache  = NULL;
    Req->Stream = NULL;
    Req->Address[0] = '\0';
    Req->LastStamp = RequestStamp;
    RequestsFree--;

    Len = CreateUrlRequest ("GET", NULL, NULL, 0, c->Url, Req->ReqBuffer, TRUE, TRUE);
    sprintf (& Req->ReqBuffer [Len], "User-Agent: %s\r\nAccept: */*\r\n\r\n", VERSHTTP);
    Req->ReqRecv = strlen (Req->ReqBuffer);
    debug (("initiating request '%s'\n", Req->ReqBuffer));
    if (c->File [0] == '@')
	c->Url [0] = '\0';
    RemCacheEntry (c);
    ServConnect (Req);
}


/*)) */
/*(( "CheckTimeouts ()" */
/* Check whether any timeouts have occured */
/* it is checked at least every MIN_TIMEOUT to 2*MIN_TIMEOUT seconds.
 * Thus MIN_TIMEOUT is the minimal granularity for checks. */

void CheckTimeouts (void)
{
    int i;
    long CalcTime;
    request_t *Req;
    struct DateStamp TimeoutStamp;  /* Timeout check Stamp */

    DateStamp (&RequestStamp);

	/* Calculate NextCheck stamp */
    NextCheckStamp.ds_Minute        = NextCheckStamp.ds_Days   = 0;
    if ( (NextCheckStamp.ds_Tick    = RequestStamp.ds_Tick   + (MIN_TIMEOUT % 60) * TICKS_PER_SECOND) >= 60 * TICKS_PER_SECOND)
    {
	NextCheckStamp.ds_Tick     -= 60 * TICKS_PER_SECOND;
	NextCheckStamp.ds_Minute    = 1;
    }
    if ( (NextCheckStamp.ds_Minute += RequestStamp.ds_Minute + (MIN_TIMEOUT / 60) % (24 * 60)) >= 24 * 60)
    {
	NextCheckStamp.ds_Minute   -= 24 * 60;
	NextCheckStamp.ds_Days      = 1;
    }
    NextCheckStamp.ds_Days         += RequestStamp.ds_Days   + MIN_TIMEOUT / (24 * 60 * 60);

	/* Calculate Timeout check stamp */
	/* Could use the DateStamp entries, but I don't wanna rely on the entries being singed */
    CalcTime         = RequestStamp.ds_Tick - (TimeoutTime % 60) * TICKS_PER_SECOND;
    if (CalcTime < 0)
    {
	TimeoutStamp.ds_Tick = CalcTime + 60 * TICKS_PER_SECOND;
	CalcTime = -1;
    }
    else
    {
	TimeoutStamp.ds_Tick = CalcTime;
	CalcTime = 0;
    }
    CalcTime        += RequestStamp.ds_Minute - (TimeoutTime / 60) % (24 * 60);
    if (CalcTime < 0)
    {
	TimeoutStamp.ds_Minute = CalcTime + 24 * 60;
	CalcTime = -1;
    }
    else
    {
	TimeoutStamp.ds_Minute = CalcTime;
	CalcTime = 0;
    }
    CalcTime        += RequestStamp.ds_Days - TimeoutTime / (24 * 60 * 60);
    assert (CalcTime >= 0);                              /* kidding?!? */
    TimeoutStamp.ds_Days = CalcTime;

	/* Check requests */
    for (i=0, Req=Requests; i < MaxRequests; i++, Req++)
	if (Req->Flags)
	{
	    if (CompareDates (&Req->LastStamp, &TimeoutStamp) > 0)
	    {
		if (Req->Flags & REQ_CONNSOCKET)
		{
		    ErrToReq (Req, 404, 0, "Timeout", "The specified host did not send any more data.<BR>\n"
			      "The connection was cancled.",
			      NULL);
		    Req->LastStamp = RequestStamp;
		    DeleteConnect (Req, FALSE);
		}
		else
		{
		    LogErr  (Req, L_ERROR, NULL, 0, "Client timeout");
		    DeleteRequest (Req, FALSE);
		}
	    }
	}
}

/*)) */
/*(( "CheckGetQueued ()" */

/* Check number of pending requests and initiate getting of queued URLs */

void CheckGetQueued (int Force)         /* Force == 1 resets getqueueud state */
{
    static cachearray_t *a = NULL;
    static int Nr = 0;
    static int State = 1;
    cache_t *c;

    if (Force)
	State = 1;
    if (State == 3 || ! GetQueued || OffLine)
	return;
    if (a == NULL)
	a = Caches;

    debug (("State %d\n", State));

    while (RequestsFree > MIN_REQUESTS)
	switch (State) {
	case 1:                         /* get all CACHE_QUEUED & ! CACHE_VALID */
	    for (c = & a->Caches [Nr]; Nr < MAX_CACHES; Nr++, c++)
		if ((c->Flags & CACHE_QUEUED) && ! (c->Flags & CACHE_VALID))    /* get urls without expired caches first */
		    if (! CacheInUse (c))             /* inUse caches won't be get at all... */
		    {
			RequestQueued (c);
			break;
		    }
	    if (Nr < MAX_CACHES)
		continue;
	    Nr = 0;
	    if (a->Next)
	    {
		a = a->Next;
		continue;
	    }

	    State = 2;
	    a     = Caches;  /* no break! */

	case 2:
	    for (c = & a->Caches [Nr]; Nr < MAX_CACHES; Nr++, c++)
		if (c->Flags & CACHE_QUEUED)
		{
			/* assert (c->Flags & CACHE_VALID); */
		    if (! CacheInUse (c))
		    {
			RequestQueued (c);
			break;
		    }
		}
	    if (Nr < MAX_CACHES)
		continue;
	    Nr = 0;
	    if (a->Next)
	    {
		a = a->Next;
		continue;
	    }
	    State = 3;
	    return;

	default:
	    assert (0);
	}
}


/*)) */
/*(( "ShutdownConnects ()" */

/* Shutdown: Close and delete all invalid cache entries. */

void ShutdownConnects (void)
{
    cachearray_t *a;
    cache_t *c, Tmp, *cc;
    request_t *Req;
    int     i;

	/* delete and queue nonready cache entries */
    for (a = Caches; a; a = a->Next)
	for (i = 0, c = a->Caches; i < MAX_CACHES; i++, c++)
	    if (c->File[0] && (c->Flags & (CACHE_VALID | CACHE_QUEUED)) +0 == 0)      /* !!! */
	    {
		Req = CacheInUse (c);
		if (Req && (Req->Flags & REQ_CONNSOCKET))
		{
		    if (Req->Stream)                      /* close cache stream (we're getting a new file) */
			fclose (Req->Stream);
		    Req->Stream = NULL;
		    if (c->Url[0])
		    {
			    /* Don't queue inaktive requests (the ones sent from the proxy) when CacheUnreadRequests=0.
			     * Aktive requests are always requeued. */
			if (Req->Flags & REQ_REQSOCKET || CacheUnreadRequests)
			{
			    LogErr  (Req, L_INFO, NULL, 0, "queueing URL on shutdown");
			    SaveCacheUrl (c);
			    assert (c->Url[0] != '\0');
			    Tmp = *c;
			    c->Url[0] = '\0';                 /* keep the Urlfile */
			    RemCacheEntry (c);
			    cc = GetFreeCacheSlot ();
			    assert (cc != NULL);
			    InitCacheSlot (cc, &Tmp, '@', '.');
			    cc->Flags = CACHE_QUEUED;
			    Req->Cache = NULL;                /* the cache entry is already removed */
			}
		    }
		}
		else
		    debug (("File '%s', Url '%s', Flags 0x%x without connect-request!\n", c->File, c->Url, c->Flags));
	    }
	/* now remove all connections */
    for (i=0, Req=Requests; i < MaxRequests; i++, Req++)
	if ((Req->Flags & REQ_CONNSOCKET))
	{
	    LogErr  (Req, L_INFO, NULL, 0, "removing URL on shutdown");
	    DeleteConnect (Req, FALSE);
	}
}


/*)) */
/*(( "main ()" */

/* The main routine */

void main (int argc, char **argv)
{
    netmethods_t *Netlist[] = { &NetAmiTCP, &NetAS225, NULL }; /* all available net protocolls */
    netmethods_t **TestNet;
    char   *ProxyHost = NULL;
    char   *LogFile   = "nil:";
    int    ProxyPort;
    char   **FullArgv = argv;

    for (TestNet = Netlist; (Net = *TestNet); TestNet++)
	if (Net->init (FALSE))                 /* Check for open in nonblocking mode */
	    break;

    if (! Net)
    {
	fprintf (stderr, "Couldn't open network protocol handler - perhaps the network stack is not running.\n"
			 "Available network protocol handlers:\n");
	for (TestNet = Netlist; (Net = *TestNet); TestNet++)
	    fprintf (stderr, "%s\n", Net->Descr);
	ExitAll (20);
    }

    PrgName = *argv++;

    if (--argc == 1)
	if (**argv == '?')
	{
	    fprintf (stderr, "Usage: %s [proxy PROXYHOST PROXYPORT] [port PORT] [cache DIR] [del SECONDS]\n"
		     "[expire SECONDS] [reload SECONDS] [log FILE] [numreq NUMBER]\n"
		     "[unread] [offline] [get]\n"
		     "The cache keyword will change the local directory.\n", PrgName);
	    ExitAll (0);
	}

    while (argc)
    {
	if (strcasecmp (*argv, "proxy") == 0)
	{
	    if (argc < 3)
	    {
		fprintf (stderr, "%s: need two arguments for 'proxy'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 3;
	    ProxyHost = *argv++;
	    if ( (ProxyPort = atoi (*argv++)) <= 0)
	    {
		fprintf (stderr, "%s: Wrong second argument for 'proxy' (need the port number).\n", PrgName);
		ExitAll (1);
	    }
	}
	else if (strcasecmp (*argv, "port") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'port'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    ServerPort = atoi (*argv++);
	}
	else if (strcasecmp (*argv, "cache") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'cache'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    if (chdir (*argv++))
	    {
		fprintf (stderr, "%s: no directory '%s': %s\n", PrgName, argv[-1], strerror (errno));
		ExitAll (1);
	    }
	}
	else if (strcasecmp (*argv, "del") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'del'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    DelCacheTime = atoi (*argv++);
	}
	else if (strcasecmp (*argv, "expire") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'expire'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    ExpireCacheTime = atoi (*argv++);
	}
	else if (strcasecmp (*argv, "reload") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'reload'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    ReloadCacheTime = atoi (*argv++);
	}
	else if (strcasecmp (*argv, "timeout") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'timeout'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    TimeoutTime = atoi (*argv++);
	}
	else if (strcasecmp (*argv, "log") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'log'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    LogFile = *argv++;
	}
	else if (strcasecmp (*argv, "numreq") == 0)
	{
	    if (argc < 2)
	    {
		fprintf (stderr, "%s: need a argument for 'numreq'\n", PrgName);
		ExitAll (1);
	    }
	    argv++;
	    argc -= 2;
	    if ( (MaxRequests = atoi (*argv++)) <= MIN_REQUESTS)
	    {
		fprintf (stderr, "%s: argument 'numreq' needs nummeric value > %d\n", PrgName, MIN_REQUESTS);
		ExitAll (1);
	    }
	    if (MaxRequests * 2 >= Net->FDSize - 4)
	    {
		fprintf (stderr, "%s: argument 'numreq' maximum value of %d exceeded\n", PrgName, (Net->FDSize - 5) / 2);
		ExitAll (1);
	    }
	}
	else if (strcasecmp (*argv, "unread") == 0)
	{
	    argv++;
	    argc--;
	    CacheUnreadRequests = TRUE;
	}
	else if (strcasecmp (*argv, "offline") == 0)
	{
	    argv++;
	    argc--;
	    OffLine = 1;
	}
	else if (strcasecmp (*argv, "get") == 0)
	{
	    argv++;
	    argc--;
	    GetQueued = 1;
	}
	else
	{
	    fprintf (stderr, "%s: unknown option '%s'\n", PrgName, *argv);
	    ExitAll (1);
	}
    }

    assert (Net->FDSize > MaxRequests * 2);

    Init (ProxyHost, ProxyPort, LogFile, FullArgv);

    if (ReadCacheList ())               /* Read cache entries or rebuild them */
	BuildCache ();

	/* Never return */
    for (;;)
    {
	CheckGetQueued (FALSE);
	BuildFdSets ();
	debug (("Select... RequestsFree %d, CachesFree %d\n", RequestsFree, CachesFree));
	if (Net->select (ServServer, CheckTimeouts))
	{
	    fprintf (stderr, "%s: terminating due to signal - saving cachetable\n", PrgName);
	    ShutdownConnects ();
	    if (Net)              /* SaveCacheList () can take some time... */
		Net->exit ();
	    Net = NULL;
	    SaveCacheList ();
	    fprintf (stderr, "done\n");
	    LogSpecial ("terminating due to signal - cachetable saved\n");
	    ExitAll (0);
	}

	DateStamp (&RequestStamp);
	if (CompareDates (&NextCheckStamp, &RequestStamp) > 0)
	    CheckTimeouts ();
	ServRead ();
	ServWrite ();
    }
}
/*)) */

