/*
 * MPR Password Cache functions
 *
 * Copyright 1999 Ulrich Weigand
 * Copyright 2003,2004 Mike McCormack for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

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

#include "windef.h"
#include "winbase.h"
#include "winnetwk.h"
#include "winreg.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(mpr);

static const char mpr_key[] = "Software\\Wine\\Wine\\Mpr\\";

static inline BYTE hex( BYTE x )
{
    if( x <= 9 )
        return x + '0';
    return x + 'A' - 10;
}

static inline signed char ctox( CHAR x )
{
    if( ( x >= '0' ) && ( x <= '9' ) )
        return x - '0';
    if( ( x >= 'A' ) && ( x <= 'F' ) )
        return x - 'A' + 10;
    if( ( x >= 'a' ) && ( x <= 'a' ) )
        return x - 'a' + 10;
    return -1;
}

static LPSTR MPR_GetValueName( LPCSTR pbResource, WORD cbResource, BYTE nType )
{
    LPSTR name;
    DWORD  i;

    name = HeapAlloc( GetProcessHeap(), 0, 6+cbResource*2 );
    if( !name ) return NULL;

    sprintf( name, "X-%02X-", nType );
    for(i=0; i<cbResource; i++)
    {
        name[5+i*2]=hex((pbResource[i]&0xf0)>>4);
        name[6+i*2]=hex(pbResource[i]&0x0f);
    }
    name[5+i*2]=0;
    TRACE( "Value is %s\n", name );
    return name;
}


/**************************************************************************
 * WNetCachePassword [MPR.@]  Saves password in cache
 *
 * NOTES
 *	Only the parameter count is verified
 *
 *	---- everything below this line might be wrong (js) -----
 * RETURNS
 *    Success: WN_SUCCESS
 *    Failure: WN_ACCESS_DENIED, WN_BAD_PASSWORD, WN_BADVALUE, WN_NET_ERROR,
 *             WN_NOT_SUPPORTED, WN_OUT_OF_MEMORY
 */
DWORD WINAPI WNetCachePassword(
    LPSTR pbResource, /* [in] Name of workgroup, computer, or resource */
    WORD cbResource,  /* [in] Size of name */
    LPSTR pbPassword, /* [in] Buffer containing password */
    WORD cbPassword,  /* [in] Size of password */
    BYTE nType,       /* [in] Type of password to cache */
    WORD x)

{
    HKEY hkey;
    DWORD r;
    LPSTR valname;

    WARN( "(%p(%s), %d, %p(%s), %d, %d, 0x%08x): totally insecure\n",
           pbResource, debugstr_a(pbResource), cbResource,
	   pbPassword, debugstr_a(pbPassword), cbPassword,
	   nType, x );

    /* @@ Wine registry key: HKCU\Software\Wine\Wine\Mpr */
    r = RegCreateKeyA( HKEY_CURRENT_USER, mpr_key, &hkey );
    if( r )
        return WN_ACCESS_DENIED;

    valname = MPR_GetValueName( pbResource, cbResource, nType );
    if( valname )
    {
        r = RegSetValueExA( hkey, valname, 0, REG_BINARY, 
                            (LPBYTE)pbPassword, cbPassword );
        if( r )
            r = WN_CANCEL;
        else
            r = WN_SUCCESS;
        HeapFree( GetProcessHeap(), 0, valname );
    }
    else
        r = WN_OUT_OF_MEMORY;

    RegCloseKey( hkey );

    return r;
}

/*****************************************************************
 *  WNetRemoveCachedPassword [MPR.@]
 */
UINT WINAPI WNetRemoveCachedPassword(
      LPSTR pbResource,   /* [in] resource ID to delete */
      WORD cbResource,    /* [in] number of bytes in the resource ID */
      BYTE nType )        /* [in] Type of the resource to delete */
{
    HKEY hkey;
    DWORD r;
    LPSTR valname;

    WARN( "(%p(%s), %d, %d): totally insecure\n",
           pbResource, debugstr_a(pbResource), cbResource, nType );

    /* @@ Wine registry key: HKCU\Software\Wine\Wine\Mpr */
    r = RegCreateKeyA( HKEY_CURRENT_USER, mpr_key, &hkey );
    if( r )
        return WN_ACCESS_DENIED;

    valname = MPR_GetValueName( pbResource, cbResource, nType );
    if( valname )
    {
        r = RegDeleteValueA( hkey, valname );
        if( r )
            r = WN_ACCESS_DENIED;
        else
            r = WN_SUCCESS;
        HeapFree( GetProcessHeap(), 0, valname );
    }
    else
        r = WN_OUT_OF_MEMORY;

    return r;
}

/*****************************************************************
 * WNetGetCachedPassword [MPR.@]  Retrieves password from cache
 *
 * NOTES
 *  the stub seems to be wrong:
 *	arg1:	ptr	0x40xxxxxx -> (no string)
 *	arg2:	len	36
 *	arg3:	ptr	0x40xxxxxx -> (no string)
 *	arg4:	ptr	0x40xxxxxx -> 0xc8
 *	arg5:	type?	4
 *
 *	---- everything below this line might be wrong (js) -----
 * RETURNS
 *    Success: WN_SUCCESS
 *    Failure: WN_ACCESS_DENIED, WN_BAD_PASSWORD, WN_BAD_VALUE,
 *             WN_NET_ERROR, WN_NOT_SUPPORTED, WN_OUT_OF_MEMORY
 */
DWORD WINAPI WNetGetCachedPassword(
    LPSTR pbResource,   /* [in]  Name of workgroup, computer, or resource */
    WORD cbResource,    /* [in]  Size of name */
    LPSTR pbPassword,   /* [out] Buffer to receive password */
    LPWORD pcbPassword, /* [out] Receives size of password */
    BYTE nType)         /* [in]  Type of password to retrieve */
{
    HKEY hkey;
    DWORD r, type = 0, sz;
    LPSTR valname;

    WARN( "(%p(%s), %d, %p, %p, %d): totally insecure\n",
           pbResource, debugstr_a(pbResource), cbResource,
	   pbPassword, pcbPassword, nType );

    memset( pbPassword, 0, *pcbPassword);

    /* @@ Wine registry key: HKCU\Software\Wine\Wine\Mpr */
    r = RegCreateKeyA( HKEY_CURRENT_USER, mpr_key, &hkey );
    if( r )
        return WN_ACCESS_DENIED;

    valname = MPR_GetValueName( pbResource, cbResource, nType );
    if( valname )
    {
        sz = *pcbPassword;
        r = RegQueryValueExA( hkey, valname, 0, &type, (LPBYTE)pbPassword, &sz );
        *pcbPassword = sz;
        if( r )
            r = WN_CANCEL;
        else
            r = WN_SUCCESS;
        HeapFree( GetProcessHeap(), 0, valname );
    }
    else
        r = WN_OUT_OF_MEMORY;

    return r;
}

/*******************************************************************
 * WNetEnumCachedPasswords [MPR.@]
 *
 * NOTES
 *	The parameter count is verified
 * 
 *  This function is a huge security risk, as virii and such can use
 * it to grab all the passwords in the cache.  It's bad enough to 
 * store the passwords (insecurely).
 *
 *  bpPrefix and cbPrefix are used to filter the returned passwords
 *   the first cbPrefix bytes of the password resource identifier
 *   should match the same number of bytes in bpPrefix
 *
 * RETURNS
 *   Success: WN_SUCCESS   (even if no entries were enumerated)
 *   Failure: WN_ACCESS_DENIED, WN_BAD_PASSWORD, WN_BAD_VALUE,
 *             WN_NET_ERROR, WN_NOT_SUPPORTED, WN_OUT_OF_MEMORY
 */

UINT WINAPI WNetEnumCachedPasswords(
      LPSTR pbPrefix,  /* [in] prefix to filter cache entries */
      WORD cbPrefix,   /* [in] number of bytes in Prefix substring */
      BYTE nType,      /* [in] match the Type ID of the entry */
      ENUMPASSWORDPROC enumPasswordProc,  /* [in] callback function */
      DWORD param)     /* [in] parameter passed to enum function */
{
    HKEY hkey;
    DWORD r, type, val_sz, data_sz, i, j, size;
    PASSWORD_CACHE_ENTRY *entry;
    CHAR val[256], prefix[6];

    WARN( "(%s, %d, %d, %p, 0x%08x) totally insecure\n",
           debugstr_an(pbPrefix,cbPrefix), cbPrefix,
	   nType, enumPasswordProc, param );

    /* @@ Wine registry key: HKCU\Software\Wine\Wine\Mpr */
    r = RegCreateKeyA( HKEY_CURRENT_USER, mpr_key, &hkey );
    if( r )
        return WN_ACCESS_DENIED;

    sprintf(prefix, "X-%02X-", nType );

    for( i=0;  ; i++ )
    {
        val_sz  = sizeof val;
        data_sz = 0;
        type    = 0;
        val[0] = 0;
        r = RegEnumValueA( hkey, i, val, &val_sz, NULL, &type, NULL, &data_sz );
        if( r != ERROR_SUCCESS )
            break;
        if( type != REG_BINARY )
            continue;

        /* check the value is in the format we expect */
        if( val_sz < sizeof prefix )
            continue;
        if( memcmp( prefix, val, 5 ) )
            continue;

        /* decode the value */
        for(j=5; j<val_sz; j+=2 )
        {
            signed char hi = ctox( val[j] ), lo = ctox( val[j+1] );
            if( ( hi < 0 ) || ( lo < 0 ) )
                break;
            val[(j-5)/2] = (hi<<4) | lo;
        }

        /* find the decoded length */
        val_sz = (j - 5)/2;
        val[val_sz]=0;
        if( val_sz < cbPrefix )
            continue;

        /* check the prefix matches */
        if( memcmp(val, pbPrefix, cbPrefix) )
            continue;

        /* read the value data */
        size = offsetof( PASSWORD_CACHE_ENTRY, abResource[val_sz + data_sz] );
        entry = HeapAlloc( GetProcessHeap(), 0, size );
        memcpy( entry->abResource, val, val_sz );
        entry->cbEntry = size;
        entry->cbResource = val_sz;
        entry->cbPassword = data_sz;
        entry->iEntry = i;
        entry->nType = nType;
        size = sizeof val;
        r = RegEnumValueA( hkey, i, val, &size, NULL, &type,
                           &entry->abResource[val_sz], &data_sz );
        if( r == ERROR_SUCCESS )
            enumPasswordProc( entry, param );
        HeapFree( GetProcessHeap(), 0, entry );
    }

    RegCloseKey( hkey );

    return WN_SUCCESS;
}