/* 
   Netdrive Samba client plugin
   plugin API
   Copyright (C) netlabs.org 2003-2008

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#define NDPL_LARGEFILES
#define INCL_LONGLONG
#include <ndextpl2.h>
#include "smbwrp.h"
#include "util.h"

#ifndef DEBUG_PRINTF
#define debug_printf( ...)
#endif

#define log debug_printf

#if 0
void log(const char *fmt, ...)
{  
	char *ndpsmb_debug = getenv("NDPSMB_DEBUG");

	if (ndpsmb_debug != NULL) 
	{
	  	FILE * logfile = NULL;
		va_list args;
		time_t t = time(NULL);
		char timebuf[80] = {0};
		strftime(timebuf,sizeof(timebuf)-1,"%Y/%m/%d %H:%M:%S", localtime(&t));
		logfile = fopen("smblog","a");
		if (logfile == NULL)
		{
			DosBeep(400,400);
		}
		else
		{
			fprintf(logfile, "%s: (%02d) ", timebuf, _gettid());
			va_start(args, fmt);
			vfprintf(logfile, fmt, args);    
			va_end(args);
			fclose(logfile);
		}
	}
}
#endif

// -------------------------------------------------------------

/* time conversion functions: SMB protocol sends timestamps in GMT time,
*  os2 api uses localtime,
*  emx/klibc uses timezone and daylight saving to convert GMT timestamps,
*  so only the timezone must be counted in conversion.
*/
void fsphUnixTimeToDosDate( time_t time, FDATE* fdate, FTIME *ftime)
{
    struct tm* gmt = localtime( &time);
    if (gmt->tm_isdst>0) {
	debug_printf( "daylight saving in effect %d, timezone %d\n",gmt->tm_isdst, timezone); 
	time -= 3600;
	gmt = localtime( &time);
    }
    fdate->day  = gmt->tm_mday;
    fdate->month  = gmt->tm_mon+1;
    fdate->year = gmt->tm_year + 1900 - 1980;
    ftime->twosecs = gmt->tm_sec/2;
    ftime->minutes = gmt->tm_min;
    ftime->hours = gmt->tm_hour;
}

void fsphDosDateToUnixTime( FDATE fdate, FTIME ftime, unsigned long* time)
{
    struct tm gmtime = { 0 };
    struct tm* gmt;

    debug_printf( "fsphDosDateToUnixTime time %02d:%02d\n", ftime.hours, ftime.minutes);
    gmtime.tm_mday = fdate.day;
    gmtime.tm_mon = fdate.month-1;
    gmtime.tm_year = fdate.year + 1980 - 1900;
    gmtime.tm_sec = ftime.twosecs*2;
    gmtime.tm_min = ftime.minutes;
    gmtime.tm_hour = ftime.hours;
    gmtime.tm_isdst = -1; // force libc to check dst saving

    *time = mktime( &gmtime);
    debug_printf( "fsphDosDateToUnixTime time1 %d %s", *time, ctime( time));
    gmt = localtime( (time_t*) time);
    if (gmt->tm_isdst>0) {
	debug_printf( "fsphDosDateToUnixTime daylight saving in effect %d, timezone %d\n",gmt->tm_isdst, timezone); 
	*time += 3600;
    }
    debug_printf( "fsphDosDateToUnixTime time2 %d %s", *time, ctime( time));
}

// -------------------------------------------------------------
	
int StrLen(char * s)
{
	char * p;
	if (!s)
	{
		return 0;
	}
	for (p = s; *p; p++);
	return (p - s);
}

char * StrNCat(char *dst, const char *src, int count)
{
	int i;
	if (!dst || !src || count <= 0)
	{
		return dst;
	}
	for (i = 0; dst[i]; i++);
	for (;i < count && *src; i++, src++)
	{
		dst[i] = *src;
	}
	dst[i] = 0;
	return dst;
}

char * StrNCpy(char *dst, const char *src, int count)
{
	if (!dst || !src || count <= 0)
	{
		return dst;
	}
	*dst = 0;
	return StrNCat(dst, src, count);
}

char * StrCpy(char *dst, const char *src)
{
	char * p;
	if (!dst || !src)
	{
		return dst;
	}
	p = dst;
	while (*p++ = *src++);
	return dst;
}

char * StrCat(char *dst, const char *src)
{
	int i;
	if (!dst || !src)
	{
		return dst;
	}
	for (i = 0; dst[i]; i++);
	for (; *src; i++, src++)
	{
		dst[i] = *src;
	}
	dst[i] = 0;
	return dst;
}

void * MemCpy(void * dst, const void * src, int len)
{
	int i;
	if (!src || !dst || len <= 0)
	{
		return dst;
	}
	for (i = 0; i < len; i++)
	{
		((char *)dst)[i] = ((char *)src)[i];
	}
	return dst;
}

void *MemSet(void *dst, char c, int len)
{
	int i;
	if (!dst || len <= 0)
	{
		return dst;
	}
	for (i = 0; i < len; i++)
	{
		((char *)dst)[i] = c;
	}
	return dst;
}

// -------------------------------------------------------------

/* uppercased type of resource */
const char *NdpTypes[] =
{
	"SMBFS",
	NULL
}
;

/* Properties of supported resource types */

/* Properties of resource */
static const NDPROPERTYINFO smbProperties[] =
{
	{ND_PROP_STRING, 0, "WORKGROUP", ""},
	{ND_PROP_STRING, 0, "SERVER", ""},
	{ND_PROP_STRING, 0, "SHARE", ""},
	{ND_PROP_STRING, 0, "USER", "guest"},
	{ND_PROP_STRING, 0, "PASSWORD", ""},
        {ND_PROP_STRING, 0, "SPASSWORD",  ""},
	{ND_PROP_STRING, 0, "MASTER", "WORKGROUP"},
	{ ND_PROP_ULONG, 0, "MASTERTYPE", "1"},
	{ ND_PROP_ULONG, 0, "MEMLEN", "2"},
	{ND_PROP_STRING, 0, "LOGFILE", ""},
	{ ND_PROP_ULONG, 0, "LOGLEVEL", "0"},
	{ ND_PROP_ULONG, 0, "EASUPPORT", "1"},
	{ND_PROP_STRING, 0, NULL, NULL}
};


/* Exported array of properties */
const NDPROPERTYINFO *NdpPropertiesInfo[] =
{
	smbProperties
};


static PLUGINHELPERTABLE2L *ph;
static int ifL;

int APIENTRY NdpPluginLoad (PLUGINHELPERTABLE2L *pPHT)
{
	int rc;
	HPIPE pipe;
	unsigned long action;
	ph = pPHT;
	ifL = 0;
/*
	if (ph->cb < sizeof (PLUGINHELPERTABLE2))
	{
		return ERROR_INVALID_FUNCTION;
	}
*/
	if (ph->cb >= sizeof (PLUGINHELPERTABLE2L))
	{
		ifL = 1;
	}
	log("Working with %s bit fileio NDFS\n", ifL ? "64" : "32");
	return NO_ERROR;
}


int APIENTRY NdpPluginFree (void)
{
	return NO_ERROR;
}


void getfindinfo(Connection * pConn, FILEFINDBUF3 * stat, smbwrp_fileinfo * finfo)
{
	char * name = ph->fsphStrRChr(finfo->fname, '\\');
	if (name)
	{
		name++;
	}
	else
	{
		name = finfo->fname;
	}
	if (!*name)
	{
		name = pConn->pRes->srv.share_name;
	}
	StrNCpy(stat->achName, name, CCHMAXPATHCOMP - 1);
	stat->cbFile = finfo->size;
	stat->cbFileAlloc = stat->cbFile;
	stat->oNextEntryOffset = 0ul;
	stat->cchName = StrLen(stat->achName);
	stat->attrFile = (finfo->attr & 0x37);

    	fsphUnixTimeToDosDate(finfo->mtime, &stat->fdateLastWrite, &stat->ftimeLastWrite);
    	fsphUnixTimeToDosDate(finfo->ctime, &stat->fdateCreation, &stat->ftimeCreation);
    	fsphUnixTimeToDosDate(finfo->atime, &stat->fdateLastAccess, &stat->ftimeLastAccess);
}

int getfindinfoL(Connection * pConn, void * plist, smbwrp_fileinfo * finfo, ULONG ulAttribute, char * mask)
{
	FILESTATUS3L stat = {0};
	char * name = ph->fsphStrRChr(finfo->fname, '\\');
	if (name)
	{
		name++;
	}
	else
	{
		name = finfo->fname;
	}
	if (!*name)
	{
		name = pConn->pRes->srv.share_name;
	}
	if (mask && (!ph->fsphAttrMatch(ulAttribute, finfo->attr & 0x37) || !ph->fsphWildMatch(mask, name, ND_IGNORE_CASE)))
	{
		return 0;
	}

	stat.cbFile = finfo->size;
	stat.cbFileAlloc = stat.cbFile;
	stat.attrFile = (finfo->attr & 0x37);

    	fsphUnixTimeToDosDate(finfo->mtime, &stat.fdateLastWrite, &stat.ftimeLastWrite);
    	fsphUnixTimeToDosDate(finfo->ctime, &stat.fdateCreation, &stat.ftimeCreation);
    	fsphUnixTimeToDosDate(finfo->atime, &stat.fdateLastAccess, &stat.ftimeLastAccess);
	debug_printf( "fname %s\n", finfo->fname);
	debug_printf( "mtime %d %s", finfo->mtime, ctime( &finfo->mtime));
	debug_printf( "ftimeLastAccess %02d:%02d:%02d\n", stat.ftimeLastWrite.hours, stat.ftimeLastWrite.minutes, stat.ftimeLastWrite.twosecs*2);

	ph->fsphAddFile32L(plist, &stat, name, StrLen(name), finfo, sizeof(*finfo), 0);
	return 1;
}

static unsigned char fromhex (char c)
{
    if ('0' <= c && c <= '9')
    {
         return c - '0';
    }
    
    if ('A' <= c && c <= 'F')
    {
         return c - 'A' + 0xA;
    }
    
    if ('a' <= c && c <= 'f')
    {
         return c - 'a' + 0xA;
    }
    
    return 0;
}

static char tohex (unsigned char b)
{
    b &= 0xF;
    
    if (b <= 9)
    {
        return b + '0';
    }
    
    return 'A' + (b - 0xA);
}

static void decryptPassword (const char *pszCrypt, char *pszPlain)
{
    /* A simple "decryption", character from the hex value. */
    const char *s = pszCrypt;
    char *d = pszPlain;
    
    while (*s)
    {
        *d++ = (char)((fromhex (*s++) << 4) + fromhex (*s++));
    }
    
    *d++ = 0;
}

static void encryptPassword (const char *pszPlain, char *pszCrypt)
{
    /* A simple "encryption" encode each character as hex value. */
    const char *s = pszPlain;
    char *d = pszCrypt;
    
    while (*s)
    {
        *d++ = tohex ((*s) >> 4);
        *d++ = tohex (*s);
        s++;
    }
    
    *d++ = 0;
}

/* accept parameters in form
 * [filename][;name=filename]
 */
int initResource (Resource *pRes, NDPROPERTYHANDLE properties)
{
	int rc = NO_ERROR;
	unsigned long t;
	const unsigned char * q = NULL;
	int defaultPassword = 1;

	pRes->rootlevel = 0;
	*pRes->logfile = 0;
	pRes->loglevel = 0;
	pRes->easupport = 1;
#ifdef HAVE_KRB5_H
	pRes->krb5support = 1;
#else
	pRes->krb5support = 0;
#endif

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "WORKGROUP", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.workgroup, q, sizeof(pRes->srv.workgroup) - 1);
		pRes->rootlevel = 1;
	}

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "SERVER", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.server_name, q, sizeof(pRes->srv.server_name) - 1);
		pRes->rootlevel = 2;
	}

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "SHARE", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.share_name, q, sizeof(pRes->srv.share_name) - 1);
		pRes->rootlevel = 3;
	}

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "USER", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.username, q, sizeof(pRes->srv.username) - 1);
	}

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "PASSWORD", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.password, q, sizeof(pRes->srv.password) - 1);
		defaultPassword = 0;
	}

	t = 0, q = NULL;
        rc = ph->fsphQueryStringProperty (properties, "SPASSWORD", &q, &t);
        if (   rc == NO_ERROR
            && *q != '\0'
            && defaultPassword)
        {
            char p[1024];
            p[0] = 0;
        
            decryptPassword (q, p);
        
            if (*p)
            {
		StrNCpy(pRes->srv.password, p, sizeof(pRes->srv.password) - 1);
        
                /* clear the plain password */
                ph->fsphSetProperty (properties, "PASSWORD", "");
            }
        }
        else
        {
            char c[1024];
            encryptPassword (pRes->srv.password, c);
        
            ph->fsphSetProperty (properties, "SPASSWORD", c);
        
            // clear the plain password
            ph->fsphSetProperty (properties, "PASSWORD", "");
        }

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "MASTER", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->srv.master, q, sizeof(pRes->srv.master) - 1);
	}

	t = 0, q = NULL;
	rc = ph->fsphQueryStringProperty (properties, "LOGFILE", &q, &t);
	if (!rc && t && *q)
	{
		StrNCpy(pRes->logfile, q, sizeof(pRes->logfile) - 1);
	}

	t = 0;
	rc = ph->fsphQueryUlongProperty (properties, "LOGLEVEL", &t);
	if (!rc)
	{
		if (t > 9)
		{
			t = 9;
			rc = ERROR_INVALID_PARAMETER;
		}
		pRes->loglevel = t;
	}

	t = 0;
	rc = ph->fsphQueryUlongProperty (properties, "MASTERTYPE", &t);
	if (!rc)
	{
		if (t > 1)
		{
			rc = ERROR_INVALID_PARAMETER;
		}
		else
		{
			pRes->srv.ifmastergroup = t;
		}
	}

	t = 0;
	rc = ph->fsphQueryUlongProperty (properties, "EASUPPORT", &t);
	if (!rc)
	{
		if (t > 1)
		{
			rc = ERROR_INVALID_PARAMETER;
		}
		else
		{
			pRes->easupport = t;
		}
	}

#if 0
	t = 0;
	rc = ph->fsphQueryUlongProperty (properties, "MEMLEN", &t);
	if (!rc)
	{
		if (t <= (pRes->easupport ? 1 : 0) || t > 10)
		{
			rc = ERROR_INVALID_PARAMETER;
		}
		else
		{
			pRes->memlen = t * 65536;
		}
	}
#endif

	return rc;
}

int iftestpath(char * path)
{
	char * p = path;
	if (!path)
	{
		return 0;
	}
	while ((p = ph->fsphStrChr(p, 'A')) != NULL)
	{
		if (ph->fsphStrNCmp(p, "A.+,;=[].B", 10) == 0)
		{
			return 1;
		}
		p++;
	}
	return 0;
}

int pathparser(Resource *pRes, Connection * pConn, char * path, char * result)
{
	int rootlevel;
	int rc = NO_ERROR;
	if (!pRes || !path || !result)
	{
		return ERROR_INVALID_PARAMETER;
	}
	// handle special case when someone wants to test support of LFN or smth similar
	if (iftestpath(path))
	{
		StrCpy(result, "\\A.+,;=[].B");
		return NO_ERROR;
	}

	rootlevel = pRes->rootlevel;
	if (*path == '\\') path++;
#if 0 // FIXME
	if (rootlevel < 3)
	{
		char * p;
		int newlevel = 0;
		smbwrp_server * tmp = (smbwrp_server *)pConn->mem;
		MemCpy(tmp, &pConn->srv, sizeof(pConn->srv));
		if (rootlevel == 0)
		{
			p = ph->fsphStrChr(path, '\\');
			if (!p)
			{
				p = path + StrLen(path);
			}
			if (StrLen(tmp->workgroup) != p - path
				|| (p == path || ph->fsphStrNICmp(path, tmp->workgroup, p - path)))
			{
				StrNCpy(tmp->workgroup, path, p - path);
				tmp->workgroup[p - path] = 0;
				newlevel = 1;
			}
			path = *p == '\\' ? p + 1 : p;
			rootlevel = 1;
		}
		if (rootlevel == 1) // root path starts from server name
		{
			p = ph->fsphStrChr(path, '\\');
			if (!p)
			{
				p = path + StrLen(path);
			}
			if (StrLen(tmp->server_name) != p - path
				|| (p == path || ph->fsphStrNICmp(path, tmp->server_name, p - path)))
			{
				StrNCpy(tmp->server_name, path, p - path);
				tmp->server_name[p - path] = 0;
				newlevel = 1;
			}
			path = *p == '\\' ? p + 1 : p;
			rootlevel = 2;
		}
		if (rootlevel == 2) // root path starts from share name
		{
			p = ph->fsphStrChr(path, '\\');
			if (!p)
			{
				p = path + StrLen(path);
			}
			if (StrLen(tmp->share_name) != (p - path)
				|| (p == path || ph->fsphStrNICmp(path, tmp->share_name, p - path)))
			{
				StrNCpy(tmp->share_name, path, p - path);
				tmp->share_name[p - path] = 0;
				newlevel = 1;
			}
			path = *p == '\\' ? p + 1 : p;
		}
		if (newlevel)
		{
			// reconnect to server here
			unsigned long action;
			smb_request req = {0};
			smb_response resp = {0};

			req.request = SMBREQ_DISCONNECT;
			req.param = pConn->mem;
			req.length = 0;
			req.paramlen = 0;

			rc = _DosTransactNPipe(pConn, &req, sizeof(req), &resp, sizeof(resp), &action);

			req.request = SMBREQ_CONNECT;
			req.param = pConn->mem;
			req.length = pRes->memlen;
			req.paramlen = sizeof(pConn->srv);

			rc = _DosTransactNPipe(pConn, &req, sizeof(req), &resp, sizeof(resp), &action);
			if (rc || action < sizeof(resp) || resp.rc)
			{
				APIRET rc2;
				rc = rc ? rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
				MemCpy(tmp, &pRes->srv, sizeof(pRes->srv));
				rc2 = _DosTransactNPipe(pConn, &req, sizeof(req), &resp, sizeof(resp), &action);
				// TODO: what to do if the reconnect will fail ?
			}
			else
			{
				MemCpy(&pConn->srv, tmp, sizeof(pConn->srv));
			}
		}
	}
#endif
	StrCpy(result, "\\");
	StrNCat(result, path, CCHMAXPATH);

	return rc;
}


// -------------------------------------------------------------

/* check if the requested resource is available */
static int checkMountResource( Resource* pRes)
{
	int rc;
	unsigned long action;
	//smbwrp_server srv;
	cli_state* cli;
	smbwrp_file file;

	debug_printf("checkMountResource in tid#%d\n", _gettid());
	rc = smbwrp_connect( pRes, &cli);
	if (rc)
	    rc = (rc == 7 ? ERROR_BAD_DEV_TYPE : ERROR_ACCESS_DENIED);
	smbwrp_disconnect( pRes, cli);

	return rc;
}

int APIENTRY NdpMountResource (HRESOURCE *presource, int type, NDPROPERTYHANDLE properties)
{
	int rc = NO_ERROR;
	unsigned long objany = OBJ_ANY;
	Resource *pRes = NULL;

	log("NdpMountResource in\n");

	// init code
    	smbwrp_init();

	/* since samba plugin support only 1 type of resources we do not need */
	/* to check what the found type really is */
	pRes = malloc( sizeof(Resource));
	if (pRes == NULL)
	{
		rc = ERROR_NOT_ENOUGH_MEMORY;
	} 
	else
	{
		MemSet(pRes, 0, sizeof(Resource));
		//pRes->objany = objany;
		// parse init string
		rc = initResource (pRes, properties);
		// try to connect to resource (check type) only if thread!=1, so ndctl startup
		// is not slowed down by network connections.
		// ndctl does mounting on main thread (#1)
		// nd/ndpm do not use main thread
		if (!rc && _gettid()!=1)
		    rc = checkMountResource( pRes);
		if (!rc)
		{
			// store resource data
			*presource = (HRESOURCE)pRes;
		}
		else
		{
			free(pRes);
		}
	}
	log("NdpMountResource rc=%d\n", rc);
	return rc;
}

// -------------------------------------------------------------

int APIENTRY NdpFreeResource (HRESOURCE resource)
{
	Resource *pRes = (Resource *)resource;
	MemSet(&pRes->srv, 0, sizeof(pRes->srv));
	free(pRes);
	log("NdpFreeResource %d\n", NO_ERROR);
	return NO_ERROR;
}

// -------------------------------------------------------------

int APIENTRY NdpRsrcCompare (HRESOURCE resource, HRESOURCE resource2)
{
	Resource *pRes = (Resource *)resource;
	Resource *pRes2 = (Resource *)resource2;
	int rc = ND_RSRC_DIFFERENT;

	log("NdpRsrcCompare in\n");
	if (ph->fsphStrICmp(pRes->srv.server_name, pRes2->srv.server_name) == 0
		&& ph->fsphStrICmp(pRes->srv.share_name, pRes2->srv.share_name) == 0
		&& ph->fsphStrICmp(pRes->srv.username, pRes2->srv.username) == 0
		&& ph->fsphStrICmp(pRes->srv.workgroup, pRes2->srv.workgroup) == 0)
	{
		// resources are equal
		rc = ND_RSRC_EQUAL;
	}

	log("NdpRsrcCompare %d\n", rc);

	return rc;
}

int APIENTRY NdpRsrcUpdate (HRESOURCE resource, HRESOURCE resource2)
{
	// do nothing
	log("NdpRsrcUpdate %d\n", NO_ERROR);
	return NO_ERROR;
}

int APIENTRY NdpRsrcQueryInfo (HRESOURCE resource, ULONG *pulFlags, void *pdata, ULONG insize, ULONG *poutlen)
{
	Resource *pRes = (Resource *)resource;
	int rc = NO_ERROR;
	char s[4096];

	log("NdpRsrcQueryInfo in\n");

	switch (pRes->rootlevel)
	{
		case 0:
		{
			ph->fsph_snprintf(s, sizeof(s) - 1, "SMBFS%s \\\\@%s", ifL ? "64" : "32", pRes->srv.username);
		} break;
		case 1:
		{
			ph->fsph_snprintf(s, sizeof(s) - 1, "SMBFS%s %s: \\\\@%s", ifL ? "64" : "32", pRes->srv.workgroup, pRes->srv.username);
		} break;
		case 2:
		{
			ph->fsph_snprintf(s, sizeof(s) - 1, "SMBFS%s \\\\%s%s%s@%s", ifL ? "64" : "32", *pRes->srv.workgroup ? pRes->srv.workgroup : "", *pRes->srv.workgroup ? ":" : "", pRes->srv.server_name, pRes->srv.username);
		} break;
		default:
		{
			ph->fsph_snprintf(s, sizeof(s) - 1, "SMBFS%s \\\\%s%s%s\\%s@%s", ifL ? "64" : "32", *pRes->srv.workgroup ? pRes->srv.workgroup : "", *pRes->srv.workgroup ? ":" : "", pRes->srv.server_name, pRes->srv.share_name, pRes->srv.username);
		} break;
	}
	*poutlen = StrLen(s) + 1;
	if (*poutlen > insize)
	{
		rc = ERROR_BUFFER_OVERFLOW;
	}
	else
	{
		MemCpy(pdata, s, *poutlen);
	}

	log("NdpRsrcQueryInfo %d\n", rc);

	return rc;
}

int APIENTRY NdpRsrcQueryFSAttach (HRESOURCE resource, void *pdata, ULONG insize, ULONG *poutlen)
{
	ULONG ulDummy = 0;
	/* just return the resource info string */
	return NdpRsrcQueryInfo (resource, &ulDummy, pdata, insize, poutlen);
}

int APIENTRY NdpRsrcQueryFSAllocate (HRESOURCE resource, NDFSALLOCATE *pfsa)
{
	Resource *pRes = (Resource *)resource;
	int rc = NO_ERROR, rc1;
	unsigned long action = 0;
	smbwrp_file file;
	cli_state* cli;
	FSALLOCATE fsa;

	log("NdpRsrcQueryFSAllocate %08x\n", pfsa);

	if (!pfsa)
	{
		return NO_ERROR;
	}

	debug_printf("checkMountResource in tid#%d\n", _gettid());
	rc = smbwrp_connect( pRes, &cli);
	if (rc) 
	{
	    log("NdpCreateConnection failed rc=%d\n", rc);	
	    pfsa->cSectorUnit = 1;
	    pfsa->cUnit = 123456;
	    pfsa->cUnitAvail = 123456;
	    pfsa->cbSector = 2048;
	    rc = (rc == 7 ? ERROR_BAD_DEV_TYPE : ERROR_ACCESS_DENIED);
	    return rc;
	}

	rc = smbwrp_dskattr( cli, &fsa);
	if (rc)
	{
		pfsa->cSectorUnit = 1;
		pfsa->cUnit = 123456;
		pfsa->cUnitAvail = 123456;
		pfsa->cbSector = 2048;
		//rc = rc ? rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
	}
	else
	{
		pfsa->cSectorUnit = fsa.cSectorUnit;
		pfsa->cUnit = fsa.cUnit;
		pfsa->cUnitAvail = fsa.cUnitAvail;
		pfsa->cbSector = fsa.cbSector;
	}

	smbwrp_disconnect( pRes, cli);

	log("NdpRsrcQueryFSAllocate %d/%d (cUnit = %d/cUnitAvail = %d/cbSector = %d)\n", rc, rc1, pfsa->cUnit, pfsa->cUnitAvail, pfsa->cbSector);
	return rc;
}

// -------------------------------------------------------------

int APIENTRY NdpCreateConnection (HRESOURCE resource, HCONNECTION *pconn)
{
	int rc = 0;
	Resource * pRes = (Resource *)resource;
	unsigned long action;
	Connection *pConn = NULL;

	log("NdpCreateConnection in\n");

	pConn = malloc( sizeof(Connection));
	if (pConn == NULL)
	{
		rc =  ERROR_NOT_ENOUGH_MEMORY;
	}
	if (rc)
	{
		log("NdpCreateConnection ERROR_NOT_ENOUGH_MEMORY %d\n", rc);
		return rc;
	}
	MemSet(pConn, 0, sizeof(Connection));
	pConn->pRes = pRes;
	pConn->file.fd = -1;

	log("NdpCreateConnection send CONNECT\n");
	rc = smbwrp_connect( pRes, &pConn->cli);
	if (rc)
	{
		free(pConn);
		pConn = NULL;
		rc = (rc == 7 ? ERROR_BAD_DEV_TYPE : ERROR_INVALID_PARAMETER);
	}

	*pconn = (HCONNECTION)pConn;
	log("NdpCreateConnection %d\n", rc);
	return rc;
}

// -------------------------------------------------------------

int APIENTRY NdpFreeConnection (HCONNECTION conn)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc;

	log("NdpFreeConnection in\n");
	if (pConn->file.fd >= 0)
	{
		rc = smbwrp_close( pConn->cli, &pConn->file);
		pConn->file.fd = -1;
	}

	smbwrp_disconnect( pRes, pConn->cli);

	free(pConn);
	log("NdpFreeConnection %d\n", NO_ERROR);
	return NO_ERROR;
}

// -------------------------------------------------------------

/* 
 * NdpQueryPathInfo is the most important  function :) netdrive always calls
 * the function before every operation to find out the path status: does it exist, is it a file, does a
 *  parent directory exist, etc.
 * Plugin must return one of the following error codes:
 * NO_ERROR - path exists and the path information have been successfully retrieved.
 * ERROR_FILE_NOT_FOUND - all but the last component of the path exist and the 
 *    path without the last component is a directory. dir1_ok\dir2_ok\does_not_exist. 
 *    the wildcard can not exist, so the plugin returns FILE_NOT_FOUND, if the parent 
 *    directory exist.
 * ERROR_PATH_NOT_FOUND - any of not last path components does not exist, or all
 *    but the last component exist and is a file: \dir_ok\dir2_ok\file_ok\non_existing.
 * ERROR_REM_NOT_LIST - resource is temporarily unavailable for some reasons.
 * Any other error codes means an internal plugin error, not related to the status 
 * of the path queried.
 */
int APIENTRY NdpQueryPathInfo (HCONNECTION conn, void *plist, char *szPath)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	smbwrp_fileinfo finfo;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};
	int retry = 0;

		log("NdpQueryPathInfo in <%s>, retry = %d\n", szPath, retry);
	
		// is wildcard is specified, we suppose parent dir exist, so exit immediately
		if (ph->fsphStrChr(szPath, '*') || ph->fsphStrChr(szPath, '?'))
		{
			return ERROR_FILE_NOT_FOUND;
		}


		do {

			rc = pathparser(pRes, pConn, szPath, path);
			log("NdpQueryPathInfo pathparser for <%s> rc=%d\n", path, rc);
			switch (rc)
			{
				case NO_ERROR :
				case ERROR_FILE_NOT_FOUND:
				case ERROR_PATH_NOT_FOUND:
				case ERROR_ACCESS_DENIED:
				case ERROR_INVALID_PARAMETER:
				{
					break;
				}
				default :
				{	
					rc = ERROR_PATH_NOT_FOUND;
				}
			}
			if (rc)
			{
				break;
			}
			StrNCpy(finfo.fname, path, sizeof(finfo.fname) - 1);
			log("NdpQueryPathInfo smbwrp_getattr for <%s>\n", path);
			rc = smbwrp_getattr( &pRes->srv, pConn->cli, &finfo);
			if (rc)
			{
				// remote server not available for first time?
				if (rc == ERROR_REM_NOT_LIST && retry == 0)
				{
				    // free current cli resources
				    smbwrp_disconnect( pRes, pConn->cli);
				    // reconnect
				    smbwrp_connect( pRes, &pConn->cli);
				    // try file list again
				    rc = smbwrp_getattr( &pRes->srv, pConn->cli, &finfo);
				    log("NdpQueryPathInfo remote connection lost, retry rc = %d\n", rc);
				}
				switch (rc)
				{
					case NO_ERROR :
					case ERROR_FILE_NOT_FOUND:
					case ERROR_PATH_NOT_FOUND:
					case ERROR_ACCESS_DENIED:
					case ERROR_INVALID_PARAMETER:
					case ERROR_REM_NOT_LIST:
					break;
					default :
					{
						rc = ERROR_PATH_NOT_FOUND;
					}
				}
			}
			else
			{
				finfo.easize = -1;
				getfindinfoL(pConn, plist, &finfo, 0, NULL);
			}
			if (rc == ERROR_FILE_NOT_FOUND)
			{
				// now try the upper path
				char * p = ph->fsphStrChr(finfo.fname, '\\');
				if (p && p > finfo.fname)
				{
					*p = 0;
					rc = smbwrp_getattr( &pRes->srv, pConn->cli, &finfo);
					if (rc)
					{	
					    log("NdpQueryPathInfo upper path in <%s>, retry = %d\n",  finfo.fname, retry);
					    rc = rc ? ERROR_PATH_NOT_FOUND : ERROR_INVALID_PARAMETER;
					}
				}
			}
		} while (0);
		log("NdpQueryPathInfo <%s> (%s) %d\n", szPath, path, rc);

	return rc;
}

// -------------------------------------------------------------

int APIENTRY NdpFindStart (HCONNECTION conn, void *plist, NDFILEINFOL *pfiparent, char *szPath, ULONG ulAttribute)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = NO_ERROR, count = 0;
	unsigned long action;
	char *mask = "*";
	char dir[CCHMAXPATH+1] = {0};
	char path[CCHMAXPATH+1] = {0};
	smbwrp_fileinfo * data;
	NDPATHELEMENT *pel = ph->fsphNameElem(0);
	filelist_state state;
	char * p;

	debug_printf("NdpFindStart in\n");

		StrNCpy(dir, szPath, sizeof(dir) - 1);
		if (pel)
		{
			mask = pel->name;
			dir[StrLen(szPath) - pel->length] = 0;
		}
		action = StrLen(dir) - 1;
		if (dir[action] == '\\')
		{
			dir[action] = 0;
		}
		rc = pathparser(pRes, pConn, dir, path);
		if (rc)
		{
			return rc;
		}
		action = StrLen(path) - 1;
		if (path[action] != '\\')
		{
			StrNCat(path, "\\", sizeof(path) - 1);
		}
		StrCpy(dir, path);
		StrNCat(path, mask, sizeof(path) - 1);

		// this structure will be used by libsmb callbacks, so we store here all we need 
		// to fill netdrive structures 
		state.pConn = pConn;
		state.plist = plist;
		state.ulAttribute = ulAttribute;
		strcpy( state.dir, dir);
		strcpy( state.dir_mask, mask);
		strcpy( state.mask, path);
		p = getlastslash(state.mask);
		if (p)
		{
			*(p + 1) = '*';
			*(p + 2) = 0;
		} 
		else
		{
			strcpy(state.mask, "\\*");
		}
		rc = smbwrp_filelist( &pRes->srv, pConn->cli, &state);
		// we need to handle reconnection also here, because NdpQueryPathInfo 
		// could be called with '*' and exit then immediately (without calling libsmb) 
		if (rc == ERROR_REM_NOT_LIST)
		{
		    // free current cli resources
		    smbwrp_disconnect( pRes, pConn->cli);
		    // reconnect
		    smbwrp_connect( pRes, &pConn->cli);
		    // try file list again next loop
		    rc = smbwrp_filelist( &pRes->srv, pConn->cli, &state);
		    log("NdpFindStart remote connection lost, retry rc = %d\n", rc);
		}

	log("NdpFindStart <%s> (%s) cnt %d %d\n", szPath, path, count, rc);

	return rc;
}

int APIENTRY NdpDeletePathInfo (HRESOURCE resource, NDFILEINFOL *pfi)
{
//	log("NdpDeletePathInfo %d\n", 0);
	return NO_ERROR;
}

int APIENTRY NdpRefresh (HCONNECTION conn, char *path, int tree)
{
	log("NdpRefresh <%s> %d\n", path, 0);
	return NO_ERROR;
}

int APIENTRY NdpDiscardResourceData (HRESOURCE resource, NDDATABUF *pdatabuf)
{
    // The plugin do not have to deallocate anything
    // because resource data did not contain any pointers
    // to plugins data.
    // Data stored by fsphSetResourceData will be
    // deallocated by NetDrive.

	log("NdpDicardresourceData %d\n", 0);
	return NO_ERROR;
}

int APIENTRY NdpSetPathInfo (HCONNECTION conn, NDFILEINFOL *pfi, char *szPathName)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};
	smbwrp_fileinfo finfo;

	debug_printf("NdpSetPathInfo in FIXME\n");

	do {
		rc = pathparser(pRes, pConn, szPathName, path);
		if (rc)
		{
			break;
		}

		MemSet(&finfo, 0, sizeof(finfo));

		StrNCpy(finfo.fname, path, sizeof(finfo.fname) - 1);
		fsphDosDateToUnixTime(pfi->stat.fdateLastWrite, pfi->stat.ftimeLastWrite, &(finfo.mtime));
		finfo.attr = pfi->stat.attrFile & 0x37;
		rc = smbwrp_setattr(pConn->cli, &finfo);
	} while (0);
	log("NdpSetPathInfo <%s> (%s) %d\n", szPathName, path, rc);

	return rc;
}

int buildFEALIST(FEALIST *pFEASrc, GEALIST *pGEAList, FEALIST *pFEAList)
{
	int rc = 0;
	FEA * pfea;
	FEA * pfeadest;
	unsigned long size, done = sizeof(pFEAList->cbList), dsize, ddone = sizeof(pFEAList->cbList);

	size = pFEASrc->cbList;
	pfea = pFEASrc->list;
	pfeadest = pFEAList->list;
	dsize = pFEAList->cbList;
//log("buildFEALIST in destsize %d srcsize %d pGEAList=%08x pGEAList->cbList=%d\n", dsize, ddone, size, pGEAList, pGEAList ? pGEAList->cbList : 0);
	while (done < size)
	{
		char * name = (char *)(pfea + 1);
		int insert = 1;
		if (pGEAList && pGEAList->cbList > sizeof(pGEAList->cbList))
		{
			GEA * pgea = pGEAList->list;
			unsigned long size = pGEAList->cbList - sizeof(pGEAList->cbList), done = 0;
			insert = 0;
			while (done < size)
			{
//log("comp <%s> <%s>\n", name, pgea->szName);
				if (!ph->fsphStrNCmp(name, pgea->szName, pgea->cbName))
				{
					insert = 1;
					break;
				}
				done += pgea->cbName + 2;
				pgea = (GEA *)((char *)pgea + pgea->cbName + 2);
			}
		}
		if (insert)
		{
			ddone += sizeof(FEA) + pfea->cbName + 1 + pfea->cbValue;
			if (ddone <= dsize)
			{
				pfeadest->cbName = pfea->cbName;
				pfeadest->cbValue = pfea->cbValue;
				pfeadest->fEA = 0;
				StrCpy((char *)(pfeadest + 1), name);
				MemCpy((char *)(pfeadest + 1) + pfea->cbName + 1, (char *)(pfea + 1) + pfea->cbName + 1, pfea->cbValue);
				pfeadest = (FEA *)((char *)pFEAList + ddone);
			}
		}
		done += sizeof(FEA) + pfea->cbName + 1 + pfea->cbValue;
//log("buuildfea <%s> insert=%d pfea->cbName=%d pfea->cbValue=%d srcdone=%d destdone=%d pfeadest=%08x pfea=%08x\n", name, insert, pfea->cbName, pfea->cbValue, done, ddone, pfeadest, pfea);
		pfea = (FEA *)((char *)pFEASrc + done);
	}
	pFEAList->cbList = ddone;
	if (ddone > dsize && dsize > sizeof(pFEAList->cbList))
	{
		rc = ERROR_BUFFER_OVERFLOW;
	}
	log("buildFEALIST rc=%d destsize=%d destdone=%d srcsize=%d pGEAList=%08x\n", rc, dsize, ddone, size, pGEAList);
	return rc;
}

int APIENTRY NdpEAQuery (HCONNECTION conn, GEALIST *pGEAList, NDFILEINFOL *pfi, FEALIST *pFEAList)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char * path = NULL;
	FEALIST * pFEASrc;
	NDDATABUF fdata = {0};
	smbwrp_fileinfo *finfo;
	char	pBuffer[64*1024];

	if (!pfi || !pfi->pszName || !pFEAList)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	rc = ph->fsphGetFileInfoData(pfi, &fdata, 0);
	if (rc || !fdata.ulSize || !fdata.pData)
	{
		log("NdpEAQuery: ph->fsphGetFileInfoData = %d/%d %08x\n", rc, fdata.ulSize, fdata.pData);
		return ERROR_EAS_NOT_SUPPORTED;
	}
	finfo = (smbwrp_fileinfo *)fdata.pData;
	path = finfo->fname;

	log("NdpEAQuery in <%s> %08x %d\n", path, pGEAList, pGEAList ? pGEAList->cbList : 0);

	do {
		rc = smbwrp_listea( pConn->cli, path, pBuffer, sizeof( pBuffer));
		pFEASrc = (FEALIST*) pBuffer;
		if (rc)
		{
			//rc = pConn->rc ? pConn->rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
			switch (rc)
			{
				case ERROR_FILE_NOT_FOUND :
				case ERROR_PATH_NOT_FOUND :
				{
					pFEAList->cbList = sizeof(pFEAList->cbList);
					rc = NO_ERROR;
				} break;
				case ERROR_BUFFER_OVERFLOW :
				{
					pFEAList->cbList = pFEASrc->cbList;
				} break;
				default :
				{
					rc = ERROR_EAS_NOT_SUPPORTED;
				}
			}
		}
		else
		{
			rc = buildFEALIST((FEALIST *)pFEASrc, pGEAList, pFEAList);
		}
	} while (0);
	log("NdpEAQuery <%s> %d %d %d\n", pfi->pszName, rc, pFEASrc->cbList, pFEAList->cbList);

	return rc;
}

int APIENTRY NdpEASet (HCONNECTION conn, FEALIST *pFEAList, NDFILEINFOL *pfi)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	char * path;
	unsigned long action;
	NDDATABUF fdata = {0};
	smbwrp_fileinfo *finfo;

	log("NdpEASet in\n");

	if (!pfi || !pfi->pszName || !pFEAList || pFEAList->cbList <= sizeof(long))
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	rc = ph->fsphGetFileInfoData(pfi, &fdata, 0);
	if (rc || !fdata.ulSize || !fdata.pData)
	{
		log("NdpEASet: ph->fsphGetFileInfoData = %d/%d/%08x\n", rc, fdata.ulSize, fdata.pData);
		return ERROR_EAS_NOT_SUPPORTED;
	}
	finfo = (smbwrp_fileinfo *)fdata.pData;
	path = finfo->fname;

	do {
		// got FEA there
		FEA * pfea;
		unsigned long done = sizeof(long);
		pfea = pFEAList->list;
		while (done < pFEAList->cbList)
		{
			rc = smbwrp_setea(pConn->cli, path, (char*)(pfea + 1), pfea->cbValue ? (char *)(pfea + 1) + pfea->cbName + 1: NULL, pfea->cbValue);
			if (rc)
			{
				break;
			}
			pfea = (FEA *)((char *)(pfea + 1) + pfea->cbName + 1 + pfea->cbValue);
			done += sizeof(FEA) + pfea->cbName + 1 + pfea->cbValue;
		}
	} while (0);
	log("NdpEASet %d\n", rc);

	return rc;
}

int APIENTRY NdpEASize (HCONNECTION conn, NDFILEINFOL *pfi, ULONG *pulEASize)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char * path = NULL;
	FEALIST * pfealist;
	NDDATABUF fdata = {0};
	smbwrp_fileinfo *finfo;
	char	pBuffer[64*1024];
	int easize;

	if (!pfi || !pulEASize)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	rc = ph->fsphGetFileInfoData(pfi, &fdata, 0);
	if (rc || !fdata.ulSize || !fdata.pData)
	{
		log("NdpEASize: ph->fsphGetFileInfoData = %d/%d/%08x\n", rc, fdata.ulSize, fdata.pData);
		return ERROR_EAS_NOT_SUPPORTED;
	}
	finfo = (smbwrp_fileinfo *)fdata.pData;
	easize = finfo->easize;
	finfo->easize = -1;
	path = finfo->fname;
	if (easize >= 0)
	{
		*pulEASize = easize;
		log("NdpEASize <%s> cached %d\n", path, easize);
		return NO_ERROR;
	}

	log("NdpEASize in <%s> \n", path);

	do {
		rc = smbwrp_listea(pConn->cli, path, pBuffer, sizeof( pBuffer));
		pfealist = (FEALIST*)pBuffer;
		if (rc)
		{
			//rc = pConn->rc ? pConn->rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
			switch (rc)
			{
				case ERROR_FILE_NOT_FOUND :
				case ERROR_PATH_NOT_FOUND :
				{
					pfealist->cbList = sizeof(pfealist->cbList);
				} /* Fall through */
				case ERROR_BUFFER_OVERFLOW :
				{
					rc = NO_ERROR;
				} break;
				default :
				{
					rc = ERROR_EAS_NOT_SUPPORTED;
				}
			}
		}
		*pulEASize = pfealist->cbList;
	} while (0);
	log("NdpEASize <%s> %d %d\n", pfi->pszName, *pulEASize, rc);

	return rc;
}

int APIENTRY NdpSetCurrentDir (HCONNECTION conn, NDFILEINFOL *pfi, char *szPath)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};
	
	log("NdpSetCurrentDir in\n");

	do {
		rc = pathparser(pRes, pConn, szPath, path);
		if (rc)
		{
			break;
		}

		rc = smbwrp_chdir(&pRes->srv, pConn->cli, path);
	} while (0);
	log("NdpSetCurrentDir <%s> (%s) %d\n", szPath, path, rc);

	return rc;
}

int APIENTRY NdpCopy (HCONNECTION conn, NDFILEINFOL *pfiDst, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc, ULONG ulOption)
{
	log("NdpCopy <%s> -> <%s> %d\n", szSrc, szDst, ERROR_CANNOT_COPY);
	return ERROR_CANNOT_COPY;
}

int APIENTRY NdpCopy2 (HCONNECTION conn, HRESOURCE resDst, NDFILEINFOL *pfiDst, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc, ULONG ulOption)
{
	log("NdpCopy2 <%s> -> <%s> %d\n", szSrc, szDst, ERROR_CANNOT_COPY);
	return ERROR_CANNOT_COPY;
}

int APIENTRY NdpForceDelete (HCONNECTION conn, NDFILEINFOL *pfi, char *szFile)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};

	log("NdpForceDelete in\n");

	do {
		rc = pathparser(pRes, pConn, szFile, path);
		if (rc)
		{
			break;
		}

		rc = smbwrp_unlink(pConn->cli, path);
	} while (0);
	log("NdpForceDelete <%s> (%s) %d\n", szFile, path, rc);

	return rc;
}

int APIENTRY NdpCreateDir (HCONNECTION conn, NDFILEINFOL *pfiparent, char *szDirName, FEALIST *pFEAList)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};

	log("NdpCreateDir in\n");

	do {
		rc = pathparser(pRes, pConn, szDirName, path);
		if (rc)
		{
			break;
		}

		rc = smbwrp_mkdir(pConn->cli, path);
	} while (0);
	log("NdpCreateDir <%s> (%s) %d\n", szDirName, path, rc);

	return rc;
}

int APIENTRY NdpDeleteDir (HCONNECTION conn, NDFILEINFOL *pfi, char *szDir)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};

	log("NdpDeleteDir in\n");

	do {
		rc = pathparser(pRes, pConn, szDir, path);
		if (rc)
		{
			break;
		}

		rc = smbwrp_rmdir(pConn->cli, path);
	} while (0);
	log("NdpDeleteDir <%s> (%s) %d\n", szDir, path, rc);

	return rc;
}

int APIENTRY NdpMove (HCONNECTION conn, NDFILEINFOL *pfiDst, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char src[CCHMAXPATH+1] = {0};
	int l1, l2;
	char * p = szDst;

	log("NdpMove in from <%s> to <%s>\n", szSrc, szDst);

	do
	{
		rc = pathparser(pRes, pConn, szSrc, src);
		if (rc)
		{
			break;
		}
		l1 = StrLen(szSrc);
		l2 = StrLen(src);
		if (l1 > l2)
		{
			if (ph->fsphStrNICmp(szSrc, szDst, l1 - l2))
			{
				// the file moved accross different shares or servers or workgroups
				rc = ERROR_WRITE_PROTECT;
				break;
			}
			p = szDst + l1 - l2 + 1;
		}
		//pConn->mem[CCHMAXPATH + 1] = '\\';
		rc = smbwrp_rename(pConn->cli, src, p);
	} while (0);
	log("NdpMove <%s> -> <%s> (%s) %d\n", szSrc, szDst, src, rc);

	return rc;
}

int APIENTRY NdpMove2 (HCONNECTION conn, HRESOURCE resDst, NDFILEINFOL *pfiDst, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc)
{
	log("NdpMove2 <%s> -> <%s> %d\n", szSrc, szDst, ERROR_WRITE_PROTECT);
	return ERROR_WRITE_PROTECT;
}


int APIENTRY NdpChangeCase (HCONNECTION conn, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc, char *szNewName, ULONG ulNameLen)
{
	return NdpMove (conn, pfiSrc, szDst, pfiSrc, szSrc);
}

int APIENTRY NdpRename (HCONNECTION conn, char *szDst, NDFILEINFOL *pfiSrc, char *szSrc, char *szNewName, ULONG ulNameLen)
{
	return NdpMove (conn, pfiSrc, szDst, pfiSrc, szSrc);
}

int smbopen(Connection *pConn, char *szFileName, int flags, ULONG ulOpenMode, ULONG ulAttribute, FEALIST *pFEAList)
{
	Resource *pRes = pConn->pRes;
	unsigned long action;
	int rc = 0;
	char path[CCHMAXPATH+1] = {0};

	log("smbopen in %d\n", pConn->file.fd);

	do {
		if (pConn->file.fd > 0)
		{
			rc = ERROR_TOO_MANY_OPEN_FILES;
			break;
		}

		rc = pathparser(pRes, pConn, szFileName, path);
		if (rc)
		{
			break;
		}

		StrNCpy(pConn->file.fullname, szFileName, sizeof(pConn->file.fullname) - 1);
		StrNCpy(pConn->file.fname, path, sizeof(pConn->file.fname) - 1);
		flags |= O_BINARY;
		switch (ulOpenMode & 3)
		{
			case OPEN_ACCESS_READONLY : flags |= O_RDONLY; break;
			case OPEN_ACCESS_WRITEONLY : flags |= O_WRONLY; break;
			case OPEN_ACCESS_READWRITE : flags |= O_RDWR; break;
			default : flags |= O_RDWR;
		}
		pConn->file.openmode = flags;
		pConn->file.openattr = ulAttribute & 0x37;
		pConn->file.denymode = (ulOpenMode & 0x70) >> 4;
		rc = smbwrp_open(pConn->cli, &pConn->file);
	} while (0);
	log("smbopen <%s> (%s) %08x %08x %08x %d. file = %d\n", szFileName, path, flags, ulOpenMode, ulAttribute, rc, pConn->file.fd);
	if (!rc && pFEAList)
	{
		int rc1 = NdpFileEASet((HCONNECTION)pConn, (NDFILEHANDLE)0, pFEAList);
		log("smbopen NdpFileEASet %d. pFEAList->cbList %d\n", rc1, pFEAList->cbList);
	}

	return rc;
}

int APIENTRY NdpOpenReplace (HCONNECTION conn, NDFILEINFOL *pfi, NDFILEHANDLE *phandle, char *szFileName, ULONG ulSize, ULONG ulOpenMode, ULONG ulAttribute, FEALIST *pFEAList)
{
	return smbopen((Connection *)conn, szFileName, O_TRUNC, ulOpenMode, ulAttribute, pFEAList);
}

int APIENTRY NdpOpenReplaceL(HCONNECTION conn, NDFILEINFO *pfi, NDFILEHANDLE *phandle, char *szFileName, LONGLONG llSize, ULONG ulOpenMode, ULONG ulAttribute, FEALIST *pFEAList)
{
	return smbopen((Connection *)conn, szFileName, O_TRUNC, ulOpenMode, ulAttribute, pFEAList);
}

int APIENTRY NdpOpenCreate (HCONNECTION conn, NDFILEINFOL *pfiparent, NDFILEHANDLE *phandle, char *szFileName, ULONG ulSize, ULONG ulOpenMode, ULONG ulAttribute, FEALIST *pFEAList)
{
//	return smbopen((Connection *)conn, szFileName, O_CREAT, ulOpenMode, ulAttribute);
	return smbopen((Connection *)conn, szFileName, O_CREAT | O_EXCL, ulOpenMode, ulAttribute, pFEAList);
}

int APIENTRY NdpOpenCreateL(HCONNECTION conn, NDFILEINFO *pfiparent, NDFILEHANDLE *phandle, char *szFileName, LONGLONG llSize, ULONG ulOpenMode, ULONG ulAttribute, FEALIST *pFEAList)
{
	return smbopen((Connection *)conn, szFileName, O_CREAT | O_EXCL, ulOpenMode, ulAttribute, pFEAList);
}

int APIENTRY NdpOpenExisting (HCONNECTION conn, NDFILEINFOL *pfi, NDFILEHANDLE *phandle, char *szFileName, ULONG ulOpenMode, USHORT *pfNeedEA)
{
	if (pfNeedEA) *pfNeedEA = 0; // wtf is this ?
	return smbopen((Connection *)conn, szFileName, 0, ulOpenMode, 0, NULL);
}

int APIENTRY NdpSetFileAttribute (HCONNECTION conn, NDFILEINFOL *pfi, char *szFileName, USHORT usAttr)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;

	smbwrp_fileinfo finfo;
	char path[CCHMAXPATH+1] = {0};

	log("NdpSetFileAttribute in\n");
	do {
		rc = pathparser(pRes, pConn, szFileName, path);
		if (rc)
		{
			break;
		}

		MemSet(&finfo, 0, sizeof(finfo));
		StrNCpy(finfo.fname, path, sizeof(finfo.fname) - 1);
		finfo.attr = usAttr & 0x37;
		rc = smbwrp_setattr(pConn->cli, &finfo);
	} while (0);
	log("NdpSetFileAttribute <%s> (%s) %04x %d\n", szFileName, path, usAttr, rc);

	return rc;
}

int APIENTRY NdpFlush (HRESOURCE resource)
{
	log("NdpFlush %d\n", ERROR_NOT_SUPPORTED);
	return ERROR_NOT_SUPPORTED;
}

int APIENTRY NdpIOCTL (int type, HRESOURCE resource, char *path, int function, void *in, ULONG insize, PULONG poutlen)
{
    log("NdpIOCTL <%s> %d\n", path, function);

    if (in && insize > 4096)
    {
	char out[4096];
	sprintf (out, "SAMBA IOCTL function = %d, parms [%s] insize = %d, *poutlen = %d", function, in, insize, *poutlen);
	*poutlen = strlen(out);
	strcpy (in, out);
	return NO_ERROR;
    }

    return ERROR_NOT_SUPPORTED;
}

int APIENTRY NdpFileQueryInfo (HCONNECTION conn, NDFILEHANDLE handle, void *plist)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	smbwrp_fileinfo finfo;

	debug_printf("NdpFileQueryInfo in\n");
	do {
		if (pConn->file.fd < 0 || !*pConn->file.fname)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		StrNCpy(finfo.fname, pConn->file.fname, sizeof(finfo.fname) - 1);
		rc = smbwrp_fgetattr(pConn->cli, &pConn->file, &finfo);
		if (!rc)
		{
			finfo.easize = -1;
			getfindinfoL(pConn, plist, &finfo, 0, NULL);
		}
	} while (0);
	log("NdpFileQueryInfo <%s> %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, rc);

	return rc;
}

int APIENTRY NdpFileEAQuery (HCONNECTION conn, NDFILEHANDLE handle, GEALIST *pGEAList, FEALIST *pFEAList)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char pBuffer[64*1024];
	FEALIST * pFEASrc;

	if (!pFEAList)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	log("NdpFileEAQuery in <%s>/%d pGEAList=%08x\n", pConn->file.fname, pConn->file.fd, pGEAList);
	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		rc = smbwrp_flistea(pConn->cli, &pConn->file, pBuffer, sizeof( pBuffer));
		pFEASrc = (FEALIST *) pBuffer;
		if (rc)
		{
			//rc = pConn->rc ? pConn->rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
			switch (rc)
			{
				case ERROR_FILE_NOT_FOUND :
				case ERROR_PATH_NOT_FOUND :
				{
					pFEAList->cbList = sizeof(pFEAList->cbList);
					rc = NO_ERROR;
				} break;
				case ERROR_BUFFER_OVERFLOW :
				{
					pFEAList->cbList = pFEASrc->cbList;
				} break;
				default :
				{
					rc = ERROR_EAS_NOT_SUPPORTED;
				}
			}
		}
		else
		{
			rc = buildFEALIST(pFEASrc, pGEAList, pFEAList);
		}
	} while (0);
	log("NdpFileEAQuery out <%s>/%d pFEASrc->cbList=%d pFEAList->cbList=%d rc=%d\n", pConn->file.fname, pConn->file.fd, pFEASrc->cbList, pFEAList->cbList, rc);

	return rc;
}

int APIENTRY NdpFileEASet (HCONNECTION conn, NDFILEHANDLE handle, FEALIST *pFEAList)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;

	log("NdpFileEASet in\n");

	if (!pFEAList || pFEAList->cbList <= sizeof(long))
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	do {
		// got FEA there
		FEA * pfea;
		unsigned long done = sizeof(long);
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}

		pfea = pFEAList->list;
		while (done < pFEAList->cbList)
		{
			rc = smbwrp_fsetea(pConn->cli, &pConn->file, (char *)(pfea + 1), pfea->cbValue ? (char *)(pfea + 1) + pfea->cbName + 1: NULL, pfea->cbValue);
			if (rc)
			{
				break;
			}
			pfea = (FEA *)((char *)(pfea + 1) + pfea->cbName + 1 + pfea->cbValue);
			done += sizeof(FEA) + pfea->cbName + 1 + pfea->cbValue;
		}

	} while (0);
	log("NdpFileEASet %d\n", rc);

	return rc;
}

int APIENTRY NdpFileEASize (HCONNECTION conn, NDFILEHANDLE handle, ULONG *pulEASize)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;
	char path[CCHMAXPATH+1] = {0};
	FEALIST * pFEAList;
	char pBuffer[64*1024];

	if (!pulEASize)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}
	if (!pRes->easupport)
	{
		return ERROR_EAS_NOT_SUPPORTED;
	}

	log("NdpFileEASize in <%s>/%d \n", pConn->file.fname, pConn->file.fd);
	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		rc = smbwrp_flistea(pConn->cli, &pConn->file, pBuffer, sizeof(pBuffer));
		pFEAList = (FEALIST*) pBuffer;
		if (rc)
		{
			//rc = pConn->rc ? pConn->rc : (resp.rc ? resp.rc : ERROR_INVALID_PARAMETER);
			switch (rc)
			{
				case ERROR_FILE_NOT_FOUND :
				case ERROR_PATH_NOT_FOUND :
				{
					pFEAList->cbList = sizeof(pFEAList->cbList);
				} /* Fall through */
				case ERROR_BUFFER_OVERFLOW :
				{
					rc = NO_ERROR;
				} break;
				default :
				{
					rc = ERROR_EAS_NOT_SUPPORTED;
				}
			}
		}
		*pulEASize = pFEAList->cbList;
	} while (0);
	log("NdpFileEASize %d %d\n", *pulEASize, rc);

	return rc;
}

int APIENTRY NdpFileSetInfo (HCONNECTION conn, NDFILEHANDLE handle, NDFILEINFOL *pfi)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action, attrFile;

	debug_printf("NdpFileSetInfo in\n");
	do {
		if (pConn->file.fd < 0 || !*pConn->file.fname)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		attrFile = pfi->stat.attrFile;
		// deferred setinfo - on closing the file
		pConn->file.openattr = attrFile;
		fsphDosDateToUnixTime(pfi->stat.fdateLastWrite, pfi->stat.ftimeLastWrite, &(pConn->file.mtime));
		debug_printf("NdpFileSetInfo mtime %d\n", pConn->file.mtime);
	} while (0);
	log("NdpFileSetInfo <%s> %08x %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, attrFile, rc);

	return NO_ERROR;
}

int APIENTRY NdpFileSetFilePtrL(HCONNECTION conn, NDFILEHANDLE handle, LONGLONG llOffset, ULONG ulMethod, LONGLONG *pllActual)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;

	log("NdpFileSetFilePtrl in\n");

	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}

		rc = smbwrp_lseek(pConn->cli, &pConn->file, ulMethod, llOffset);
		if (!rc)
			*pllActual = pConn->file.offset;

	} while (0);
	log("NdpFileSetFilePtrL <%s> %lld %lu %lld %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, llOffset, ulMethod, *pllActual, rc);

	return rc;
}

int APIENTRY NdpFileSetFilePtr (HCONNECTION conn, NDFILEHANDLE handle, LONG lOffset, ULONG ulMethod, ULONG *pulActual)
{
	LONGLONG llActual;
	int rc = NdpFileSetFilePtrL(conn, handle, lOffset, ulMethod, &llActual);
	*pulActual = llActual & 0xFFFFFFFF;
	log("NdpFileSetFilePtr %ld %lu %ld %d\n", lOffset, ulMethod, *pulActual, rc);
	return rc;
}

int APIENTRY NdpFileClose (HCONNECTION conn, NDFILEHANDLE handle)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;

	log("NdpFileClose in %d <%s>\n", pConn->file.fd, pConn->file.fd < 0 ? "!null!" : pConn->file.fname);

	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}

		rc = smbwrp_close(pConn->cli, &pConn->file);

	} while (0);
	log("NdpFileClose %d %d\n", pConn->file.fd, rc);

	pConn->file.fd = -1;
	return rc;
}

int APIENTRY NdpFileCommit (HCONNECTION conn, NDFILEHANDLE handle)
{
	log("NdpFileCommit %d\n", NO_ERROR);
	return NO_ERROR;
}


int APIENTRY NdpFileNewSize (HCONNECTION conn, NDFILEHANDLE handle, ULONG ulLen)
{
	int rc = NdpFileNewSizeL(conn, handle, ulLen);
	log("NdpFileNewSize %ld %d\n", ulLen, rc);
	return rc;
}

int APIENTRY NdpFileNewSizeL(HCONNECTION conn, NDFILEHANDLE handle, LONGLONG llLen)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long action;

	log("NdpFileNewSizeL in\n");

	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}

		rc = smbwrp_setfilesize(pConn->cli, &pConn->file, llLen); 

	} while (0);
	log("NdpFileNewSizeL <%s> %lld %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, llLen, rc);

	return rc;
}

int APIENTRY NdpFileRead (HCONNECTION conn, NDFILEHANDLE handle, void *pBuffer, ULONG ulRead, ULONG *pulActual)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long done = 0;
	unsigned long onedone;
	unsigned long action;

	log("NdpFileRead in\n");

	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		rc = smbwrp_read(pConn->cli, &pConn->file, pBuffer, ulRead, pulActual);
		//*pulActual = ulRead;
		//DosSleep(0);

	} while (0);
	log("NdpFileRead <%s> %lu %lu %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, ulRead, *pulActual, rc);

	return rc;
}

int APIENTRY NdpFileWrite (HCONNECTION conn, NDFILEHANDLE handle, void *pBuffer, ULONG ulWrite, ULONG *pulActual)
{
	Connection *pConn = (Connection *)conn;
	Resource *pRes = pConn->pRes;
	int rc = 0;
	unsigned long done = 0;
	unsigned long onedone;
	unsigned long action;

	log("NdpFileWrite in\n");

	do {
		if (pConn->file.fd < 0)
		{
			rc = ERROR_INVALID_HANDLE;
			break;
		}
		rc = smbwrp_write(pConn->cli, &pConn->file, pBuffer, ulWrite, pulActual);

	} while (0);
	log("NdpFileWrite <%s> %lu %lu %d\n", pConn->file.fd < 0 ? "!null!" : pConn->file.fname, ulWrite, *pulActual, rc);

	return rc;
}

