/* Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define WS_SSL

#include "httpd.h"
#include "ap_config.h"
#include "http_config.h"
#include "http_log.h"
#include <dirent.h>

extern char ap_server_root[MAX_STRING_LEN];

void ap_os_dso_init(void)
{
}

void *ap_os_dso_load(const char *path)
{
    unsigned int nlmHandle;
    char *moduleName = NULL;
    
    moduleName = strrchr(path, '/');

    if (moduleName) {
        moduleName++;
    }
    
    nlmHandle = FindNLMHandleInAddressSpace((char*)moduleName, NULL);

    if (nlmHandle == NULL) {
        spawnlp(P_NOWAIT | P_SPAWN_IN_CURRENT_DOMAIN, path, NULL);        
        nlmHandle = FindNLMHandleInAddressSpace((char*)moduleName, NULL);
    }

    return (void *)nlmHandle;
}

void ap_os_dso_unload(void *handle)
{
	KillMe(handle);
}

void *ap_os_dso_sym(void *handle, const char *symname)
{
    return ImportSymbol((int)GetNLMHandle(), (char *)symname);
}

void ap_os_dso_unsym(void *handle, const char *symname)
{
    UnimportSymbol((int)GetNLMHandle(), (char *)symname);
}

const char *ap_os_dso_error(void)
{
    return NULL;
}

char *remove_filename(char* str)
{
    int i, len = strlen(str);    
  
    for (i=len; i; i--) {
        if (str[i] == '\\' || str[i] == '/') {
            str[i] = NULL;
            break;
        }
    }
    return str;
}

char *bslash2slash(char* str)
{
    int i, len = strlen(str);    
  
    for (i=0; i<len; i++) {
        if (str[i] == '\\') {
            str[i] = '/';
            break;
        }
    }
    return str;
}

void check_clean_load(module *top_module)
{
    if (top_module != NULL) {
        module *m;

        ap_log_error(APLOG_MARK, APLOG_CRIT, NULL,
            "abnormal shutdown detected, performing a clean shutdown: please restart apache");
        for (m = top_module; m; m = m->next)
            ap_os_dso_unload((ap_os_dso_handle_t)m->dynamic_load_handle);
        exit(1);
    }
}

void init_name_space()
{
    UnAugmentAsterisk(TRUE);
    SetCurrentNameSpace(NW_NS_LONG);
    SetTargetNameSpace(NW_NS_LONG);
}

/*  Perform complete canonicalization.  On NetWare we are just
	lower casing the file name so that the comparisons will match.
	NetWare assumes that all physical paths are fully qualified.  
	Each file path must include a volume name.
 */
static char *os_canonical_filename(pool *pPool, const char *szFile)
{
    char *pNewName = ap_pstrdup(pPool, szFile);
    char *slash_test;
	
    bslash2slash(pNewName);
    /* Don't try to canonicalize a filename that isn't even valid
        This way we don't mess up proxy requests or other kinds
        of special filenames.
    */
    if (ap_os_is_filename_valid(pNewName)) {
        if ((pNewName[0] == '/') && (strchr (pNewName, ':') == NULL))
        {
            char vol[256];

            _splitpath (ap_server_root, vol, NULL, NULL, NULL);
            pNewName = ap_pstrcat (pPool, vol, pNewName, NULL);
        }
        if ((slash_test = strchr(pNewName, ':')) && (*(slash_test+1) != '/') 
            && (*(slash_test+1) != '\0'))
        {
            char vol[_MAX_VOLUME+1];
        
            _splitpath (pNewName, vol, NULL, NULL, NULL);
            pNewName = ap_pstrcat (pPool, vol, "/", pNewName+strlen(vol), NULL);
        }
    }
    return pNewName;
}

char *ap_os_canonical_filename(pool *pPool, const char *szFile)
{
    char *pNewName = os_canonical_filename(pPool, szFile);

    /* Lower case the name so that the interal string compares work */
    strlwr(pNewName);
    return pNewName;
}


char *ap_os_case_canonical_filename(pool *pPool, const char *szFile)
{
    /* First thing we need to do is get a copy of the 
        canonicalized path */
    char *pNewName = os_canonical_filename(pPool, szFile);
    int	  volnum=0;
    long  dirnum=0;
    long  pathcount=0;
    char *path;
    char  vol[_MAX_VOLUME+1];
    int   retval, x, y;
	    
    /* See if path exists by trying to get the volume and directory number */
    retval = FEMapPathVolumeDirToVolumeDir(pNewName, 0, 0, &volnum, &dirnum);
    if (retval == 0) {
        /* allocate a buffer and ask the file system for the real name of
            the directory and file */
        path = ap_palloc(pPool, strlen(pNewName)+2);
        FEMapVolumeAndDirectoryToPath (volnum, dirnum, path, &pathcount);

        /* The file system gives it back in a lengh preceded string so we
            need to convert it to a null terminated string. */
        x = 0;
        while (pathcount-- > 0) {
            y = path[x];
            path[x] = '/';
            x += y + 1;
        }
        path[x] = '\0';  /* null terminate the full path */

        /* Get the name of the volume so that we can prepend it onto the path */
        FEMapVolumeNumberToName (volnum, vol);
        vol[vol[0]+1] = '\0';
        pNewName = ap_pstrcat (pPool, &(vol[1]), ":", path, NULL);
    }

    /* At this point we either have a real case accurate canonical path or 
        the original name canonicalized */
    return pNewName;
}


/*
 * ap_os_is_filename_valid is given a filename, and returns 0 if the filename
 * is not valid for use on this system. On NetWare, this means it fails any
 * of the tests below. Otherwise returns 1.
 *
 * The tests are:
 *
 * 1) total path length greater than MAX_PATH
 * 
 * 2) the file path must contain a volume specifier and no / or \
 *     can appear before the volume specifier.
 *
 * 3) anything using the octets 0-31 or characters " < > | :
 *    (these are reserved for Windows use in filenames. In addition
 *     each file system has its own additional characters that are
 *     invalid. See KB article Q100108 for more details).
 *
 * 4) anything ending in "." (no matter how many)
 *    (filename doc, doc. and doc... all refer to the same file)
 *
 * 5) any segment in which the basename (before first period) matches
 *    one of the DOS device names
 *    (the list comes from KB article Q100108 although some people
 *     reports that additional names such as "COM5" are also special
 *     devices).
 *
 * If the path fails ANY of these tests, the result must be to deny access.
 */

int ap_os_is_filename_valid(const char *file)
{
    const char *segstart;
    unsigned int seglength;
    const char *pos;
	char *colonpos, *fslashpos, *bslashpos;
    static const char * const invalid_characters = "?\"<>*|:";
    static const char * const invalid_filenames[] = { 
		"CON", "AUX", "COM1", "COM2", "COM3", 
		"COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL 
    };

	/* First check to make sure that we have a file so that we don't abend */
	if (file == NULL)
		return 0;

    /* Test 1 */
    if (strlen(file) >= _MAX_PATH) {
		/* Path too long for Windows. Note that this test is not valid
		 * if the path starts with //?/ or \\?\. */
		return 0;
    }

    pos = file;

    /* Skip any leading non-path components. This can be either a
     * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\.
     * We continue and check the rest of the path based on the rules above.
     * This means we could eliminate valid filenames from servers which
     * are not running NT (such as Samba).
     */

	colonpos = strchr (file, ':');

	if (!colonpos)
		return 0;

	pos = ++colonpos;
   	if (!*pos) {
		/* No path information */
		/* Same as specifying volume:\ */
		return 1;
    }

    while (*pos) {
		unsigned int idx;
		unsigned int baselength;

		while (*pos == '/' || *pos == '\\') {
    	    pos++;
		}
		if (*pos == '\0') {
		    break;
		}
		segstart = pos;	/* start of segment */
		while (*pos && *pos != '/' && *pos != '\\') {
		    pos++;
		}
		seglength = pos - segstart;
		/* 
		 * Now we have a segment of the path, starting at position "segstart"
		 * and length "seglength"
		 */

		/* Test 2 */
		for (idx = 0; idx < seglength; idx++) {
	    	if ((segstart[idx] > 0 && segstart[idx] < 32) ||
				strchr(invalid_characters, segstart[idx])) {
				return 0;
	    	}
		}

		/* Test 2.5 */
		if (seglength == 2) {
			if ( (segstart[0] == '.') && (segstart[1] == '.') ) {
					return 1;
			}
		
		}

		/* Test 3 */
		if (segstart[seglength-1] == '.') {
		    return 0;
		}

		/* Test 4 */
		for (baselength = 0; baselength < seglength; baselength++) {
		    if (segstart[baselength] == '.') {
				break;
		    }
		}

		/* baselength is the number of characters in the base path of
		 * the segment (which could be the same as the whole segment length,
		 * if it does not include any dot characters). */
		if (baselength == 3 || baselength == 4) {
	    	for (idx = 0; invalid_filenames[idx]; idx++) {
				if (strlen(invalid_filenames[idx]) == baselength &&
				    !strnicmp(invalid_filenames[idx], segstart, baselength)) {
				    return 0;
				}
	    	}
		}
    }

    return 1;
}

#undef opendir_411
DIR *os_opendir (const char *pathname)
{
	struct stat s;
	DIR *d = opendir_411 (pathname);

    if (d) {
        strcpy (d->d_name, "<<**");
    }

	if (!d) {
		/* Let's check if this is an empty directory */
		if (stat(pathname, &s) != 0)
			return NULL;
		if (!(S_ISDIR(s.st_mode)))
			return NULL; 
		
		/* If we are here, then this appears to be a directory */
		/* We allocate a name */
		d = NULL;
        d = (DIR *)malloc(sizeof(DIR));
        if (d) {
            memset(d, 0, sizeof(DIR));
            strcpy(d->d_name, "**<<");
			d->d_cdatetime = 50;

        }    

  	}

    return d;

}

#undef readdir_411
DIR *os_readdir (DIR *dirP)
{

/*
 * First three if statements added for empty directory support.
 *
 */
    if (  (dirP->d_cdatetime == 50) && (dirP->d_name[0] == '*') &&
       	  (dirP->d_name[2] == '<') )
    {
        strcpy (dirP->d_name, ".");
        strcpy (dirP->d_nameDOS, ".");
        return (dirP);
    }
    else if ((dirP->d_cdatetime == 50) &&
             (dirP->d_name[0] == '.') &&
             (dirP->d_name[1] == '\0')) {
        strcpy (dirP->d_name, "..");
        strcpy (dirP->d_nameDOS, "..");
        return (dirP);
    }
    else if ( (dirP->d_cdatetime == 50) &&
             (dirP->d_name[0] == '.') &&
             (dirP->d_name[1] == '.') &&
             (dirP->d_name[2] == '\0') ) {
        return (NULL);
    }
    else if ((dirP->d_name[0] == '<') && (dirP->d_name[2] == '*')) {
        strcpy (dirP->d_name, ".");
        strcpy (dirP->d_nameDOS, ".");
        return (dirP);
    }
    else if ((dirP->d_name[0] == '.') && (dirP->d_name[1] == '\0')) {
        strcpy (dirP->d_name, "..");
        strcpy (dirP->d_nameDOS, "..");
        return (dirP);
    }
    else
        return readdir_411 (dirP);
}


#undef closedir_510
int os_closedir (DIR *dirP)
{
/*
 * Modified to handle empty directories.
 *
 */

	if (dirP == NULL) {
		return 0;
	}

    if (  (  (dirP->d_cdatetime == 50) && (dirP->d_name[0] == '*') &&
       	  	 (dirP->d_name[2] == '<') 
       	  ) ||
       	  (	 (dirP->d_cdatetime == 50) && (dirP->d_name[0] == '.') &&
       	     (dirP->d_name[1] == '\0')
       	  ) ||
       	  (	 (dirP->d_cdatetime == 50) && (dirP->d_name[0] == '.') &&
       	     (dirP->d_name[1] == '.') && (dirP->d_name[2] == '\0')
       	  )
       )
	{

 	       free(dirP); 
 	       dirP = NULL;
 	       return 0;
 	}
 	else {
	       return closedir_510(dirP);
	}
		

}

char *ap_os_http_method(void *r)
{
    int s = ((request_rec*)r)->connection->client->fd;
    unsigned int optParam;

    if (!WSAIoctl(s, SO_SSL_GET_FLAGS, NULL, 0, &optParam, sizeof(optParam), NULL, NULL, NULL))
        if (optParam & (SO_SSL_ENABLE | SO_SSL_SERVER)) return "https";
    return "http";
}

unsigned short ap_os_default_port(void *r)
{
  return ap_default_port_for_scheme(ap_os_http_method(r));
}
