#include <windows.h>
#include <winreg.h>
#include <nb30.h>
#include <tchar.h>
#include <shellapi.h>
#include <objbase.h>
#include <shlobj.h>
#include <wtypes.h>
#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include <lanahelper.h>

#define NOLOGGING
#ifndef NOLOGGING
extern "C" {
#include <stdarg.h>

    void afsi_log(TCHAR *p, ...) {
        va_list marker;
        TCHAR buffer[200];

        va_start(marker,p);
        _vstprintf(buffer,p,marker);
        va_end(marker);
        _tcscat(buffer,_T("\n"));

        OutputDebugString(buffer);
    }
}
#endif

static const char *szAFSConfigKeyName = "SYSTEM\\CurrentControlSet\\Services\\TransarcAFSDaemon\\Parameters";
static const char *szNetbiosNameValue = "NetbiosName";
static const char *szIsGatewayValue = "IsGateway";
static const char *szLanAdapterValue = "LanAdapter";
static const char *szNoFindLanaByName = "NoFindLanaByName";

// Use the IShellFolder API to get the connection name for the given Guid.
static HRESULT lana_ShellGetNameFromGuidW(WCHAR *wGuid, WCHAR *wName, int NameSize)
{
    // This is the GUID for the network connections folder. It is constant.
    // {7007ACC7-3202-11D1-AAD2-00805FC1270E}
    const GUID CLSID_NetworkConnections = {
        0x7007ACC7, 0x3202, 0x11D1, {
	    0xAA, 0xD2, 0x00, 0x80, 0x5F, 0xC1, 0x27, 0x0E
	}
    };
    LPITEMIDLIST pidl;
    IShellFolder *pShellFolder;
    IMalloc *pShellMalloc;

    // Build the display name in the form "::{GUID}".
    if (wcslen(wGuid) >= MAX_PATH)
        return E_INVALIDARG;
    WCHAR szAdapterGuid[MAX_PATH + 2];
    swprintf(szAdapterGuid, L"::%ls", wGuid);

    // Initialize COM.
    CoInitialize(NULL);

    // Get the shell allocator.
    HRESULT hr = SHGetMalloc(&pShellMalloc);
    if (SUCCEEDED(hr))
    {
        // Create an instance of the network connections folder.
        hr = CoCreateInstance(CLSID_NetworkConnections, NULL,
			      CLSCTX_INPROC_SERVER, IID_IShellFolder,
			      reinterpret_cast<LPVOID *>(&pShellFolder));
    }
    if (SUCCEEDED(hr)) 
    {
        hr = pShellFolder->ParseDisplayName(NULL, NULL, szAdapterGuid, NULL,
					    &pidl, NULL);
    }
    if (SUCCEEDED(hr)) {
        // Get the display name; this returns the friendly name.
        STRRET sName;
	hr = pShellFolder->GetDisplayNameOf(pidl, SHGDN_NORMAL, &sName);
	if (SUCCEEDED(hr))
            wcsncpy(wName, sName.pOleStr, NameSize);
	pShellMalloc->Free(pidl);
    }

    CoUninitialize();
    return hr;
}

// Get the Connection Name for the given GUID.
extern "C" int lana_GetNameFromGuid(char *Guid, char **Name)
{
    typedef HRESULT (WINAPI *HrLanProcAddr)(GUID *, PCWSTR, PWSTR, LPDWORD);
    HrLanProcAddr HrLanProc = NULL;
    HMODULE hNetMan;
    int size;
    WCHAR *wGuid = NULL;
    WCHAR wName[MAX_PATH];
    DWORD NameSize = MAX_PATH;
    char *name = NULL;
    HRESULT status;
        
    // Convert the Guid string to Unicode.  First we ask only for the size
    // of the converted string.  Then we allocate a buffer of sufficient
    // size to hold the result of the conversion.
    size = MultiByteToWideChar(CP_ACP, 0, Guid, -1, NULL, 0);
    wGuid = (WCHAR *) malloc(size * sizeof(WCHAR));
    MultiByteToWideChar(CP_ACP, 0, Guid, -1, wGuid, size);

    // First try the IShellFolder interface, which was unimplemented
    // for the network connections folder before XP.

    /* XXX pbh 9/11/03 - revert to using the undocumented APIs on XP while
     *   waiting to hear back from PSS about the slow reboot issue.
     *   This is an ugly, misleading hack, but is minimally invasive
     *   and will be easy to rollback.
     */

    //status = getname_shellfolder(wGuid, wName, NameSize);
    status = E_NOTIMPL;

    /* XXX end of pbh 9/11/03 temporary hack*/	

    if (status == E_NOTIMPL) {
        // The IShellFolder interface is not implemented on this platform.
        // Try the (undocumented) HrLanConnectionNameFromGuidOrPath API
        // from the netman DLL.
#ifndef NOLOGGING
        afsi_log("IShellFolder API not implemented, trying HrLanConnectionNameFromGuidOrPath");
#endif
        hNetMan = LoadLibrary("netman.dll");
        if (hNetMan == NULL) {
            free(wGuid);
            return -1;
        }
        /* Super Secret Microsoft Call */
        HrLanProc = (HrLanProcAddr) GetProcAddress(hNetMan,
                                                   "HrLanConnectionNameFromGuidOrPath");
        if (HrLanProc == NULL) {
            FreeLibrary(hNetMan);
            free(wGuid);
            return -1;
        }
        status = HrLanProc(NULL, wGuid, wName, &NameSize);
        FreeLibrary(hNetMan);
    }
    free(wGuid);
    if (FAILED(status)) {
#ifndef NOLOGGING
        afsi_log("lana_GetNameFromGuid: failed to get connection name (status %ld)",
		 status);
#endif
        return -1;
    }

    // Get the required buffer size, and then convert the string.
    size = WideCharToMultiByte(CP_ACP, 0, wName, -1, NULL, 0, NULL, NULL);
    name = (char *) malloc(size);
    if (name == NULL)
        return -1;
    WideCharToMultiByte(CP_ACP, 0, wName, -1, name, size, NULL, NULL);
#ifndef NOLOGGING
    afsi_log("Connection name for %s is '%s'", Guid, name);
#endif
    if (*Name)
        *Name = name;
    else
        free(name);
    return 0;
}

// Return an array of LANAINFOs corresponding to a connection named LanaName
// (NULL LanaName matches all connections), and has an IPv4 binding. Returns
// NULL if something goes wrong.
// NOTE: caller must free the returned block if non NULL.
extern "C" LANAINFO * lana_FindLanaByName(const char *LanaName)
{
    const char RegNetBiosLinkageKeyName[] =
        "System\\CurrentControlSet\\Services\\NetBios\\Linkage";
    HKEY hkey;
    LONG status;
    struct {
        BYTE flags;
        BYTE number;
    } lanamap[MAX_LANA+1];
    DWORD lanamapsize = sizeof(lanamap);
    DWORD type;
    char *bindpaths = NULL;
    DWORD bindpathsize;
    int nlana;
    int i;
    char *guid;
    char *name;
    char *pBind;
    char *p;

    LANAINFO * lanainfo;

    // Open the NetBios Linkage key.
    status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegNetBiosLinkageKeyName, 0, 
                          KEY_QUERY_VALUE, &hkey);
        
    if (status != ERROR_SUCCESS) {
#ifndef NOLOGGING
        afsi_log("Failed to open NetBios Linkage key (status %ld)", status);
#endif
        return NULL;
    }

    // Read the lana map.
    status = RegQueryValueEx(hkey, "LanaMap", 0, &type,
                         (BYTE *) &lanamap, &lanamapsize);
    if (status != ERROR_SUCCESS) {
#ifndef NOLOGGING
        afsi_log("Failed to read LanaMap (status %ld)", status);
#endif
        RegCloseKey(hkey);
        return NULL;
    }
    if (lanamapsize == 0) {
#ifndef NOLOGGING
        afsi_log("No data in LanaMap");
#endif
        return NULL;
    }
    nlana = lanamapsize / sizeof(lanamap[0]);

    // Get the bind paths for NetBios so we can match them up
    // with the lana map.  First we query for the size, so we
    // can allocate an appropriate buffer.
    status = RegQueryValueEx(hkey, "Bind", 0, &type, NULL, &bindpathsize);
    if (status == ERROR_SUCCESS && bindpathsize != 0) {
        bindpaths = (char *) malloc(bindpathsize * sizeof(char));
        if (bindpaths == NULL) {
#ifndef NOLOGGING
            afsi_log("Cannot allocate %ld bytes for bindpaths", bindpathsize);
#endif
            RegCloseKey(hkey);
            return NULL;
        }
        status = RegQueryValueEx(hkey, "Bind", 0, &type, 
                                 (BYTE *) bindpaths, &bindpathsize);
    }
    RegCloseKey(hkey);
    if (status != ERROR_SUCCESS) {
#ifndef NOLOGGING
        afsi_log("Failed to read bind paths (status %ld)", status);
#endif
        if (bindpaths != NULL)
            free(bindpaths);
        return NULL;
      }
    if (bindpathsize == 0) {
#ifndef NOLOGGING
        afsi_log("No bindpath data");
#endif
        if (bindpaths != NULL)
            free(bindpaths);
        return NULL;
    }

    if (LanaName)
    {
        lanainfo = (LANAINFO *) malloc(sizeof(LANAINFO)*2);
        if(lanainfo == NULL) {
            free(bindpaths);
            return NULL;
        }
        memset(lanainfo, 0, sizeof(LANAINFO) * 2);
        lanainfo[0].lana_number = LANA_INVALID;
    }
    else
    {
        lanainfo = (LANAINFO *) malloc(sizeof(LANAINFO)*(nlana+1));
        if(lanainfo == NULL) {
            free(bindpaths);
            return NULL;
        }
        memset(lanainfo, 0, sizeof(LANAINFO) * (nlana+1));
    }
    
    int index = 0;

    // Iterate over the lana map entries and bind paths.
    for (i = 0, pBind = bindpaths; i < nlana;
         i++, pBind += strlen(pBind) + 1) {
	// Ignore an invalid map entry.
        if ((lanamap[i].flags & 1) == 0)
            continue;

		// check for an IPv4 binding
		if(!strstr(pBind,"_Tcpip_"))
			continue;

        // Find the beginning of the GUID.
        guid = strchr(pBind, '{');
        if (guid == NULL)
	    continue;                   // Malformed path entry?
        guid = strdup(guid);
        if (guid == NULL)
            continue;
	// Find the end of the GUID.
        p = strchr(guid, '}');
        if (p == NULL) {
	    free(guid);                 // Malformed GUID?
            continue;
        }
        *++p = '\0';                    // Ignore anything after the GUID.
        status = lana_GetNameFromGuid(guid, &name);
        free(guid);

        if (status == 0 && name != 0)
        {
            if (LanaName)
            {
                if (strcmp(name, LanaName) ==0)
                {
                    lanainfo[index].lana_number = lanamap[i].number;
                    _tcscpy(lanainfo[index].lana_name, name);
                    free(name);
                    index++;
                    break;
                }
            }
            else
            {
                lanainfo[index].lana_number = lanamap[i].number;
                _tcscpy(lanainfo[index].lana_name, name);
                free(name);
                index++;
            }
        }
    }

    lanainfo[index].lana_number = LANA_INVALID;

    free(bindpaths);
    return lanainfo;
}

extern "C" lana_number_t lana_FindLoopback(void)
{
    NCB ncb;
    LANA_ENUM lana_list;
    int status;
    int i;

    memset(&ncb, 0, sizeof(ncb));
    ncb.ncb_command = NCBENUM;
    ncb.ncb_buffer = (UCHAR *) &lana_list;
    ncb.ncb_length = sizeof(lana_list);
    status = Netbios(&ncb);
    if (status != 0) {
#ifndef NOLOGGING
        afsi_log("Netbios NCBENUM failed: status %ld", status);
#endif
        return LANA_INVALID;
    }
    for (i = 0; i < lana_list.length; i++) {
	if (lana_IsLoopback(lana_list.lana[i])) {
	    // Found one, return it.
#ifndef NOLOGGING
	    afsi_log("lana_FindLoopback: Found LAN adapter %d",
		     lana_list.lana[i]);
#endif
	    return lana_list.lana[i];
	}
    }
    // Could not find a loopback adapter.
    return LANA_INVALID;
}

// Is the given lana a Windows Loopback Adapter?
// TODO: implement a better check for loopback
// TODO: also check for proper bindings (IPv4)
extern "C" BOOL lana_IsLoopback(lana_number_t lana)
{
    NCB ncb;
    struct {
        ADAPTER_STATUS status;
        NAME_BUFFER names[MAX_LANA+1];
    } astat;
    unsigned char kWLA_MAC[6] = { 0x02, 0x00, 0x4c, 0x4f, 0x4f, 0x50 };
    int status;

    // Reset the adapter: in Win32, this is required for every process, and
    // acts as an init call, not as a real hardware reset.
    memset(&ncb, 0, sizeof(ncb));
    ncb.ncb_command = NCBRESET;
    ncb.ncb_callname[0] = 100;
    ncb.ncb_callname[2] = 100;
    ncb.ncb_lana_num = lana;
    status = Netbios(&ncb);
    if (status == 0)
        status = ncb.ncb_retcode;
    if (status != 0) {
#ifndef NOLOGGING
       afsi_log("NCBRESET failed: lana %u, status %ld", lana, status);
#endif
        return FALSE;
    }

    // Use the NCBASTAT command to get the adapter address.
    memset(&ncb, 0, sizeof(ncb));
    ncb.ncb_command = NCBASTAT;
    ncb.ncb_lana_num = lana;
    strcpy((char *) ncb.ncb_callname, "*               ");
    ncb.ncb_buffer = (UCHAR *) &astat;
    ncb.ncb_length = sizeof(astat);
    status = Netbios(&ncb);
    if (status == 0)
        status = ncb.ncb_retcode;
    if (ncb.ncb_retcode != 0) {
#ifndef NOLOGGING   
        afsi_log("NCBASTAT failed: lana %u, status %ld", lana, status);
#endif
        return FALSE;
    }
    return (memcmp(astat.status.adapter_address, kWLA_MAC, 6) == 0);
}

// Get the netbios named used/to-be-used by the AFS SMB server.
// IF <lana specified> THEN
//     Use specified lana
// ELSE
//	   Look for an adapter named "AFS", failing which,
//     look for a loopback adapter.
// ENDIF
// IF lana is for a loopback && !IsGateway THEN
//    IF netbios name is specified THEN
//       use specified netbios name
//    ELSE
//       use "AFS"
//    ENDIF
// ELSE
//    use netbios name "<hostname>-AFS"
// ENDIF
// Return ERROR_SUCCESS if netbios name was successfully generated.
// Returns the lana number to use in *pLana (if pLana is non-NULL) and also
//         the IsGateway setting in *pIsGateway (if pIsGateway is non-NULL).
//         the type of name returned.
//
// buffer is assumed to hold at least MAX_NB_NAME_LENGTH bytes.
//
// flags :
//        LANA_NETBIOS_NAME_IN : Use the values of *pLana and *pIsGateway as [in] parameters.
//        LANA_NETBIOS_NAME_SUFFIX : Only return the suffix of netbios name
//		  LANA_NETBIOS_NAME_FULL : Return full netbios name
extern "C" long lana_GetUncServerNameEx(char *buffer, lana_number_t * pLana, int * pIsGateway, int flags) {
    HKEY hkConfig;
	DWORD dummyLen;
	LONG rv;
	int regLana;
	int regGateway, regNoFindLanaByName;
	TCHAR regNbName[MAX_NB_NAME_LENGTH];
	TCHAR nbName[MAX_NB_NAME_LENGTH];
	TCHAR hostname[MAX_COMPUTERNAME_LENGTH+1];

	rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE,szAFSConfigKeyName,0,KEY_READ,&hkConfig);
	if(rv == ERROR_SUCCESS) {
		if(!(flags & LANA_NETBIOS_NAME_IN) || !pLana) {
			dummyLen = sizeof(regLana);
			rv = RegQueryValueEx(hkConfig, szLanAdapterValue, NULL, NULL, (LPBYTE) &regLana, &dummyLen);
			if(rv != ERROR_SUCCESS) regLana = -1;
		} else
			regLana = *pLana;

		if(!(flags & LANA_NETBIOS_NAME_IN) || !pIsGateway) {
			dummyLen = sizeof(regGateway);
			rv = RegQueryValueEx(hkConfig, szIsGatewayValue, NULL, NULL, (LPBYTE) &regGateway, &dummyLen);
			if(rv != ERROR_SUCCESS) regGateway = 0;
		} else
			regGateway = *pIsGateway;

		dummyLen = sizeof(regNoFindLanaByName);
		rv = RegQueryValueEx(hkConfig, szNoFindLanaByName, NULL, NULL, (LPBYTE) &regNoFindLanaByName, &dummyLen);
		if(rv != ERROR_SUCCESS) regNoFindLanaByName = 0;

		// Do not care if the call fails for insufficient buffer size.  We are not interested
		// in netbios names over 15 chars.
		dummyLen = sizeof(regNbName);
		rv = RegQueryValueEx(hkConfig, szNetbiosNameValue, NULL, NULL, (LPBYTE) &regNbName, &dummyLen);
		if(rv != ERROR_SUCCESS) regNbName[0] = 0;
		else regNbName[15] = 0;

		RegCloseKey(hkConfig);
	} else {
		if(flags & LANA_NETBIOS_NAME_IN) {
			regLana = (pLana)? *pLana: -1;
			regGateway = (pIsGateway)? *pIsGateway: 0;
		} else {
			regLana = -1;
			regGateway = 0;
		}
        regNoFindLanaByName = 0;
		regNbName[0] = 0;
	}

    if(regLana < 0 || regLana > MAX_LANA) 
        regLana = -1;

	if(regLana == -1) {
		LANAINFO *lanaInfo = NULL;
        int nLana = LANA_INVALID;

        if (!regNoFindLanaByName)
            lanaInfo = lana_FindLanaByName("AFS");
		if(lanaInfo != NULL) {
            nLana = lanaInfo[0].lana_number;
			free(lanaInfo);
		} else
			nLana = LANA_INVALID;

		if(nLana == LANA_INVALID && !regGateway) {
			nLana = lana_FindLoopback();
		}
		if(nLana != LANA_INVALID) 
            regLana = nLana;
	}

	if(regNbName[0] &&
       (regLana >=0 && lana_IsLoopback((lana_number_t) regLana))) {
        strncpy(nbName,regNbName,15);
        nbName[16] = 0;
        strupr(nbName);
	} else {
		char * dot;

		if(flags & LANA_NETBIOS_NAME_SUFFIX) {
			strcpy(nbName,"-AFS");
		} else {
			dummyLen = sizeof(hostname);
			// assume we are not a cluster.
			rv = GetComputerName(hostname, &dummyLen);
			if(!SUCCEEDED(rv)) { // should not happen, but...
				return rv;
			}
			strncpy(nbName, hostname, 11);
			nbName[11] = 0;
			if(dot = strchr(nbName,'.'))
				*dot = 0;
			strcat(nbName,"-AFS");
		}
	}

	if(pLana) *pLana = regLana;
	if(pIsGateway) *pIsGateway = regGateway;

	strcpy(buffer, nbName);

	return ERROR_SUCCESS;
}

extern "C" void lana_GetUncServerNameDynamic(int lanaNumber, BOOL isGateway, TCHAR *name, int type) {
	char szName[MAX_NB_NAME_LENGTH];
	lana_number_t lana = (lana_number_t) lanaNumber;
	int gateway = (int) isGateway;

	if(SUCCEEDED(lana_GetUncServerNameEx(szName, &lana, &gateway, LANA_NETBIOS_NAME_IN | type))) {
#ifdef _UNICODE
		mbswcs(name,szName,MAX_NB_NAME_LENGTH);
#else
		strncpy(name,szName,MAX_NB_NAME_LENGTH);
#endif
	} else
		*name = _T('\0');
}

extern "C" void lana_GetUncServerName(TCHAR *name, int type) {
	char szName[MAX_NB_NAME_LENGTH];

	if(SUCCEEDED(lana_GetUncServerNameEx(szName,NULL,NULL,type))) {
#ifdef _UNICODE
		mbswcs(name,szName,MAX_NB_NAME_LENGTH);
#else
		strncpy(name,szName,MAX_NB_NAME_LENGTH);
#endif
	} else {
        *name = _T('\0');
	}
}

extern "C" void lana_GetAfsNameString(int lanaNumber, BOOL isGateway, TCHAR* name)
{
    TCHAR netbiosName[32];
    lana_GetUncServerNameDynamic(lanaNumber, isGateway, netbiosName, LANA_NETBIOS_NAME_FULL);
    _stprintf(name, _T("Your UNC name to reach the root of AFS is \\\\%s\\all"), netbiosName);
}

extern "C" void lana_GetNetbiosName(LPTSTR pszName, int type)
{
	HKEY hkCfg;
    TCHAR name[MAX_NB_NAME_LENGTH];
	DWORD dummyLen;

    memset(name, 0, sizeof(name));
    if (GetVersion() >= 0x80000000) // not WindowsNT
    {
        if (type == LANA_NETBIOS_NAME_SUFFIX)
        {
            _tcscpy(pszName, TEXT("-afs"));
            return;
        }

		if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,szAFSConfigKeyName,0,KEY_READ,&hkCfg) == ERROR_SUCCESS) {
			dummyLen = sizeof(name);
			if(RegQueryValueEx(hkCfg,TEXT("Gateway"),NULL,NULL,(LPBYTE) name,&dummyLen) == ERROR_SUCCESS)
				name[0] = _T('\0');
			RegCloseKey(hkCfg);
		}

        if (_tcslen(name) == 0)
        {
            _tcscpy(pszName, TEXT("unknown"));
            return;
        }

		_tcscpy(pszName, name);
        _tcscat(pszName, TEXT("-afs"));
        return;
    }

    lana_GetUncServerName(name,type);
	_tcslwr(name);
    _tcscpy(pszName, name);
    return;
}

