#include "mslocusr.h"
#include "msluglob.h"

#include "resource.h"
#include <winnetwk.h>
#include <netspi.h>

#pragma data_seg(".shared")
char szDefaultLogonUsername[MAX_PATH] = "";
char szDefaultLogonPassword[MAX_PATH] = "";
BOOL fDoDefaultLogon = FALSE;
#pragma data_seg()


struct LogonData
{
    LPLOGONINFO lpAuthentInfo;
    DWORD dwFlags;
    IUser **ppOut;
    HBITMAP hbmTransparent;
};


void ObfuscateString(LPSTR pszBuffer)
{
    DWORD dwMask = 0xa95e633b;      /* nice random collection of bits */

    unsigned char ch;
    do {
        ch = *pszBuffer;

        *(pszBuffer++) = ch ^ (unsigned char)(dwMask & 0xff);
        dwMask = (dwMask >> 8) | (dwMask << 24);
    } while (ch);
}


void DeObfuscateString(LPSTR pszBuffer)
{
    DWORD dwMask = 0xa95e633b;      /* nice random collection of bits */

    unsigned char ch;
    do {
        ch = *pszBuffer ^ (unsigned char)(dwMask & 0xff);

        *(pszBuffer++) = ch;
        dwMask = (dwMask >> 8) | (dwMask << 24);
    } while (ch);
}


void CacheLogonCredentials(LPCSTR pszUsername, LPCSTR pszPassword)
{
    lstrcpy(szDefaultLogonUsername, pszUsername);
    lstrcpy(szDefaultLogonPassword, pszPassword);
    fDoDefaultLogon = TRUE;

    ObfuscateString(szDefaultLogonUsername);
    ObfuscateString(szDefaultLogonPassword);
}


SPIENTRY NPGetCaps(
    DWORD nIndex
    )
{
    switch (nIndex) {
    case WNNC_SPEC_VERSION:
        return 0x00040001;          /* spec version 4.1 */

    case WNNC_NET_TYPE:
        return WNNC_NET_MSNET;

    case WNNC_DRIVER_VERSION:
        return 0x00010000;          /* driver version 1.0 */

    case WNNC_USER:
        return
//          WNNC_USR_GETUSER |
            0;

    case WNNC_CONNECTION:
        return
            0;

    case WNNC_DIALOG:
        return
            0;

    case WNNC_ENUMERATION:
        return
            0;

    case WNNC_START:
        return 0x1;                 /* started */

    case WNNC_RESOURCE:
        return
            0;

    case WNNC_AUTHENTICATION:
        return
            WNNC_AUTH_LOGON |
            WNNC_AUTH_LOGOFF |
//          WNNC_AUTH_GETHOMEDIRECTORY |
//          WNNC_AUTH_GETPOLICYPATH |
            0;
    }

    return 0;
}

// FEATURE not multimonitor friendly

VOID PlaceDialog(HWND hDlg, BOOL fTopThird)
{
    RECT rc;
    int dyScreen = GetSystemMetrics(SM_CYSCREEN);
    int yDialog;

    GetWindowRect(hDlg,&rc);

    if (fTopThird)
        yDialog = (dyScreen / 3) - ((rc.bottom-rc.top) / 2);
    else
        yDialog = (dyScreen - (rc.bottom - rc.top)) / 2;

    SetWindowPos(hDlg,NULL,
               (GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2,
               yDialog, 0, 0, SWP_NOSIZE);
}


void UserSelected(HWND hwndLB, int iItem)
{
    BOOL fNeedPassword;
    BOOL fEnableOK;

    if (iItem == LB_ERR) {
        fNeedPassword = FALSE;
        fEnableOK = FALSE;
    }
    else {
        IUser *pUser = (IUser *)::SendMessage(hwndLB, LB_GETITEMDATA, iItem, 0);
        fNeedPassword = FAILED(pUser->Authenticate(""));
        fEnableOK = TRUE;
    }
    HWND hDlg = GetParent(hwndLB);
    EnableWindow(GetDlgItem(hDlg, IDC_PASSWORD_LABEL), fNeedPassword);
    EnableWindow(GetDlgItem(hDlg, IDC_PASSWORD), fNeedPassword);
    EnableWindow(GetDlgItem(hDlg, IDOK), fEnableOK);
}


HRESULT FillUserList(HWND hwndLB, IUserDatabase *pDB, LPCSTR pszDefaultSelection,
                     BOOL fIncludeGuest, PFNSELNOTIFY pfnSelNotify)
{
    IEnumUnknown *pEnum;
    BOOL fSelectionSet = FALSE;

    if (fIncludeGuest) {
        NLS_STR nlsTemp(MAX_RES_STR_LEN);
        if (nlsTemp.LoadString(IDS_GUEST_USERNAME) == ERROR_SUCCESS) {
            UINT iItem = (UINT)::SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)nlsTemp.QueryPch());
            if (iItem != LB_ERR && iItem != LB_ERRSPACE) {
                ::SendMessage(hwndLB, LB_SETITEMDATA, iItem, 0);
            }
        }
    }

    HRESULT hres = pDB->EnumUsers(&pEnum);
    if (SUCCEEDED(hres)) {
        IUnknown *pUnk;
        while (pEnum->Next(1, &pUnk, NULL) == S_OK) {
            IUser *pUser;
            if (SUCCEEDED(pUnk->QueryInterface(IID_IUser, (void **)&pUser))) {
                char szBuf[cchMaxUsername+1];
                DWORD cbBuffer = sizeof(szBuf);

                if (SUCCEEDED(pUser->GetName(szBuf, &cbBuffer))) {
                    UINT iItem = (UINT)::SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuf);
                    if (iItem != LB_ERR && iItem != LB_ERRSPACE) {
                        if (::SendMessage(hwndLB, LB_SETITEMDATA, iItem, (LPARAM)pUser) == LB_ERR)
                            ::SendMessage(hwndLB, LB_SETITEMDATA, iItem, 0);
                        if (!fSelectionSet) {
                            if (pszDefaultSelection != NULL && !::stricmpf(szBuf, pszDefaultSelection)) {
                                fSelectionSet = TRUE;
                                ::SendMessage(hwndLB, LB_SETCURSEL, iItem, 0);
                                if (pfnSelNotify != NULL)
                                    (*pfnSelNotify)(hwndLB, iItem);
                            }
                        }
                    }
                }
                /* Note that pUser is not Release()d here, since the
                 * listbox has a pointer to it.
                 */
            }
            pUnk->Release();
        }

        if (!fSelectionSet) {
            if (pfnSelNotify)
                (*pfnSelNotify)(hwndLB, LB_ERR);
        }
        else {
            /* If we select the default item above, then insert more names
             * above it, the focus rect and the selection will be different,
             * which is confusing if the user tabs to the listbox.  Work
             * around this by setting the caret index manually.
             */
            LRESULT iItem = ::SendMessage(hwndLB, LB_GETCURSEL, 0, 0);
            if (iItem != LB_ERR)
                ::SendMessage(hwndLB, LB_SETCURSEL, iItem, 0);
        }

        pEnum->Release();
    }

    if (FAILED(hres))
        return hres;

    return fSelectionSet ? NOERROR : S_FALSE;
}


BOOL IsMemphis(void)
{
    OSVERSIONINFOA osvi;

    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionExA(&osvi);

    return (VER_PLATFORM_WIN32_WINDOWS == osvi.dwPlatformId &&
            (osvi.dwMajorVersion > 4 || 
             (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion >= 10)));
}


DWORD InitLogonDialog(HWND hwndDialog, LogonData *pld)
{
    DWORD err = WN_NO_NETWORK;
    BOOL fSelectionSet = FALSE;

    ::SetWindowLongPtr(hwndDialog, DWLP_USER, (LONG_PTR)pld);

    PlaceDialog(hwndDialog, FALSE);

    int idBitmap;
    DWORD dwFlags;

    if (IsMemphis())
        idBitmap = IDB_IMAGE_WIN98_LOGON;
    else
        idBitmap = IDB_IMAGE_LOGON;

    /* The bitmap we show at the top of the logon dialog has black text on a
     * transparent background.  If the dialog background is very dark, the
     * text will be unreadable.  In that case we use a static bitmap with
     * a white background.  For more common background colors, though, we use
     * LoadImage to load a transparent image and replace the bitmap in the
     * dialog.
     *
     * CODEWORK: Could try a variant of COLORISLIGHT macro from shell32,
     * defview.cpp;  it seems pretty aggressive about declaring blues in
     * particular as "dark".  Maybe we could have the alternate bitmap be
     * 3D-mapped as well, but have white text and maybe a white box around
     * the Windows flag, then we could always be transparent and just choose
     * one or the other at an arbitrary cutoff point.
     */
    DWORD clrBtnFace = GetSysColor(COLOR_3DFACE);
    if ((LOBYTE(clrBtnFace) >= 128) ||
        (LOBYTE(clrBtnFace >> 8) >= 128) ||
        (LOBYTE(clrBtnFace >> 16) >= 128)) {

        dwFlags = LR_LOADMAP3DCOLORS;       /* we'll use a transparent bitmap */
    }
    else {
        idBitmap++;             /* advance to static bitmap ID */
        dwFlags = LR_DEFAULTCOLOR;
    }

    pld->hbmTransparent = (HBITMAP)LoadImage(::hInstance,
                                        MAKEINTRESOURCE(idBitmap),
                                        IMAGE_BITMAP, 0, 0,
                                        dwFlags);
    if (pld->hbmTransparent != NULL) {
        HBITMAP hbmOld = (HBITMAP)SendDlgItemMessage(hwndDialog,
                                                     IDC_MAIN_CAPTION,
                                                     STM_SETIMAGE,
                                                     (WPARAM)IMAGE_BITMAP,
                                                     (LPARAM)pld->hbmTransparent);
        /* If we set the new bitmap into the control, we got the old one
         * back.  Delete the old one.  We will also have to delete the
         * new one when the dialog is dismissed.
         */
        if (hbmOld != NULL)
            DeleteObject(hbmOld);
    }

    IUserDatabase *pDB;

    if (SUCCEEDED(::CreateUserDatabase(IID_IUserDatabase, (void **)&pDB))) {
        HRESULT hres = FillUserList(GetDlgItem(hwndDialog, IDC_USERNAME),
                                    pDB, pld->lpAuthentInfo ? pld->lpAuthentInfo->lpUsername : NULL,
                                    FALSE, UserSelected);

        if (SUCCEEDED(hres)) {
            err = ERROR_SUCCESS;

            ::SetFocus(::GetDlgItem(hwndDialog, hres == NOERROR ? IDC_PASSWORD : IDC_USERNAME));
        }
        pDB->Release();
    }

    return err;
}


BOOL ValidateLogonDialog(HWND hwndDialog)
{
    LRESULT iItem = ::SendDlgItemMessage(hwndDialog, IDC_USERNAME, LB_GETCURSEL, 0, 0);
    if (iItem == LB_ERR)
        return FALSE;

    IUser *pUser = (IUser *)::SendDlgItemMessage(hwndDialog, IDC_USERNAME, LB_GETITEMDATA, iItem, 0);

    if (pUser != NULL) {
        NLS_STR nlsUsername(cchMaxUsername+1);
        if (nlsUsername.QueryError())
            return FALSE;

        DWORD cbBuffer = nlsUsername.QueryAllocSize();
        pUser->GetName(nlsUsername.Party(), &cbBuffer);
        nlsUsername.DonePartying();

        HWND hwndPassword = ::GetDlgItem(hwndDialog, IDC_PASSWORD);
        NLS_STR nlsPassword(::GetWindowTextLength(hwndPassword)+2);
        if (nlsPassword.QueryError())
            return FALSE;

        ::GetWindowText(hwndPassword, nlsPassword.Party(), nlsPassword.QueryAllocSize()-1);
        nlsPassword.DonePartying();

        if (SUCCEEDED(pUser->Authenticate(nlsPassword.QueryPch()))) {
            LogonData *pld = (LogonData *)::GetWindowLongPtr(hwndDialog, DWLP_USER);
            if (pld->lpAuthentInfo) {
                DWORD cbUsername = pld->lpAuthentInfo->cbUsername;
                DWORD cbPassword = pld->lpAuthentInfo->cbPassword;
                NPSCopyNLS(&nlsUsername, pld->lpAuthentInfo->lpUsername, &cbUsername);
                NPSCopyNLS(&nlsPassword, pld->lpAuthentInfo->lpPassword, &cbPassword);
            }

            if (pld->ppOut) {
                *pld->ppOut = pUser;
                pUser->AddRef();
            }

            if (pld->dwFlags & LUA_FORNEXTLOGON) {
                CacheLogonCredentials(nlsUsername.QueryPch(), nlsPassword.QueryPch());
            }

            return TRUE;
        }

        NLS_STR nlsTitle(MAX_RES_STR_LEN);
        NLS_STR nlsMessage(MAX_RES_STR_LEN);
        if (!nlsTitle.QueryError() && !nlsMessage.QueryError()) {
            nlsTitle.LoadString(IDS_LOGONTITLE);
            nlsMessage.LoadString(IDS_BADPASSWORD);
            ::MessageBox(hwndDialog, nlsMessage.QueryPch(), nlsTitle.QueryPch(), MB_ICONSTOP | MB_OK);
        }
        ::SetFocus(hwndPassword);
        ::SendMessage(hwndPassword, EM_SETSEL, (WPARAM)(INT)0, (WPARAM)(INT)-1);
    }
    return FALSE;
}


void DestroyUserList(HWND hwndLB)
{
    LRESULT cItems = ::SendMessage(hwndLB, LB_GETCOUNT, 0, 0);

    for (LRESULT iItem = 0; iItem < cItems; iItem++) {
        IUser *pUser = (IUser *)::SendMessage(hwndLB, LB_GETITEMDATA, iItem, 0);
        if (pUser != NULL) {
            pUser->Release();
        }
    }
}


void ExitLogonDialog(HWND hwndDialog, DWORD err)
{
    DestroyUserList(GetDlgItem(hwndDialog, IDC_USERNAME));

    LogonData *pld = (LogonData *)::GetWindowLongPtr(hwndDialog, DWLP_USER);
    if (pld->hbmTransparent != NULL)
        DeleteObject(pld->hbmTransparent);

    ::EndDialog(hwndDialog, err);
}


extern "C" {

INT_PTR LogonDlgProc(
    HWND hwndDlg,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
#if 0   /*** no help for now ***/
    // Help text array
    static DWORD aIds[] = {
        IDC_DUMMY1, IDH_NET_LOG_USERNAME,
        IDD_LOG_USERNAME, IDH_NET_LOG_USERNAME,
        IDC_DUMMY2, IDH_NET_LOG_PASSWORD,
        IDD_LOG_PASSWORD, IDH_NET_LOG_PASSWORD,
        IDC_LOGOFRAME, NO_HELP,
        IDC_DUMMY3, NO_HELP,
        0,0
    };
#endif

    switch (msg) {
    case WM_INITDIALOG:
        {
            DWORD err = ::InitLogonDialog(hwndDlg, (LogonData *)lParam);
            if (err != ERROR_SUCCESS) {
                ::ExitLogonDialog(hwndDlg, err);
            }
        }
        return FALSE;           /* we set the focus */

    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDCANCEL:
            ::ExitLogonDialog(hwndDlg, WN_CANCEL);
            return TRUE;        /* we processed a message */

        case IDOK:
            if (::ValidateLogonDialog(hwndDlg))
                ::ExitLogonDialog(hwndDlg, WN_SUCCESS);
            return TRUE;        /* we processed a message */

        case IDC_USERNAME:
            if (HIWORD(wParam) == LBN_SELCHANGE) {
                int iItem = (int)::SendDlgItemMessage(hwndDlg, IDC_USERNAME, LB_GETCURSEL, 0, 0);
                UserSelected((HWND)lParam, iItem);
            }
        }
        break;

#if 0   /*** no help for now ***/
    case WM_HELP:
        WinHelp( ((LPHELPINFO)lParam)->hItemHandle, szHelpFile,
                HELP_WM_HELP, (DWORD)(LPVOID)aIds );
        return TRUE;

    case WM_CONTEXTMENU:
        WinHelp( (HWND)wParam, szHelpFile, HELP_CONTEXTMENU,
                (DWORD)(LPVOID)aIds );
        return TRUE;
#endif
    }

    return FALSE;               /* we didn't process the message */
}

};  /* extern "C" */


DWORD DoLogonDialog(HWND hwndOwner, LPLOGONINFO lpAuthentInfo)
{
    LogonData ld;

    ld.lpAuthentInfo = lpAuthentInfo;
    ld.dwFlags = 0;
    ld.ppOut = NULL;
    ld.hbmTransparent = NULL;

    INT_PTR nRet = ::DialogBoxParam(::hInstance, MAKEINTRESOURCE(IDD_LOGON),
                                    hwndOwner, LogonDlgProc, (LPARAM)&ld);

    if (nRet == -1)
        return WN_OUT_OF_MEMORY;
    else
        return (DWORD)nRet;
}


HRESULT DoUserDialog(HWND hwndOwner, DWORD dwFlags, IUser **ppOut)
{
    LogonData ld;

    ld.lpAuthentInfo = NULL;
    ld.dwFlags = dwFlags;
    ld.ppOut = ppOut;
    if (ppOut != NULL)
        *ppOut = NULL;

    INT_PTR nRet = ::DialogBoxParam(::hInstance, MAKEINTRESOURCE(IDD_LOGON),
                                    hwndOwner, LogonDlgProc, (LPARAM)&ld);

    if (nRet == -1)
        return E_OUTOFMEMORY;
    else
        return (nRet == WN_SUCCESS) ? S_OK : E_ABORT;
}


DWORD TryDefaultLogon(LPCSTR pszUsername, LPCSTR pszPassword, LPLOGONINFO lpAuthentInfo)
{
    IUserDatabase *pDB = NULL;
    if (FAILED(::CreateUserDatabase(IID_IUserDatabase, (void **)&pDB))) {
        return WN_OUT_OF_MEMORY;
    }

    DWORD err;
    IUser *pUser;
    if (SUCCEEDED(pDB->GetUser(pszUsername, &pUser))) {
        if (SUCCEEDED(pUser->Authenticate(pszPassword)))
            err = WN_SUCCESS;
        else
            err = WN_BAD_PASSWORD;

        pUser->Release();
    }
    else {
        err = WN_BAD_USER;
    }

    pDB->Release();

    if (err == WN_SUCCESS) {
        DWORD cbUsername = lpAuthentInfo->cbUsername;
        DWORD cbPassword = lpAuthentInfo->cbPassword;
        NPSCopyString(pszUsername, lpAuthentInfo->lpUsername, &cbUsername);
        NPSCopyString(pszPassword, lpAuthentInfo->lpPassword, &cbPassword);
    }

    return err;
}


SPIENTRY NPLogon(
    HWND hwndOwner,
    LPLOGONINFO lpAuthentInfo,
    LPLOGONINFO lpPreviousAuthentInfo,
    LPTSTR lpLogonScript,
    DWORD dwBufferSize,
    DWORD dwFlags
    )
{
    /* ignore logon done notification, we only act on logon starting */
    if (dwFlags & LOGON_DONE) {
        return WN_SUCCESS;
    }

    /* we have nothing to do if we're not the primary logon provider */
    if (!(dwFlags & LOGON_PRIMARY)) {
        return WN_SUCCESS;
    }

    /* make sure profiles are enabled, fall back to windows logon if not */
    HKEY hkeyLogon;
    DWORD err;
    DWORD fProfilesEnabled = FALSE;
    DWORD cbData = sizeof(fProfilesEnabled);
    err = ::RegOpenKey(HKEY_LOCAL_MACHINE, ::szLogonKey, &hkeyLogon);
    if (err != ERROR_SUCCESS)
        return WN_NO_NETWORK;
    err = ::RegQueryValueEx(hkeyLogon, ::szUserProfiles, NULL, NULL,
                            (LPBYTE)&fProfilesEnabled, &cbData);
    ::RegCloseKey(hkeyLogon);
    if (err != ERROR_SUCCESS || !fProfilesEnabled)
        return WN_NO_NETWORK;

    /* If we have cached logon credentials, attempt to use them. */

    if (fDoDefaultLogon) {
        DeObfuscateString(szDefaultLogonUsername);
        DeObfuscateString(szDefaultLogonPassword);

        DWORD err = TryDefaultLogon(szDefaultLogonUsername, szDefaultLogonPassword, lpAuthentInfo);

        ::memsetf(szDefaultLogonUsername, '\0', sizeof(szDefaultLogonUsername));
        ::memsetf(szDefaultLogonPassword, '\0', sizeof(szDefaultLogonPassword));
        fDoDefaultLogon = FALSE;

        if (err == WN_SUCCESS)
            return WN_SUCCESS;
    }

    return DoLogonDialog(hwndOwner, lpAuthentInfo);
}


SPIENTRY NPLogoff(
    HWND hwndOwner,
    LPLOGONINFO lpAuthentInfo,
    DWORD dwReason
    )
{
    return WN_SUCCESS;
}


SPIENTRY NPGetPolicyPath(
    LPTSTR lpPath,
    LPDWORD lpBufferSize,
    DWORD dwFlags
    )
{
    return WN_NOT_SUPPORTED;
}
