/* LREADsrv.c
   Program to read Linux extended 2 filesystems under DOS

   HTTP-like Server for the LTOOLS

   When you have this program running, you can use any web-browser as a user interface for the LTOOLS.

   Please note:
   Under DOS/Windows this program must be compiled as a 32bit program, edit 'makefile' appropriately
   and do a 'make'.
   Under UNIX edit 'makefile.unx' and do a 'make -fmakefile.unx'.


   Copyright (C) 1999-2006 Werner Zimmermann(Werner.Zimmermann@fht-esslingen.de)

   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, 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.

   For further information please see file README.TXT.

   ---History, Changes:-----------------------------------------------------------
   V1.0: First release for public alpha test.
   W.Zimmermann, 15 March 1999
   V1.1: Port to LINUX, should run on any UNIX like operating system
   W.Zimmermann, 01 April 1999
   V1.2: LDIR calls modified to '-x' instead of '>zzz.zwz' for output redirection.
   Does not work with versions prior to LDIR V3.4.
   W.Zimmermann, 15 April 1999
   V1.3: Comments update
   W.Zimmermann, 28 Mai 1999
   V1.4: New set of icons by Peter Santo, RWTH Aachen. Thanks Peter!
   Rename files as of LTOOLs V4.0 implemented.
   W.Zimmermann, 2 Dec 1999
   V1.5: Create directories and symlinks. Requires LTOOLS V4.2 or newer.
   W.Zimmermann, 15 Dec 1999
   V1.6: Make it compatible to Microsoft Visual C++
   W.Zimmermann, 26 Mar 2000
   V1.7: Bugfixes for findfirst/findnext with Microsoft Visual C++ and
   some minor fixes (memset, type of currentIP, spelling/formatting)
   M.Drechsler, 17 Aug 2000
   V1.8: Added browsing of DOS filenames with spaces (writeability still beta !),
   some useful HTML formatting subfunctions and some minor enhancements (status codes etc.)
   M.Drechsler, 29 Aug 2000
   V2.0: Improved handling of filenames with spaces. Initiated by Alan Borgolotto.
   W. Zimmermann, 04 Apr 2003
   V2.1: Prepare lreadsrv to run as a Windows NT service, started by 
   	loaddrv -instService lreadsrv.exe lreadsrv
   	loaddrv -start lreadsrv
   W. Zimmermann, 20 Dec 2003
 */

#ifdef _MSC_VER							//for Microsoft Visual C++
#pragma warning(disable: 4047 4068)
#endif

// ********************************************************************************************
#define VERSION  "V2.1"
// ********************************************************************************************

#include <stdio.h>
#include <string.h>
#ifndef UNIX
#include <windows.h>
#include <winsock.h>
#include <conio.h>
#ifdef _MSC_VER
#include <io.h>
#else
#include <dir.h>
#endif
#include <dos.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <dirent.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>

//Redefinitions to make LREADsrv work under UNIX operating systems---------------------------------------
#ifdef UNIX
#define SOCKET                int
#define WORD                  unsigned short
#define DWORD                 unsigned int
#define BOOL                  char
#define BOOLEAN		      char
#define SOCKADDR_IN           struct sockaddr_in
#define FALSE                 0
#define TRUE                  1
#define SOCKET_ERROR          -1
#define INVALID_SOCKET        -1
#define closesocket           close
int WSACleanup(void)
{   return 0;
}
int WSAGetLastError(void)
{   return 0;
}

char *strlwr(char *string)
{   int i;
    static char temp[256];

    strcpy(temp, string);

    for (i = 0; i < strlen(temp); i++)
    {   temp[i] = (char) tolower(temp[i]);
    }
    return temp;
}

char *strupr(char *string)
{   int i;
    static char temp[256];

    strcpy(temp, string);

    for (i = 0; i < strlen(temp); i++)
    {   temp[i] = (char) toupper(temp[i]);
    }
    return temp;
}

void ltoa(long input, char *output, int base)
{   char temp[128];
    if (base == 10)
	sprintf(temp, "%ld", input);
    else if (base == 16)
	sprintf(temp, "%lX", input);
    strcpy(output, temp);
}

void itoa(int input, char *output, int base)
{   char temp[128];
    if (base == 10)
	sprintf(temp, "%d", input);
    else if (base == 16)
	sprintf(temp, "%X", input);
    strcpy(output, temp);
}

void GetFileName(char *fullPathName, char **fileName)
{   char *p;
    p = strrchr(fullPathName, '/');
    *fileName = p + 1;
}

struct ffblk
{   long ff_reserved;
    long ff_fsize;						// file size
    unsigned long ff_attrib;					// attribute found
    unsigned short ff_ftime;					// file time
    unsigned short ff_fdate;					// file date
    char ff_name[256];						// found file name
};

#define FA_NORMAL   0x00					// Normal file, no attributes
#define FA_RDONLY   0x01					// Read only attribute
#define FA_HIDDEN   0x02					// Hidden file
#define FA_SYSTEM   0x04					// System file
#define FA_LABEL    0x08					// Volume label
#define FA_DIREC    0x10					// Directory
#define FA_ARCH     0x20					// Archive
#define FA_LINK     0x40					// Link ???
#define FA_DEV      0x80					// Device file ???

int findfirst(const char *fullPathName, struct ffblk *ffinfo, int attrib)
{   static char dirName[256];
    char tempName[256], *p;
    struct stat statBuf;
    static struct DIR *dir;
    struct dirent *ent;

    if (fullPathName != NULL)
    {   strcpy(dirName, fullPathName);
	p = strrchr(dirName, '/');
	*(p + 1) = 0;

	if ((dir = (struct DIR *) opendir(dirName)) == NULL)
        {   return -1;
	}
    }
    if ((ent = readdir((DIR *) dir)) == NULL)
    {   closedir((DIR *) dir);
	return -1;
    }
    strcpy(tempName, dirName);
    strcat(tempName, ent->d_name);

    stat(tempName, &statBuf);

    ffinfo->ff_fsize = statBuf.st_size;
    ffinfo->ff_attrib = FA_NORMAL;
    if (S_ISDIR(statBuf.st_mode))
	ffinfo->ff_attrib = ffinfo->ff_attrib | FA_DIREC;
    if (S_ISLNK(statBuf.st_mode))
	ffinfo->ff_attrib = ffinfo->ff_attrib | FA_LINK;
    if (S_ISBLK(statBuf.st_mode))
	ffinfo->ff_attrib = ffinfo->ff_attrib | FA_DEV;
    if (S_ISCHR(statBuf.st_mode))
	ffinfo->ff_attrib = ffinfo->ff_attrib | FA_DEV;
    if (S_ISREG(statBuf.st_mode))
	ffinfo->ff_attrib = ffinfo->ff_attrib | FA_NORMAL;
    ffinfo->ff_ftime = statBuf.st_ctime & 0xFFFF;
    ffinfo->ff_fdate = (statBuf.st_ctime >> 16) & 0xFFFF;;
    strcpy(ffinfo->ff_name, ent->d_name);

    return 0;
}
#endif
/// End of redefinitions to make LREADsrv work under UNIX operating systems------------------------

//Redefinitions to make LREADsrv compile with Microsoft Visual C++---------------------------------
#ifdef _MSC_VER
#include <time.h>

long handle;

struct ffblk
{   long ff_reserved;
    long ff_fsize;						// file size
    unsigned long ff_attrib;					// attribute found
    unsigned short ff_ftime;					// file time
    unsigned short ff_fdate;					// file date
    char ff_name[256];						// found file name
};

#define FA_NORMAL   0x00					// Normal file, no attributes
#define FA_RDONLY   0x01					// Read only attribute
#define FA_HIDDEN   0x02					// Hidden file
#define FA_SYSTEM   0x04					// System file
#define FA_DIREC    0x10					// Directory
#define FA_ARCH     0x20					// Archive

int findfirst(const char *pathname, struct ffblk *ffblk, int attrib)
{   struct _finddata_t fileinfo;
    struct tm *filetime;
    fileinfo.attrib = ffblk->ff_attrib;

    handle = _findfirst(pathname, &fileinfo);
    if (handle == -1)
	return -1;						//fail

    strcpy(ffblk->ff_name, fileinfo.name);
    ffblk->ff_attrib = fileinfo.attrib;
    ffblk->ff_fsize = fileinfo.size;
    filetime = localtime(&fileinfo.time_write);
    ffblk->ff_fdate = ((filetime->tm_year - 80) << 9) | ((filetime->tm_mon + 1) << 5) | (filetime->tm_mday);
    ffblk->ff_ftime = (filetime->tm_hour << 11) | (filetime->tm_min << 5) | (filetime->tm_sec / 2);

    return 0;							//success
}
int findnext(struct ffblk *ffblk)
{   struct _finddata_t fileinfo;
    struct tm *filetime;
    int val;

    fileinfo.attrib = ffblk->ff_attrib;
    strcpy(fileinfo.name, ffblk->ff_name);

    val = _findnext(handle, &fileinfo);
    // if there is no next entry, leave here
    if (val == -1)
	return -1;						//fail

    strcpy(ffblk->ff_name, fileinfo.name);
    ffblk->ff_attrib = fileinfo.attrib;
    ffblk->ff_fsize = fileinfo.size;
    filetime = localtime(&fileinfo.time_write);
    ffblk->ff_fdate = ((filetime->tm_year - 80) << 9) | ((filetime->tm_mon + 1) << 5) | (filetime->tm_mday);
    ffblk->ff_ftime = (filetime->tm_hour << 11) | (filetime->tm_min << 5) | (filetime->tm_sec / 2);
    return val;
}
#endif
/// End of redefinitions to make LREADsrv compile with Microsoft Visual C++------------------------

#define BUFSIZE         32768
#define CLIENTMAX           4

//--------------------------------------------------------------------------------------------------
#if 0								//set to 1, if you do want all these debugging messages
#define DEBUG         printf
#else
#define DEBUG         dummy
#pragma argsused
int dummy(const char *format,...)
{   return 0;
}
#endif
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
//Macro to send ASCII data (zero terminated strings)
//--------------------------------------------------------------------------------------------------
// ********************************************************************************************
#define SEND(daten)     memset(sendBuffer,0,sizeof(sendBuffer));                                        \
			strcpy(sendBuffer,daten);                                                        \
			DEBUG("### Send: %s\n",daten);							\
			if (send(sd_current, (char *) sendBuffer, strlen(sendBuffer),0) == SOCKET_ERROR)\
			{ WSAGetLastError(); perror("srv:send");                                         \
			}

//Macro to send binary data
#define SENDDATA(data,length)                                                                            \
			memset(sendBuffer,0,length);                                                    \
			memcpy(sendBuffer,data,length);                                                  \
			if (send(sd_current, (char *) sendBuffer,length,0) == SOCKET_ERROR)             \
			{ WSAGetLastError(); perror("srv:send");                                         \
			}

//Macro to receive ASCII data
#define RECEIVE         memset(readBuffer,0,sizeof(readBuffer));                                        \
			if ((i=recv(sd_current, (char *) readBuffer, sizeof(readBuffer),0))==SOCKET_ERROR)\
			{ fprintf(stderr,"Error=%u\n",WSAGetLastError());perror("srv:recv"); break;      \
			}                                                                                \
			DEBUG("received command: >>>%s",readBuffer);

//Macro to receive binary data
#define RECEIVEDATA     memset(readBuffer,0,sizeof(readBuffer));                                        \
			if ((i=recv(cd, (char *) readBuffer, sizeof(readBuffer),0)) == SOCKET_ERROR)    \
			{ fprintf(stderr,"Error=%u\n",WSAGetLastError());perror("srv:recv"); break;      \
			}
// ********************************************************************************************

//--------------------------------------------------------------------------------------------------
//Winsock global variables
//--------------------------------------------------------------------------------------------------
SOCKET sd = 0, sd_current = 0;
SOCKADDR_IN Sin, Pin;
int addrlen;
WORD wVersionRequested;
#ifndef UNIX
WSADATA wsaData;
#endif
unsigned short serverPort = 80;
char strServerPort[32] = "80";
unsigned short clientDataPort;
unsigned long registeredClientIP[CLIENTMAX];
BOOL connectionAllowed = FALSE;
struct solinger
{   int l_onoff;
    int l_linger;
}
linger;


//--------------------------------------------------------------------------------------------------
//Other global variables
//--------------------------------------------------------------------------------------------------
FILE *fd = NULL;

char sendBuffer[BUFSIZE], readBuffer[BUFSIZE], currentLINdirectory[256] = "/", currentLINfile[256] = "", currentDOSdirectory[256] = "/",
 currentDOSfile[256] = "", tempBuffer[BUFSIZE], line[BUFSIZE], tempLine[BUFSIZE], *p;
char value[4] = "1";
char currentLINdrive[16] = "/dev/hda";
#ifndef UNIX
char currentDOSdrive[16] = "c:";
#else
char currentDOSdrive[16] = "";
#endif
BOOL LINmode = TRUE;

#ifndef UNIX
//##################################################################################################
//Control-Break-Handler
//As the server runs in an infinite loop, it can only be stopped by pressing the Control-Break keys.
//This routine allows a 'graceful' shutdown.
//--------------------------------------------------------------------------------------------------
BOOL CtrlBreakHandler(DWORD dwCtrlType)
{   switch (dwCtrlType)
    {   case CTRL_C_EVENT:
	case CTRL_BREAK_EVENT:
	case CTRL_CLOSE_EVENT:
	case CTRL_SHUTDOWN_EVENT:
	    if (fd != NULL)
		fclose(fd);
	    if (sd != NULL)
		closesocket(sd);
	    if (sd_current != NULL)
		closesocket(sd_current);
	    WSACleanup();
	    printf("\n####### Exit LTOOLS HTTP Server ########\n");
	    exit(0);
	default:
	    return FALSE;
    }
}
#endif

//##################################################################################################
//Substitute HTML %xx escape codes (URI) by ASCII character
//--------------------------------------------------------------------------------------------------
void decodeURI(char *p, BOOLEAN plus, BOOLEAN substPathDelimiter)
{   int i, j, N=strlen(p);
    unsigned long k;
    char c[3];
    
    DEBUG("IN:  decodeURI=%s   >>>%u %u\n", p, plus, substPathDelimiter);
    for (i=0, j=0; i < N; i++)
    {	if (p[i]=='%')						//If it is a '%', copy a ' ' and skip the next to characters
    	{   c[0]=p[i+1];
    	    c[1]=p[i+2];
    	    c[2]=0;
    	    k=strtol(c, NULL, 16);
    	    sprintf((char*) &p[j],"%c",k);			//If it is not '%' copy input character to output
//    	    DEBUG("is: i=%2u j=%2u p=%s---%c---%c---%X\n",i, j, p, p[i], p[j], k);
    	    i=i+2;   	
    	} else
    	{   p[j]=p[i];
//    	    DEBUG("no: i=%2u j=%2u p=%s---%c---%c\n",i, j, p, p[i], p[j]);
    	    if (plus && (p[j]== '+'))				//Substitute '+' by ' '
    	    {   p[j]=' ';
//    	        DEBUG("plus: i=%2u j=%2u p=%s---%c---%c\n",i, j, p, p[i], p[j]);
    	    }    	
    	}
//    	DEBUG("---j=%u  p[j]=%c   i=%u  p[i]=%c\n",j, p[j], i, p[i]);
    	if (substPathDelimiter && (p[j]== '\\'))		//Substitute '\' by '/'
    	{   p[j]='/';
//    	    DEBUG("plus: i=%2u j=%2u p=%s---%c---%c\n",i, j, p, p[i], p[j]);
    	}    	
        j++;
    }
    p[j]=0;
    DEBUG("OUT: decodeURI=%s\n", p);
}

//##################################################################################################
//Search a string for all occurences of token and replace by substitute, note token and substitute may
//be of different length, string must be long enough to hold the result
//--------------------------------------------------------------------------------------------------
void strxchg(char *string, char *token, char *substitute)
{   char temp[256], result[256] = "", *next, *last;

    memset(result, 0, sizeof(result));
    strcpy(temp, string);					//local copy of string
    last = temp;						//last points to first character of local copy

    while (1)
    {   next = strstr(last, token);				//next no points to the next occurence of the token
	if (next == NULL)
	    break;						//if empty, no more tokens in string
	strncat(result, last, next - last);			//append string to beginning of the token
	strcat(result, substitute);				//append substitute
	last = next + strlen(token);
	if (*last == 0)
	    break;						//don't parse after the end
    }
    if (*last != 0)
	strcat(result, last);					//append the rest of our string
    strcpy(string, result);					//copy back result
}

//##################################################################################################
//Search a string for all occurences of space and replace by %20, note spaces are unsafe and substitute
//may be of different length, string must be long enough to hold the result
//--------------------------------------------------------------------------------------------------
void strxspc(char *string)
{   char result[256], *src, *dst;

    memset(result, 0, sizeof(result));
    src = string;
    dst = result;

    while (*src)
    {   if (*src == ' ')
        {   strcat(result, "%20");
	    dst += 3;
	} else
        {   *dst = *src;
	    dst++;
	}
	src++;
    }
    strcpy(string, result);					//copy back result
}

//##################################################################################################
//Extract a variable's value from a string, the variable's name is given by startToken; endToken is a
//delimiter at the end of the variables value.
//--If startToken and endToken are found :      strExtract = +1      value = variable contents
//--If the startToken is not found, value:      strExtract =  0      value = ""
//--If startToken found, but endToken not found:strExtract = -1      value = 'rest' of string
//
//strExtract may be called with input=NULL, to find multiple occurrences of the variable.
//Note: Do only call strExtract with input=NULL, if a previous call returned +1 !
//--------------------------------------------------------------------------------------------------
int strExtract(char *input, char *startToken, char *endToken, char *value)
{   char *start, *end;
    static char *inputTemp = NULL;

    if (input != NULL)
	inputTemp = input;

    if ((start = strstr(input, startToken)) == NULL)		//startToken not found
    {   strcpy(value, "");
	return 0;
    }
    if ((end = strstr(start + strlen(startToken), endToken)) == NULL)	//endToken not found
    {   strcpy(value, start + strlen(startToken));
	return -1;
    }
    memcpy(value, start + strlen(startToken), end - start - strlen(startToken));	//startToken and endToken found
    value[end - start - strlen(startToken)] = 0;
    inputTemp = end + 1;
    return +1;
}

//##################################################################################################
//start an HTML context
//  initially start creating a HTTP response with the apropriate state information
//
//  old data in global tempBuffer will be overwritten
//--------------------------------------------------------------------------------------------------
void openHTTPResponse(int state)
{								/*
								   RFC1945 - Status Code and Reason Phrase

								   o 1xx: Informational - Not used, but reserved for future use

								   o 2xx: Success - The action was successfully received, understood, and accepted.

								   o 3xx: Redirection - Further action must be taken in order to complete the request

								   o 4xx: Client Error - The request contains bad syntax or cannot be fulfilled

								   o 5xx: Server Error - The server failed to fulfill an apparently valid request

								 */

    // clear tempBuffer here
    memset(tempBuffer, 0, sizeof(tempBuffer));

    // use Version 1.0
    strcpy(tempBuffer, "HTTP/1.0 ");

    switch (state)
    {   case 200:
	    strcat(tempBuffer, "200 Ok\n");
	    break;
	case 201:
	    strcat(tempBuffer, "201 Created\n");
	    break;
	case 202:
	    strcat(tempBuffer, "202 Accepted\n");
	    break;
	case 204:
	    strcat(tempBuffer, "204 No Content\n");
	    break;
	case 301:
	    strcat(tempBuffer, "301 Moved Permanently\n");
	    break;
	case 302:
	    strcat(tempBuffer, "302 Moved Temporarily\n");
	    break;
	case 304:
	    strcat(tempBuffer, "304 Not Modified\n");
	    break;
	case 400:
	    strcat(tempBuffer, "400 Bad Request\n");
	    break;
	case 401:
	    strcat(tempBuffer, "401 Unauthorized\n");
	    break;
	case 403:
	    strcat(tempBuffer, "403 Forbidden\n");
	    break;
	case 404:
	    strcat(tempBuffer, "404 Not Found\n");
	    break;
	case 500:
	    strcat(tempBuffer, "500 Internal Server Error\n");
	    break;
	case 501:
	    strcat(tempBuffer, "501 Not Implemented\n");
	    break;
	case 502:
	    strcat(tempBuffer, "502 Bad Gateway\n");
	    break;
	case 503:
	    strcat(tempBuffer, "503 Service Unavailable\n");
	    break;
	default:
	    strcat(tempBuffer, "400 Bad Request\n");
	    break;
    }
    // always use this one
    strcat(tempBuffer, "Server: LTOOLS HTTP Server (C) 1999-2006 Werner Zimmermann\n");
}

//##################################################################################################
//concatenate an HTML title/body-start
//--------------------------------------------------------------------------------------------------
void catHTTPStart(const char *lpp)
{   strcat(tempBuffer, "Content-Type: text/html\n\n");

    strcat(tempBuffer, "<HTML><HEAD>\n");
    strcat(tempBuffer, "<meta name=\"GENERATOR\" content=\"Lreadsrv\">\n");
    strcat(tempBuffer, "<meta name=\"Description\" content=\"LTOOLS HTTP Server\">\n");
    strcat(tempBuffer, "<meta name=\"Copyright\" content=\"(C) 1999-2006 Werner Zimmermann\">\n");
    strcat(tempBuffer, "<meta name=\"Author\" content=\"Werner Zimmermann\">\n");
    strcat(tempBuffer, "<TITLE>");
    strcat(tempBuffer, lpp);
    strcat(tempBuffer, "</TITLE></HEAD>\n<BODY>\n");
}

//##################################################################################################
//concatenate an HTML body-end
//--------------------------------------------------------------------------------------------------
void catHTTPEnd(void)
{   strcat(tempBuffer, "</BODY><HTML>\n");
}

//##################################################################################################
//concatenate an HTML topline for DOS mode
//--------------------------------------------------------------------------------------------------
void catHTTPTopDOS(const char *lpp)
{   strcat(tempBuffer, "<TABLE BORDER=\"0\" WIDTH=\"100%\">\n<TR>\n<TD WIDTH=\"68%\"><FONT size=\"+1\"><B>");
#ifndef UNIX
    strcat(tempBuffer, "LTOOLS DOS Directory   ");
#else
    strcat(tempBuffer, "LTOOLS UNIX Directory   ");
#endif
    strcat(tempBuffer, lpp);
    strcat(tempBuffer, "</B></FONT></TD>\n");
    strcat(tempBuffer, "<TD WIDTH=\"8%\"><A HREF=\"/zzzmod?LIN\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzlin.gif\" ALT=\"LINUX\"></A></TD>\n \
				        <TD WIDTH=\"8%\"><A HREF=\"/zzzdrv.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzdrv.gif\" ALT=\"DRIVE\"></A></TD>\n \
				        <TD WIDTH=\"8%\">&nbsp;</TD>\n \
				        <TD WIDTH=\"8%\"><A HREF=\"/zzzhlp.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzhlp.gif\" ALT=\"HELP\"></A></TD>\n");
    //                     <TD WIDTH=\"8%\"><A HREF=\"/zzzwri.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzwri.gif\" ALT=\"WRITE\"></A></TD>\n
    strcat(tempBuffer, "</TR>\n</TABLE>\n<HR>\n<PRE>\n");
}

//##################################################################################################
//concatenate an HTML topline for Linux mode
//--------------------------------------------------------------------------------------------------
void catHTTPTopLIN(const char *lpp)
{   strcat(tempBuffer, "<TABLE BORDER=\"0\" WIDTH=\"100%\">\n<TR>\n<TD WIDTH=\"68%\"><FONT size=\"+1\"><B>");
    strcat(tempBuffer, "LTOOLS LINUX Directory   ");
    strcat(tempBuffer, lpp);
    strcat(tempBuffer, "</B></FONT></TD>\n");
    strcat(tempBuffer, "<TD WIDTH=\"8%\"><A HREF=\"/zzzmod?DOS\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzdos.gif\" ALT=\"DOS\"></A></TD>\n \
				        <TD WIDTH=\"8%\"><A HREF=\"/zzzdrv.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzdrv.gif\" ALT=\"DRIVE\"></A></TD>\n \
				        <TD WIDTH=\"8%\"><A HREF=\"/zzzwri.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzwri.gif\" ALT=\"WRITE\"></A></TD>\n \
				        <TD WIDTH=\"8%\"><A HREF=\"/zzzhlp.htm\"><IMG ALIGN=\"bottom\" BORDER=\"0\" SRC=\"/zzzhlp.gif\" ALT=\"HELP\"></A></TD>\n");
    strcat(tempBuffer, "</TR>\n</TABLE>\n<HR>\n<PRE>\n");
}

//##################################################################################################
//concatenate an HTML content type
//--------------------------------------------------------------------------------------------------
void catHTTPContentType(const char *lpp, long sz)
{   char *startVar;

    // don't strlwr() parts of the file, all or nothing !!!
    // so at the moment strlwr() only a local copy here !!!
    char tempFile[256];						// use common size (define) later ?
    char *startExt = tempFile;

    strcat(tempBuffer, "Content-Type: ");			//  for all

    //Find out Content-Type and place it in buffer
    startVar = strrchr(lpp, '.');				//search for file extension to find contents type
    if (startVar == NULL)
    {   strcat(tempBuffer, "application/octet-stream\n");	//  none
    } else
    {   strcpy(startExt, startVar);
	strlwr(startExt);

	if (strstr(startExt, ".txt"))
        {   strcat(tempBuffer, "text/plain\n");			//'.txt'
	} else if (strstr(startExt, ".rtf"))
        {   strcat(tempBuffer, "text/rtf\n");			//'.rtf'
	} else if (strstr(startExt, ".htm"))
        {   strcat(tempBuffer, "text/html\n");			//'.htm'
	} else if (strstr(startExt, ".pdf"))
        {   strcat(tempBuffer, "application/pdf\n");		//'.pdf'
	} else if (strstr(startExt, ".zip"))
        {   strcat(tempBuffer, "application/x-compressed\n");	//'.zip'
	} else if (strstr(startExt, ".tar"))
        {   strcat(tempBuffer, "application/x-compressed\n");	//'.tar'
	} else if (strstr(startExt, ".tgz"))
        {   strcat(tempBuffer, "application/x-compressed\n");	//'.tgz'
	} else if (strstr(startExt, ".doc"))
        {   strcat(tempBuffer, "application/msword\n");		//'.doc'
	} else if (strstr(startExt, ".jpg"))
        {   strcat(tempBuffer, "image/jpeg\n");			//'.jpg'
	} else if (strstr(startExt, ".jpeg"))
        {   strcat(tempBuffer, "image/jpeg\n");			//'.jpeg'
	} else if (strstr(startExt, ".gif"))
        {   strcat(tempBuffer, "image/gif\n");			//'.gif'
	} else if (strstr(startExt, ".tif"))
        {   strcat(tempBuffer, "image/tiff\n");			//'.tif'
	} else
        {   strcat(tempBuffer, "application/octet-stream\n");	//unknown
	}
    }
    ltoa(sz, tempLine, 10);
    strcat(tempBuffer, "Content-Length: ");
    strcat(tempBuffer, tempLine);				//append info about the file's length

    strcat(tempBuffer, "\n\n");
}

//##################################################################################################
//print out a startup message
//--------------------------------------------------------------------------------------------------
void printStartup(void)
{								//############ Startup message #################################################
    printf("\n******************************************************************************\n");
    printf("LTOOLS HTTP Server version %s \n(C) 1999-2006 Werner.Zimmermann@fht-esslingen.de\n", VERSION);
    printf("******************************************************************************\n");
    printf("List, read and write files on Linux Extended 2 filesystems\n\n");
    printf("usage: lreadsrv [<portnumber> [<registered Client IP> ...]]\n");
    printf("       * default value for the server's TCP/IP portnumber is 80\n");
    printf("       * use any web browser program as a frontend for the LTOOLS\n");
    printf("       * connect your browser to host 'localhost' or 'localhost:<portnumber>\n");
    printf("       * due to safety reasons, we do only accept connections from 'localhost'\n");
    printf("         or from one of max. 3 client IPs specified as <registered Client IP>\n");
    printf("       * client access is granted based on IP, there is no password check\n");
    printf("       * please set environment variable LDRIVE (default Linux drive)\n");
    printf("\n");
}

//##################################################################################################
//Send an HTML error message
//--------------------------------------------------------------------------------------------------
void showErrorMessage(int state, const char *text)
{   DEBUG("***showErrorMessage***\n");
    openHTTPResponse(state);
    catHTTPStart("LTOOLS HTTP Server");
    strcat(tempBuffer, "<H2>LTOOLS</H2>");
    strcat(tempBuffer, "<HR><PRE><B>");
    strcat(tempBuffer, text);
    strcat(tempBuffer, "</B></PRE><HR><BR><A HREF=\"./\"><B>GO BACK</B></A>");
    catHTTPEnd();
    SEND(tempBuffer);
}

//##################################################################################################
//Scan a directory for entries, translate the result dynamically to HTML and send it back to the web browser
//references to directories are marked with '\', normal files are not marked. In later requests this
//mark is used to decide how to provide the information
//--------------------------------------------------------------------------------------------------
void showDOSdirectoryListing(void)
{   int i, done;
    char *q;
    struct ffblk fileInfo;
    unsigned long c_time;

    DEBUG("***showDOSdirectoryListing***\n");
    strcpy(currentDOSdirectory, p);
    decodeURI(currentDOSdirectory, FALSE, TRUE);

//DEBUG("### showDOSdirectory:\np=%s\ncurrentDOSdirectory=%s\ncurrentDOSfile=%s\ncurrentDOSdrive=%s\n",
    //                                                      p,currentDOSdirectory,currentDOSfile,currentDOSdrive);

    strcpy(tempBuffer, currentDOSdrive);
    strcat(tempBuffer, currentDOSdirectory);
    strcat(tempBuffer, "*.*");					//full pathname for directory scan

#ifndef UNIX
    strxchg(tempBuffer, "/", "\\");				//change '/' to '\' for DOS access
#endif
    done = findfirst(tempBuffer, &fileInfo, FA_NORMAL | FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC);	//scan DOS directory
    // a single "done" var implies to be true or false, not negative
    if (done != 0)
    {   showErrorMessage(404, "Could not find directory");
	return;
    }
    openHTTPResponse(200);					// no error
    catHTTPStart("LTOOLS HTTP Server");

    // minimize differences for UNIX/Other, additional nicer formatting for HTML code
    catHTTPTopDOS(currentDOSdirectory);

    SEND(tempBuffer);
    memset(tempBuffer, 0, sizeof(tempBuffer));

//format of our file entries
    //strcpy(tempBuffer,"<A HREF=\"/INSTALL.LOG\"><IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-text\"> INSTALL.LOG</A>   908 bytes Wed May 20 18:29:44 1998\n");

    // a single "done" var implies to be true or false, not negative
    while (done == 0)						//read from file and send over data channel
    {   strcpy(tempBuffer, "<A HREF=\"");

	if (!strcmp(fileInfo.ff_name, ".."))			//if filename is '..', adjust the name to reflect the parent directory
        {   strcpy(line, currentDOSdirectory);
	    q = strrchr(line, '/');
	    if (q != NULL)
		*q = 0;
	    q = strrchr(line, '/');
	    if (q != NULL)
		*q = 0;
	    strxspc(line);
	    strcat(tempBuffer, line);
	} else
        {   strcpy(line, currentDOSdirectory);			//to all other files add current directory
	    strcat(line, fileInfo.ff_name);
	    strxspc(line);
	    strcat(tempBuffer, line);				//... and file name
	}

	if (!strcmp(fileInfo.ff_name, "."))			//skip, if filename is ".",i.e. current directory
        {
#ifndef UNIX
	    done = findnext(&fileInfo);
#else
	    done = findfirst(NULL, &fileInfo, 0);
#endif
	    continue;
	}
	if (fileInfo.ff_attrib & FA_DIREC)
        {   strcat(tempBuffer, "/");
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"");
	    strcat(tempBuffer, "/zzzdir.gif\" ALT=\"DIRECTORY\" > ");
#ifdef UNIX
	} else if (fileInfo.ff_attrib & FA_LINK)
        {   strcat(tempBuffer, "@");
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"");
	    strcat(tempBuffer, "/zzzlink.gif\" ALT=\"LINK\"> ");

	} else if (fileInfo.ff_attrib & FA_DEV)
        {   strcat(tempBuffer, ">");
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"");
	    strcat(tempBuffer, "/zzzdev.gif\" ALT=\"DEVICE\"> ");
#endif
	} else
        {   strcat(tempBuffer, " ");
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"");
	    strcat(tempBuffer, "/zzzfile.gif\" ALT=\"FILE\"> ");
	}

	strcat(tempBuffer, fileInfo.ff_name);			//file reference text
	strcat(tempBuffer, "</A>  ");

	for (i = strlen(fileInfo.ff_name); i < 40; i++)
	    strcat(tempBuffer, " ");				//align columns

	ltoa(fileInfo.ff_fsize, line, 10);			//file size
	for (i = strlen(line); i < 10; i++)
	    strcat(tempBuffer, " ");				//align columns
	strcat(tempBuffer, line);
	strcat(tempBuffer, "    ");
#ifndef UNIX
	itoa(fileInfo.ff_fdate & 0x001F, line, 10);		//file date - day
	if (strlen(line) == 1)
	    strcat(tempBuffer, " ");
	strcat(tempBuffer, line);
	strcat(tempBuffer, ".");
	itoa((fileInfo.ff_fdate & 0x01E0) >> 5, line, 10);	//file date - month
	if (strlen(line) == 1)
	    strcat(tempBuffer, " ");
	strcat(tempBuffer, line);
	strcat(tempBuffer, ".");
	itoa(1980 + ((fileInfo.ff_fdate & 0xFE00) >> 9), line, 10);	//file date - year
	strcat(tempBuffer, line);
	strcat(tempBuffer, "    ");
#else
	c_time = (((long) fileInfo.ff_fdate) << 16) + fileInfo.ff_ftime;
	strcpy(line, (char *) ctime(&c_time));
	strtok(line, "\n");
	strcat(tempBuffer, line);
	strcat(tempBuffer, " ");
#endif

	if (!(fileInfo.ff_attrib & FA_DIREC))
        {   strcat(tempBuffer, "<A HREF=\"/zzzcgi?");
	    strcpy(line, currentDOSdirectory);
	    strcat(line, fileInfo.ff_name);
	    strxspc(line);
	    strcat(tempBuffer, line);				//... and file name
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"/zzzsel.gif\" ALT=\"SELECT\"></A>");
	}
	strcat(tempBuffer, "\n");
	SEND(tempBuffer);

#ifndef UNIX
	done = findnext(&fileInfo);
#else
	done = findfirst(NULL, &fileInfo, 0);
#endif

    };
#ifdef _MSC_VER
    _findclose(handle);
#endif

    strcpy(tempBuffer, "</PRE></BODY><HTML>\n");
    SEND(tempBuffer);
}

//##################################################################################################
//Execute LDIR, save result as file, translate it dynamically to HTML and send it back to the web browser
//references to directories are marked with '/', reference to links with '@', references to fifos etc. with '>'
//normal files are not marked. In later requests this mark is used to decide how to provide the information
//--------------------------------------------------------------------------------------------------
void showLINdirectoryListing(void)
{   int i;

    DEBUG("***showLINdirectoryListing***\n");
    decodeURI(p, FALSE, FALSE);
    strcpy(currentLINdirectory, p);

//DEBUG("### showLINdirectory:\np=%s\ncurrentLINdirectory=%s\ncurrentLINfile=%s\ncurrentLINdrive=%s\n",
    //                                                      p,currentLINdirectory,currentLINfile,currentLINdrive);

#ifdef DRECHSLER						// Security: no current path for root, so prepend it here
    strcpy(tempBuffer, "./ldir -x -s");
#ifndef UNIX
    strxchg(tempBuffer, "/", "\\");				//change '/' to '\' for DOS access
#endif
#else
    strcpy(tempBuffer, "ldir -x -s");
#endif
    strcat(tempBuffer, currentLINdrive);
    strcat(tempBuffer, " \"");
    strcat(tempBuffer, currentLINdirectory);
    strcat(tempBuffer, "\"");
    /*strcat(tempBuffer, " >zzz.zwz");                            //execute LDIR, results go into file ZZZ.ZWZ */
#ifndef UNIX
    system("IF EXIST ZZZ.ZWZ DEL ZZZ.ZWZ");
#else
    system("rm -f zzz.zwz");
#endif
    DEBUG("executing: %s\n", tempBuffer);
    system(tempBuffer);

    // no more buffer cleaning (now in subfunctions)
    if ((fd = fopen("zzz.zwz", "r+")) == NULL)			//open file ZZZ.ZWZ
    {   printf("srv: could not open result file\n");
	showErrorMessage(404, "LTOOLS error: Linux path invalid");
	return;
    }
    openHTTPResponse(200);					// no error
    catHTTPStart("LTOOLS HTTP Server");

    // minimize differences for UNIX/Other, additional nicer formatting for HTML code
    catHTTPTopLIN(p);

    SEND(tempBuffer);

    memset(tempBuffer, 0, sizeof(tempBuffer));

    while (!feof(fd))						//read from file and send over data channel
    {   memset(&line, 0, sizeof(line));
	if (fgets(line, BUFSIZE, fd) == NULL)
	    break;
	strtok(line, "\n\r");

	strcpy(tempBuffer, "<A HREF=\"");
	p = line + 56;						//in LREAD's output the file name starts at column 56
DEBUG(">>>File:%s<<<\n", p);
	strxspc(p);
	strcpy(tempLine, p);
	strtok(tempLine, "\n\r");
	strcat(tempBuffer, currentLINdirectory);		//always add current directory  ???
	strcat(tempBuffer, tempLine);				//file reference
	if (!strcmp(tempLine, "."))
	    continue;						//skip, if filename is ".",i.e. current directory
	switch (line[0])
        {   case 'd':
		strcat(tempBuffer, "/");
		break;
	    case 'l':
		strcat(tempBuffer, "@");
		break;
	    case 'b':
		strcat(tempBuffer, ">");
		break;
	    case 'c':
		strcat(tempBuffer, ">");
		break;
	    case 'p':
		strcat(tempBuffer, ">");
		break;
	    default:
		strcat(tempBuffer, " ");
		break;
	}
	strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"");
	switch (line[0])
        {   case 'd':
		strcat(tempBuffer, "/zzzldir.gif\" ALT=\"DIRECTORY\"> ");
		break;
	    case 'l':
		strcat(tempBuffer, "/zzzlink.gif\" ALT=\"LINK\"> ");
		break;
	    case 'b':
	    case 'c':
	    case 'p':
		strcat(tempBuffer, "/zzzdev.gif\" ALT=\"DEVICE\"> ");
		break;
	    default:
		strcat(tempBuffer, "/zzzfile.gif\" ALT=\"FILE\"> ");
		break;
	}
	decodeURI(p, FALSE, FALSE);
	strcat(tempBuffer, p);					//file reference text
	strcat(tempBuffer, "</A>  ");
	for (i = strlen(line); i < 90; i++)
        {   strcat(tempBuffer, " ");				//align columns
	}

	strncat(tempBuffer, line, 56);				//access rights, file size and dates
	if (strcmp(tempLine, ".."))				//NEW icon
        {   strcat(tempBuffer, "<A HREF=\"/zzzcgi?");
	    strcat(tempBuffer, currentLINdirectory);
	    strcat(tempBuffer, tempLine);
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"/zzzchg.gif\" ALT=\"MODIFY\"></A>");
	} else
	{							//MODIFY icon
	    strcat(tempBuffer, "<A HREF=\"/zzzcgi?");
	    strcat(tempBuffer, currentLINdirectory);
	    strcat(tempBuffer, "\"><IMG ALIGN=absbottom BORDER=0 SRC=\"/zzznew.gif\" ALT=\"NEW\"></A>");
	}
	strcat(tempBuffer, "\n");
	SEND(tempBuffer);

    };
    fclose(fd);
    fd = NULL;							//close file

    strcpy(tempBuffer, "</PRE></BODY><HTML>\n");
    SEND(tempBuffer);
}

//##################################################################################################
//Execute LREAD (LDIR -READ) to copy the Linux file to DOS (temporary file is called ZZZ.ZWZ), then read
//the temporary file and send it to the web browser
//Files beginning with ZZZ... are read from lreadsrv's directory directly. Such files contain internal
//resources, e.g. the GIF-images of lreadsrv's icons etc.
//If the ZZZ... file has the extension '.htm', this file is parsed for dynamic Variables. These variables
//are substituted by the actual value of the corresponding lreadsrv's internal variable. Dynamic variables
//in HTML-Files must begin with 'ZWZ' and end with 'WZW'. Today the following are defined:
//   ZWZcurrentLINfileWZW, ZWZcurrentLINdirectoryWZW, ZWZcurrentLINdriveWZW,
//   ZWZcurrentDOSfileWZW, ZWZcurrentDOSdirectoryWZW, ZWZcurrentDOSdriveWZW
//--------------------------------------------------------------------------------------------------
void getFile(void)
{   unsigned int i, totalBytesSent, contentLength;
    struct stat fileinfo;
    BOOL localHTMLfile = FALSE;
    char *q, *startVar, *endVar, *lastVar, dynamicVariable[32];

    DEBUG("***getFile***\n");
//DEBUG("### getFile:\np=%s\ncurrentDOSdirectory=%s\ncurrentDOSfile=%s\ncurrentDOSdrive=%s\n",
    //                                                      p,currentDOSdirectory,currentDOSfile,currentDOSdrive);
    //DEBUG("currentLINdirectory=%s\ncurrentLINfile=%s\ncurrentLINdrive=%s\n",
    //                                                      p,currentLINdirectory,currentLINfile,currentLINdrive);

    if ((q = strstr(p, "/zzz")) != NULL)			//if requested filename is ZZZ..., it is one of our local files
    {   if ((fd = fopen(q + 1, "r")) == NULL)			//+++ open local file (lreadsrv resource files)
        {   printf("srv: could not open local file %s\n", q + 1);
	    showErrorMessage(404, "LREADsrv: Error - Could not open local file\n");
	    return;
	}
	if (strstr(q, ".htm"))
        {   localHTMLfile = TRUE;
	}
    } else if (LINmode == FALSE)				//if not, it may be a DOS file
    {   decodeURI(p, FALSE, FALSE);					
#ifndef UNIX
	strxchg(p, "/", "\\");
#endif
	strcpy(line, currentDOSdrive);
	strcat(line, p);
	if ((fd = fopen(line, "r")) == NULL)			//+++ open DOS file
        {   printf("srv: could not open DOS file >>>%s<<<\n", line);
	    showErrorMessage(404, "LREADsrv: Error - Could not open DOS file\n");
	    return;
	}
    } else							/*  LINmode==TRUE                                   // ... or a Linux file
	*/
    {   decodeURI(p, FALSE, FALSE);					
    	strcpy(tempBuffer, "ldir -READ -q -s");
	strcat(tempBuffer, currentLINdrive);
	strcat(tempBuffer, " \"");
	strcat(tempBuffer, p);
	strcat(tempBuffer, "\" zzz.zwz");			//+++ execute LDIR -READ, results go into file ZZZ.ZWZ
#ifndef UNIX
	system("IF EXIST ZZZ.ZWZ DEL ZZZ.ZWZ");
#else
	system("rm -f zzz.zwz");
#endif
	DEBUG("executing: %s\n", tempBuffer);
	system(tempBuffer);

	if ((fd = fopen("zzz.zwz", "r+")) == NULL)		//+++ open file ZZZ.ZWZ
        {   printf("srv: could not open result file\n");
	    showErrorMessage(404, "LREADsrv: Error - could not open file");
	    return;
	}
    }

    fstat(fileno(fd), &fileinfo);				//get an information about the file's length
    contentLength = fileinfo.st_size;

    openHTTPResponse(200);					// no error
    if (localHTMLfile == FALSE)
    {   catHTTPContentType(p, contentLength);
    } else
    {								//with our local files we sent a bit more
	catHTTPContentType(p, contentLength + 256);		//(you newer know when you substitute dynamic variables...)
    }

    SEND(tempBuffer);
    totalBytesSent = 0;

    while (!feof(fd))						//read data from file and send to client
    {   memset(&line, 0, sizeof(line));
	i = fread(line, sizeof(char), BUFSIZE, fd);
	if (localHTMLfile)					//local files are scanned to substitue dynamic variables
        {   strcpy(tempBuffer, "");
	    endVar = line - 3;
	    lastVar = line;

	    while (1)
            {   startVar = strstr(endVar + 3, "ZWZ");		//look for start of dynamic variable
		if (startVar == NULL)
		    break;					//break out if not found
		endVar = strstr(startVar, "WZW");		//look for end of dynamic variable
		if (endVar == NULL)
		    break;
		memcpy(dynamicVariable, startVar, endVar - startVar + 3);	//isolate dynamic variable
		dynamicVariable[endVar - startVar + 3] = 0;
		//DEBUG("found dynamicVariable>>>%s<<<\n",dynamicVariable);
		*startVar = 0;
		strcat(tempBuffer, lastVar);			//copy data from start (last variable) to begin of variable
		if (!strcmp(dynamicVariable, "ZWZcurrentLINfileWZW"))	//is it a known dynamic variable ?
                {   strcpy(tempLine, currentLINfile);		//if yes, substitute it
		} else if (!strcmp(dynamicVariable, "ZWZcurrentDOSfileWZW"))
                {   strcpy(tempLine, currentDOSfile);
		} else if (!strcmp(dynamicVariable, "ZWZcurrentLINdirectoryWZW"))
                {   strcpy(tempLine, currentLINdirectory);
		} else if (!strcmp(dynamicVariable, "ZWZcurrentDOSdirectoryWZW"))
                {   strcpy(tempLine, currentDOSdirectory);
		} else if (!strcmp(dynamicVariable, "ZWZcurrentLINdriveWZW"))
                {   strcpy(tempLine, currentLINdrive);
		} else if (!strcmp(dynamicVariable, "ZWZcurrentDOSdriveWZW"))
                {   strcpy(tempLine, currentDOSdrive);
		} else
                {   strcpy(tempLine, dynamicVariable);		//if no, leave it as it is
		}
		decodeURI(tempLine, FALSE, FALSE);
		strcat(tempBuffer, tempLine);
		lastVar = endVar + 3;
	    }
	    strcat(tempBuffer, lastVar);
	    i = strlen(tempBuffer);
	} else
        {   memcpy(tempBuffer, line, i);			//other files are sent directly
	}
	SENDDATA(tempBuffer, i);
	totalBytesSent += i;
    };
    fclose(fd);
    fd = NULL;							//close file

    if (localHTMLfile == FALSE)
    {   if (totalBytesSent != contentLength)
        {   printf("Error: expected to sent %d Bytes --- actually sent %d Bytes\n", totalBytesSent, contentLength);
	}
    } else
    {   if (totalBytesSent < contentLength)
        {   i = contentLength - totalBytesSent;
	    memset((void *) tempBuffer, ' ', i);
	    SENDDATA(tempBuffer, i);
	}
    }
}

//##################################################################################################
//User interface for modifying file properties or deleting files is done via HTML forms document
//ZZZchg.htm
//--------------------------------------------------------------------------------------------------
void modifyLINfile(void)
{   DEBUG("***modifyLINfile***\n");

    p = strtok(NULL, " \n\r");					//isolate full qualified pathname of file
    strcpy(currentLINfile, p);

    p = "/zzzchg.htm";
    getFile();
}

//##################################################################################################
//This routine actually does the work (modify and delete files). It is definitely not an example
//of good coding, just quick and dirty. Some day I'll write a clean parser, but for now ...
//--------------------------------------------------------------------------------------------------
void modifyLINfileExecute(void)
{   char *q, *r, fileName[256] = "", newFileName[256] = "", DeleteOK[8] = "", symLink[8] = "", UID[8] = "", GID[8] = "",
     ACC[16] = "";
    int i;

    DEBUG("***modifyLINfileExecute***\n");
    strcpy(tempBuffer, "ldir -WRITE -x ");			//begin of LDIR command line

    while (1)							//parse the POST message, to isolate the line with the CGI variables
    {   p = strtok(NULL, "\n\r");				//note here: fileName=...&DeleteOK=...&UID=...&GID=...&ACC=...
	if (p == NULL)
        {   RECEIVE;						//Netscape Navigator 4.5 under Windows NT seems to
	    p = strtok(readBuffer, "\n\r");			//send a second message containing the CGI variables
	    if (p == NULL)
		return;						//Internet Explorer merges them with the POST message
	    if (i == 0)
		return;						//This code seems to work with both.
	}
	if (strstr(p, "fileName=") != NULL)
	    break;
    }
    DEBUG("found it: p=%s\n", p);
    strcpy(line, p);

    strExtract(line, "fileName=", "&", fileName);		//scan the CGI variables and extract their values
    strExtract(line, "DeleteOK=", "&", DeleteOK);
    strExtract(line, "symLink=", "&", symLink);
    strExtract(line, "newFileName=", "&", newFileName);
    strExtract(line, "UID=", "&", UID);
    strExtract(line, "GID=", "&", GID);
    strExtract(line, "ACC=", "&", ACC);

    if (strlen(fileName) == 0)
    {   showErrorMessage(404, "Error in LINUX filename");
	return;
    }
    decodeURI(fileName, TRUE, TRUE);
    if (strlen(newFileName) > 0)
    {   decodeURI(newFileName, TRUE, TRUE);
    }
    if (strlen(symLink) == 0)					//Rename, change or delete existing file, directory or symlink
    {   if (!strncmp(DeleteOK, "yes", 3))			//+++ Delete
        {   strcat(tempBuffer, "-del ");
	    strcat(tempBuffer, "-s");
	    strcat(tempBuffer, currentLINdrive);
	    strcat(tempBuffer, " \"");
	    strcat(tempBuffer, fileName);
	    strcat(tempBuffer, "\"");
	} else if (strlen(newFileName) > 0)			//+++ Rename
        {   strcat(tempBuffer, "-ren ");
	    strcat(tempBuffer, "-s");
	    strcat(tempBuffer, currentLINdrive);
	    strcat(tempBuffer, " ");
	    if (strlen(UID) > 0)
            {   strcat(tempBuffer, "-u");
		strcat(tempBuffer, UID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(GID) > 0)
            {   strcat(tempBuffer, "-g");
		strcat(tempBuffer, GID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(ACC) > 0)
            {   strcat(tempBuffer, "-f");
		strcat(tempBuffer, ACC);
		strcat(tempBuffer, " ");
	    }
	    strcat(tempBuffer, "\"");
	    strcat(tempBuffer, fileName);
	    strcat(tempBuffer, "\" \"");
	    strcat(tempBuffer, newFileName);
	    strcat(tempBuffer, "\"");
	} else							//+++ Change
        {   strcat(tempBuffer, "-s");
	    strcat(tempBuffer, currentLINdrive);
	    strcat(tempBuffer, " ");
	    if (strlen(UID) > 0)
            {   strcat(tempBuffer, "-u");
		strcat(tempBuffer, UID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(GID) > 0)
            {   strcat(tempBuffer, "-g");
		strcat(tempBuffer, GID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(ACC) > 0)
            {   strcat(tempBuffer, "-f");
		strcat(tempBuffer, ACC);
		strcat(tempBuffer, " ");
	    }
	    strcat(tempBuffer, "\"");
	    strcat(tempBuffer, fileName);
	    strcat(tempBuffer, "\"");
	}
    } else
	//New directory or symlink
    {   if (!strcmp(currentLINdirectory, fileName))
        {   showErrorMessage(404, "Please specify the name of the new directory or symlink");
	    return;
	}
	if (!strncmp(symLink, "yes", 3))			//+++ Symlink
        {   strcat(tempBuffer, "-link ");
	    strcat(tempBuffer, "-s");
	    strcat(tempBuffer, currentLINdrive);
	    strcat(tempBuffer, " ");
	    if (strlen(UID) > 0)
            {   strcat(tempBuffer, "-u");
		strcat(tempBuffer, UID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(GID) > 0)
            {   strcat(tempBuffer, "-g");
		strcat(tempBuffer, GID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(ACC) > 0)
            {   strcat(tempBuffer, "-f");
		strcat(tempBuffer, ACC);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(newFileName) > 0)
            {   strcat(tempBuffer, "\"");
            	strcat(tempBuffer, newFileName);
		strcat(tempBuffer, "\" ");
	    } else
            {   showErrorMessage(404, "Target name for symlink missing ");
		return;
	    }
	    strcat(tempBuffer, "\"");
	    strcat(tempBuffer, fileName);
	    strcat(tempBuffer, "\"");
	} else							//+++ Directory
        {   strcat(tempBuffer, "-mkdir ");
	    strcat(tempBuffer, "-s");
	    strcat(tempBuffer, currentLINdrive);
	    strcat(tempBuffer, " ");
	    if (strlen(UID) > 0)
            {   strcat(tempBuffer, "-u");
		strcat(tempBuffer, UID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(GID) > 0)
            {   strcat(tempBuffer, "-g");
		strcat(tempBuffer, GID);
		strcat(tempBuffer, " ");
	    }
	    if (strlen(ACC) > 0)
            {   strcat(tempBuffer, "-f");
		strcat(tempBuffer, ACC);
		strcat(tempBuffer, " ");
	    }
	    strcat(tempBuffer, "ldirtmpl \"");
	    strcat(tempBuffer, fileName);
	    strcat(tempBuffer, "\"");
	}
    }

    DEBUG("executing: %s\n", tempBuffer);
    system(tempBuffer);

    strcpy(currentLINfile, "");

    p = currentLINdirectory;
    showLINdirectoryListing();

    return;

}


//##################################################################################################
//This routine actually does the work (copy files). It is definitely not an example
//of good coding, just quick and dirty. Some day I'll write a clean parser, but for now ...
//--------------------------------------------------------------------------------------------------
void copyDOSfileExecute(void)
{   char *q, *r, DOSfileName[256] = "", LINfileName[256] = "", UID[8] = "", GID[8] = "", ACC[16] = "";
    char fullName[256], *fileName;
    int i;

    DEBUG("***copyDOSfileExecute***\n");
    strcpy(tempBuffer, "ldir -WRITE -x -copy ");		//begin of LDIR command line

    while (1)							//parse the POST message, to isolate the line with the CGI variables
    {   p = strtok(NULL, "\n\r");				//note here: DOSfileName=...&LINfileName=...&UID=...&GID=...&ACC=...
	if (p == NULL)
        {   RECEIVE;						//Netscape Navigator 4.5 under Windows NT seems to
	    p = strtok(readBuffer, "\n\r");			//send a second message containing the CGI variables
	    if (p == NULL)
		return;						//Internet Explorer merges them with the POST message
	    if (i == 0)
		return;						//This code seems to work with both.
	}
	if (strstr(p, "DOSfileName=") != NULL)
	    break;
    }
    DEBUG("found it: p=%s\n", p);
    strcpy(line, p);

    strExtract(line, "DOSfileName=", "&", DOSfileName);		//scan the CGI variables and extract their values
    strExtract(line, "LINfileName=", "&", LINfileName);
    strExtract(line, "UID=", "&", UID);
    strExtract(line, "GID=", "&", GID);
    strExtract(line, "ACC=", "&", ACC);

    if (strlen(DOSfileName) == 0)
    {   showErrorMessage(404, "Error in DOS filename");
	return;
    }
    if (strlen(LINfileName) == 0)
    {   showErrorMessage(404, "Error in LINUX filename");
	return;
    }
    decodeURI(DOSfileName, TRUE, TRUE); 
    decodeURI(LINfileName, TRUE, TRUE);

    if (LINfileName[strlen(LINfileName) - 1] == '/')		//if only a pathname is given, append the DOSfileName to the LINUX path
    {
#ifndef UNIX
	strxchg(DOSfileName, "/", "\\");
	GetFullPathName(DOSfileName, sizeof(fullName), fullName, &fileName);
#else
	GetFileName(DOSfileName, &fileName);
#endif
	strcat(LINfileName, fileName);
    }
    strcat(tempBuffer, "\"");
    strcat(tempBuffer, DOSfileName);
    strcat(tempBuffer, "\" ");
    if (LINfileName[0] != '/')
	strcat(tempBuffer, currentLINdirectory);
    strcat(tempBuffer, "\"");
    strcat(tempBuffer, LINfileName);
    strcat(tempBuffer, "\" ");

    if (strlen(UID) > 0)
    {   strcat(tempBuffer, "-u");
	strcat(tempBuffer, UID);
	strcat(tempBuffer, " ");
    }
    if (strlen(GID) > 0)
    {   strcat(tempBuffer, "-g");
	strcat(tempBuffer, GID);
	strcat(tempBuffer, " ");
    }
    if (strlen(ACC) > 0)
    {   strcat(tempBuffer, "-f");
	strcat(tempBuffer, ACC);
	strcat(tempBuffer, " ");
    }
    strcat(tempBuffer, "-s");
    strcat(tempBuffer, currentLINdrive);
    strcat(tempBuffer, " ");

    DEBUG("executing: %s\n", tempBuffer);
    system(tempBuffer);						//execute LDIR -WRITE -x -copy DOSfile LINfile -u... -g... -f...
    strcpy(currentLINfile, "");
    strcpy(currentDOSfile, "");

    p = currentLINdirectory;
    showLINdirectoryListing();

    return;
}

//##################################################################################################
//This routine actually does the work (change default LINUX drive). It is definitely not an example
//of good coding, just quick and dirty. Some day I'll write a clean parser, but for now ...
//--------------------------------------------------------------------------------------------------
void changeDriveExecute(void)
{   char *q, *r;
    int i;

    DEBUG("***changeDriveExecute***\n");
    while (1)							//parse the POST message, to isolate the line with the CGI variables
    {   p = strtok(NULL, "\n\r");				//note here: DOSfileName=...&LINfileName=...&UID=...&GID=...&ACC=...
	if (p == NULL)
        {   RECEIVE;						//Netscape Navigator 4.5 under Windows NT seems to
	    p = strtok(readBuffer, "\n\r");			//send a second message containing the CGI variables
	    if (p == NULL)
		return;						//Internet Explorer merges them with the POST message
	    if (i == 0)
		return;						//This code seems to work with both.
	}
	if (strstr(p, "LINdrive=") != NULL)
	    break;
    }
    DEBUG("found it: p=%s\n", p);
    strcpy(line, p);

    strExtract(line, "LINdrive=", "&", currentLINdrive);	//scan the CGI variables and extract their values
    
    decodeURI(currentLINdrive, FALSE, TRUE);
    DEBUG("Changed current LINUX drive to %s\n", currentLINdrive);
    if (strlen(currentLINdrive) == 0)
    {   showErrorMessage(404, "Error in LINUX drive name");
	return;
    }
    strExtract(line, "DOSdrive=", "&", currentDOSdrive);	//scan the CGI variables and extract their values
    decodeURI(currentDOSdrive, FALSE, TRUE);

    DEBUG("Changed current DOS drive to %s\n", currentDOSdrive);
#ifndef UNIX
    if (strlen(currentDOSdrive) == 0)
    {   showErrorMessage(404, "Error in DOS drive name");
	return;
    }
#endif
    strcpy(currentLINdirectory, "/");				//reset current directories to root of drive
    strcpy(currentDOSdirectory, "/");

    if (LINmode == TRUE)
    {   p = currentLINdirectory;
	showLINdirectoryListing();
    } else
    {   p = currentDOSdirectory;
	showDOSdirectoryListing();
    }
    return;
}

//#################################################################################################
//Convert IP string, e.g. 127.0.0.1 to
//-------------------------------------------------------------------------------------------------
unsigned long convertIPtoLong(const char *string)
{   unsigned char *q, IPstring[32];
    unsigned long dwIP;

    strcpy(IPstring, string);

    if ((q = strtok(IPstring, ".")) == NULL)
    {   printf("Error: Malformed IP = %s - Exiting\n", IPstring);
	exit(-1);
    }
    dwIP = atol(q) << 24;
    if ((q = strtok(NULL, ".")) == NULL)
    {   printf("Error: Malformed IP = %s - Exiting\n", IPstring);
	exit(-1);
    }
    dwIP = dwIP + (atol(q) << 16);
    if ((q = strtok(NULL, ".")) == NULL)
    {   printf("Error: Malformed IP = %s - Exiting\n", IPstring);
	exit(-1);
    }
    dwIP = dwIP + (atol(q) << 8);
    if ((q = strtok(NULL, ".")) == NULL)
    {   printf("Error: Malformed IP = %s - Exiting\n", IPstring);
	exit(-1);
    }
    dwIP = dwIP + atol(q);

    printf("Registered IP: %s for incoming connections\n", string);
    return dwIP;
}


//##################################################################################################
//##################################################################################################
//##################################################################################################
//Main programm
//--------------------------------------------------------------------------------------------------
#pragma argsused
int main(int argc, char **argv)
{   int i, j;
    char *q;
    unsigned long currentIP;
#ifndef UNIX
    HKEY hKey;
    char buffer[256];
#endif
    printStartup();

#ifndef UNIX
    _fmode = O_BINARY;						//we want all files binary
    SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlBreakHandler, TRUE);	//install CTRL-Break handler    

    //In case this software was installed as a service by loaddrv
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\Loaddrive\\lreadsrv\\application", 0, KEY_ALL_ACCESS, &hKey)==ERROR_SUCCESS)
    {	i=sizeof(buffer);
    	if (RegQueryValueEx(hKey, "", NULL, &j, buffer, &i)==ERROR_SUCCESS)
    	{   q=strrchr(buffer,'\\');
    	    *q=0;
   	    SetCurrentDirectory(buffer);
    	    printf("Service running from directory %s\n\n", buffer);
    	}
    }
#endif

    if (argc > 1)
	serverPort = atoi(argv[1]);				//if portnumber is specified on the command line

    memset(registeredClientIP, 0, sizeof(registeredClientIP));	// set whole array to 0s
    //the following client IP addresses will be allowed to conntect to our server
    registeredClientIP[0] = 0x7F000001L;			//default client IP is 127.0.0.1 (localhost)
    if (argc > 2)
	registeredClientIP[1] = convertIPtoLong(argv[2]);
    if (argc > 3)
	registeredClientIP[2] = convertIPtoLong(argv[3]);
    if (argc > 4)
	registeredClientIP[3] = convertIPtoLong(argv[4]);


    p = (char *) getenv("LDRIVE");				//Get default drive from environment variable LDRIVE, if set
    if (p != NULL)
    {   strncpy(currentLINdrive, p, sizeof(currentLINdrive));
    }
    //############ Establish connection to host ####################################
#ifndef UNIX
    //Load and start Winsock.DLL
    wVersionRequested = MAKEWORD(1, 1);
    if (WSAStartup(wVersionRequested, &wsaData))
    {   perror("srv: WSAStartup");
	exit(1);
    }
    DEBUG("srv: opened Winsock.dll\n Version=%x\n HighVersion=%x\n Description=%s\n SystemStatus=%s\n MaxSockets=%u\n MaxUdpDg=%u\n VendorInfo=%s\n", \
	  wsaData.wVersion, wsaData.wHighVersion, wsaData.szDescription, wsaData.szSystemStatus, \
	  wsaData.iMaxSockets, wsaData.iMaxUdpDg, wsaData.lpVendorInfo);
#endif
    //Set TCP/IP protocol and port
    memset(&Sin, 0, sizeof(Sin));
    Sin.sin_family = AF_INET;
    Sin.sin_addr.s_addr = INADDR_ANY;
    Sin.sin_port = htons(serverPort);
    //Create a socket, bind and listen
    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    {   perror("srv:socket");
	WSACleanup();
	exit(1);
    }
    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value));
    if (bind(sd, (struct sockaddr *) &Sin, sizeof(Sin)) == SOCKET_ERROR)
    {   perror("srv:bind");
	WSACleanup();
	exit(1);
    }
    if (listen(sd, 5) == SOCKET_ERROR)
    {   perror("srv:client");
	WSACleanup();
	exit(1);
    }
    addrlen = sizeof(Pin);					//most important !

    //############ Wait for connection loop ######################################
    while ((sd_current = accept(sd, (struct sockaddr *) &Pin, &addrlen)) != INVALID_SOCKET)
    {								//checking, if incoming connection comes from one of the allowed IPs
	currentIP = ntohl(*(unsigned long *) &Pin.sin_addr);

	connectionAllowed = FALSE;
	for (i = 0; i < CLIENTMAX; i++)
	{							// a little more performance
	    if (currentIP == registeredClientIP[i])
            {   connectionAllowed = TRUE;
		break;						// just leave now !
	    }
	}
	if (connectionAllowed == FALSE)
        {   showErrorMessage(403, "Sorry, due to security reasons LREADsrv does not accept connections from your site\n");
	    printf("Sorry, due to security reasons LREADsrv does not accept connections from this site\n");
	    printf("srv: refused connection from %d.%d.%d.%d:%5d --- \n",
		   (currentIP >> 24) & 0xFF, (currentIP >> 16) & 0xFF, (currentIP >> 8) & 0xFF, (currentIP) & 0xFF, Pin.sin_port);
	    closesocket(sd_current);
	    sd_current = 0;
	    continue;
	}
#ifndef UNIX
	printf("\rsrv: connect from %d.%d.%d.%d:%5d --- ",
	(currentIP >> 24) & 0xFF, (currentIP >> 16) & 0xFF, (currentIP >> 8) & 0xFF, (currentIP) & 0xFF, Pin.sin_port);
#else
	printf("\rsrv: connect from %d.%d.%d.%d:%5d --- ",
	(currentIP >> 24) & 0xFF, (currentIP >> 16) & 0xFF, (currentIP >> 8) & 0xFF, (currentIP) & 0xFF, Pin.sin_port);
#endif
	clientDataPort = Pin.sin_port;

	setsockopt(sd_current, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value));
	linger.l_onoff = 0xFFFFFFFF;
	linger.l_linger = 1;
	setsockopt(sd_current, SO_LINGER, SO_REUSEADDR, (char *) &linger, sizeof(linger));


	// ioctlsocket(sd_current,FIONBIO,(u_long *) "1");     //make socket non blocking

	//### Send/receive loop ####################################################
	do
        {   RECEIVE;						//receive HTTP commands
	    if (i == 0)
		break;						//empty receive -> client shutdown -> break out of send/receive loop

	    p = strtok(readBuffer, " \n\r");			//isolate command in received string
	    strupr(p);						//convert command to upper case

//--------------------------------------------------------------------------------------------
	    if (strcmp(p, "GET") == 0)				//client GET request
            {   p = strtok(NULL, " ?");				//isolate requested item in received string
		printf("GET  request: %s\n", p);

		if (strstr(p, "/zzzcgi"))			//"CGI"-Interface
                {   p = strtok(NULL, " \n\r");			//isolate full qualified pathname of file
		    if (LINmode == TRUE)
                    {   if (strcmp(p, currentLINdirectory))
                        {   strcpy(currentLINfile, p);		//in LINUX mode command is for file modification
			    p = "/zzzchg.htm";
			} else
                        {   p = "/zzznew.htm";			//... or for creating a directory or symlink
			}
		    } else
                    {   q = strrchr(p, '/');
			if (q != NULL)
			    strcpy(currentDOSfile, q + 1);	//in DOS mode command is for file selection
			p = "/zzzwri.htm";
			LINmode = TRUE;				//switch back to LIN mode
		    }
		    getFile();
		    break;
		} else if (strstr(p, "/zzzmod"))		//"CGI"-Interface for switching between DOS and LINUX directory view
                {   p = strtok(NULL, " \n\r");			//isolate parameter string (DOS or LIN)
		    if (strstr(p, "DOS"))
                    {   LINmode = FALSE;
			p = currentDOSdirectory;
			showDOSdirectoryListing();
		    } else
                    {   LINmode = TRUE;
			p = currentLINdirectory;
			showLINdirectoryListing();
		    }
		    break;
		} else
		    //file/directory request
                {   switch (p[strlen(p) - 1])
                    {   case '/':
			    if (LINmode == TRUE)
				showLINdirectoryListing();	//requested item is a directory
			    else
				showDOSdirectoryListing();
			    break;
			case '@':
			    strcpy(currentLINfile, p);
			    p = "/zzzsym.htm";
			    getFile();
			    break;				//requested item is a symbolic link
			case '>':
			    strcpy(currentLINfile, p);
			    p = "/zzzdev.htm";
			    getFile();
			    break;				//requested item is a device file
			default:
			    getFile();
			    break;				//requested item is a normal file
		    }
		    break;
		}
	    } else if (strcmp(p, "POST") == 0)			//client POST request
            {   p = strtok(NULL, " ");				//isolate requested item in received string
		DEBUG("POST request: %s\n", p);

		if (strstr(p, "/zzzcgi"))			//"CGI"-Interface for file modification
                {   modifyLINfileExecute();
		} else if (strstr(p, "/zzzwri"))
                {   LINmode = TRUE;
		    copyDOSfileExecute();			//"CGI"-Interface to copy files from DOS to Linux
		} else if (strstr(p, "/zzzdrv"))
                {   changeDriveExecute();			//"CGI"-Interface for changing the Linux default drive
		}
		break;
//--------------------------------------------------------------------------------------------
	    } else if (strcmp(p, "HEAD") == 0)			//client HEAD request
            {   showErrorMessage(501, "Not implemented");	//unimplemented command
		perror("srv:Unknown Command");
		break;
	    } else
            {   showErrorMessage(400, "Unknown command");	//unknown command
		perror("srv:Unknown Command");
		break;
	    }
	}
	while (1);
	//##########################################################################
	closesocket(sd_current);
	sd_current = 0;						//close connection
#ifndef UNIX
	DEBUG(" srv: closed connection to %d.%d.%d.%d:%5d\n",
	(currentIP >> 24) & 0xFF, (currentIP >> 16) & 0xFF, (currentIP >> 8) & 0xFF, (currentIP) & 0xFF, Pin.sin_port);
#else
	DEBUG(" srv: closed connection to %d.%d.%d.%d:%5d\n",
	(currentIP >> 24) & 0xFF, (currentIP >> 16) & 0xFF, (currentIP >> 8) & 0xFF, (currentIP) & 0xFF, Pin.sin_port);
#endif
	DEBUG("------------------------------------------------------------------\n");
    }

//############ Close connection to host ########################################
    //we never should reach this point, as the infinite 'wait for connection' loop can only
    //be left by CTRL-Break.
    closesocket(sd);
    sd = 0;
    WSACleanup();
    perror("srv:accept failed");
    return (-1);
}

//##################################################################################################
//##################################################################################################
//##################################################################################################

