/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    httpfilt.cxx

Abstract:

    This module contains the code to create or set the HTTP PCT/SSL keys and
    password

Author:

    John Ludeman (johnl)   19-Oct-1995

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntsecapi.h>

#include <windows.h>

#define SECURITY_WIN32
#include <sspi.h>
#include <spseal.h>
#include <issperr.h>
#include <sslsp.h>
#include <w3svc.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <strings.h>

//
// macros
//

#define IS_ARG(c)   ((c) == L'-' || (c) == L'/')

#define TO_UNICODE( pch, ach )  \
    MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, (pch), -1, (ach), sizeof((ach))/sizeof(WCHAR))

#define TO_ANSI( pch, ach )     \
    WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK, pch, -1, ach, sizeof(ach), 0, 0 )

//
//  Private constants.
//

//
//  Private types.
//

BOOL fUUDecode = TRUE;

//
//  Private prototypes.
//

DWORD
SetRegKeys(
    IN  LPWSTR        pszServer,
    IN  LPWSTR        pszPrivateKeyFile,
    IN  LPWSTR        pszCertificateFile,
    IN  LPWSTR        pszPassword,
    IN  LPWSTR        pszAddress
    );

BOOL
SetKeySecret(
    WCHAR * pszServer,
    WCHAR * pszFormat,
    WCHAR * pszAddress,
    VOID *  pvData,
    DWORD   cbData
    );

void usage();

VOID
uudecode_cert(
    char   * bufcoded,
    DWORD  * pcbDecoded
    );

VOID
printfids(
    DWORD ids,
    ...
    );

DWORD
DeleteAll(
    WCHAR * pszServer
    );

BOOL
TsGetSecretW(
    WCHAR *       pszSecretName,
    WCHAR * *     ppchValue
    );

//
//  Public functions.
//


int
_CRTAPI1
main(
    int   argc,
    char * argv[]
    )
{
    DWORD  err;
    CHAR   buff[MAX_PATH+1];
    BOOL   fDeleteAll = FALSE;

    LPWSTR password = NULL;
    LPWSTR privatekey = NULL;
    LPWSTR cert = NULL;
    LPWSTR address = NULL;
    LPWSTR server = NULL;

    WCHAR  achpassword[MAX_PATH+1];
    WCHAR  achprivatekey[MAX_PATH+1];
    WCHAR  achcert[MAX_PATH+1];
    WCHAR  achaddress[MAX_PATH+1];
    WCHAR  achserver[MAX_PATH+1];


    printfids( IDS_BANNER1 );
    printfids( IDS_BANNER2 );

    for (--argc, ++argv; argc; --argc, ++argv) {
        if (IS_ARG(**argv)) {
            switch (*++*argv) {

            case 'u':
            case 'U':
                fUUDecode = FALSE;
                break;

            case 'd':
            case 'D':
                fDeleteAll = TRUE;
                break;

            default:
                printfids( IDS_BAD_FLAG, **argv );
                usage();

            }
        } else if ( !server && (*argv)[0] == L'\\' && (*argv)[1] == L'\\'
                    && !password ) {
            TO_UNICODE( (*argv) + 2, achserver );
            server = achserver;
        } else if (!password) {
            TO_UNICODE( *argv, achpassword );
            password = achpassword;
        } else if (!privatekey) {
            TO_UNICODE( *argv, achprivatekey );
            privatekey = achprivatekey;
        } else if (!cert) {
            TO_UNICODE( *argv, achcert );
            cert = achcert;
        } else if (!address) {
            TO_UNICODE( *argv, achaddress );
            address = achaddress;
        } else {
            printfids( IDS_BAD_ARG, *argv);
            usage();
        }
    }

    if ( fDeleteAll )
    {
        return DeleteAll( server );
    }

    //
    //  Address and server are optional
    //

    if (!(password && privatekey && cert)) {
        printfids( IDS_MISSING_ARG );
        usage();
    }

    if ( err = SetRegKeys( server, privatekey, cert, password, address ) )
    {
        printfids( IDS_FAILED_TO_SET );
    }
    else
    {
        if ( address )
        {
            TO_ANSI( address, buff );
            printfids( IDS_SUCCESSFUL_SET,
                       buff );
        }
        else
        {
            printfids( IDS_SUCCESSFUL_SET_DEF );
        }

    }

    return err;

}   // main

void usage()
{
    printfids( IDS_USAGE1 );
    printfids( IDS_USAGE2 );
    //printfids( IDS_USAGE3 );      // -p help
    printfids( IDS_USAGE4 );
    printfids( IDS_USAGE5 );
    printfids( IDS_USAGE6 );
    printfids( IDS_USAGE7 );
    printfids( IDS_USAGE8 );
    printfids( IDS_USAGE9 );
    printfids( IDS_USAGE10 );
    printfids( IDS_USAGE11 );
    printfids( IDS_USAGE12 );
    printfids( IDS_USAGE13 );
    //printfids( IDS_USAGE14 );     // -p help
    printfids( IDS_USAGE15 );
    printfids( IDS_USAGE16 );
    printfids( IDS_USAGE17 );
    printfids( IDS_USAGE18 );
    printfids( IDS_USAGE19 );
    printfids( IDS_USAGE20 );

    exit(1);
}


//+---------------------------------------------------------------------------
//
//  Function:   SetRegKeys
//
//  Synopsis:   This loads the data contained in two files, a private key
//              file, which contains the key, and a certificate file,
//              which contains the certificate of the public portion of the key.
//              These are loaded, then turned into a credential handle, then
//              set in the registry as secrets
//
//  Arguments:  [pszServer]          -- Server to create secrets, NULL for local
//              [pszPrivateKeyFile]  -- Unicode file name
//              [pszCertificateFile] -- Unicode file name
//              [pszPassword]        -- Unicode password
//              [pszAddress]         -- Unicode IP address for name or NULL
//
//  History:    9-27-95   RichardW   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
DWORD
SetRegKeys(
    IN  LPWSTR        pszServer,
    IN  LPWSTR        pszPrivateKeyFile,
    IN  LPWSTR        pszCertificateFile,
    IN  LPWSTR        pszPassword,
    IN  LPWSTR        pszAddress
    )
{
    HANDLE          hFile;
    SSL_CREDENTIAL_CERTIFICATE  creds;
    DWORD           cbRead;
    SECURITY_STATUS scRet = 0;
    TimeStamp       tsExpiry;
    CHAR            achPassword[MAX_PATH + 1];
    CredHandle      hCreds;
    DWORD           cch;
    CHAR            buff[MAX_PATH+1];

    //
    // Fetch data from files:
    //

    hFile = CreateFileW( pszPrivateKeyFile,
                         GENERIC_READ,
                         FILE_SHARE_READ,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL
                         );

    if (hFile == INVALID_HANDLE_VALUE)
    {
        TO_ANSI( pszPrivateKeyFile, buff );

        printfids( IDS_FILE_NOT_FOUND,
                   GetLastError(),
                   buff );

        return GetLastError();
    }

    creds.cbPrivateKey = GetFileSize( hFile, NULL );

    if (creds.cbPrivateKey == (DWORD) -1 )
    {
        CloseHandle( hFile );
        return GetLastError();
    }

    creds.pPrivateKey = LocalAlloc( LMEM_FIXED, creds.cbPrivateKey );

    if ( !creds.pPrivateKey )
    {
        CloseHandle( hFile );
        return GetLastError();
    }

    if (! ReadFile( hFile,
                    creds.pPrivateKey,
                    creds.cbPrivateKey,
                    &cbRead,
                    NULL ) )
    {
        CloseHandle( hFile );

        LocalFree( creds.pPrivateKey );

        return GetLastError();
    }

    CloseHandle( hFile );

    //
    //  Only the certificate is UUencoded
    //

    hFile = CreateFileW( pszCertificateFile,
                         GENERIC_READ,
                         FILE_SHARE_READ,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL );

    if (hFile == INVALID_HANDLE_VALUE)
    {
        TO_ANSI( pszCertificateFile, buff );
        printfids( IDS_FILE_NOT_FOUND,
                   GetLastError(),
                   buff );

        LocalFree( creds.pPrivateKey );
        return GetLastError();
    }

    creds.cbCertificate = GetFileSize( hFile, NULL );

    if (creds.cbCertificate == (DWORD) -1 )
    {
        CloseHandle( hFile );

        LocalFree( creds.pPrivateKey );

        return GetLastError();
    }

    creds.pCertificate = LocalAlloc( LMEM_FIXED, creds.cbCertificate + 1);

    if ( !creds.pCertificate )
    {
        CloseHandle( hFile );

        LocalFree( creds.pPrivateKey );

        return GetLastError();
    }

    if (! ReadFile( hFile,
                    creds.pCertificate,
                    creds.cbCertificate,
                    &cbRead,
                    NULL ) )
    {
        CloseHandle( hFile );

        LocalFree( creds.pPrivateKey );

        LocalFree( creds.pCertificate );

        return GetLastError();
    }

    CloseHandle( hFile );

    //
    //  Zero terminate so we can uudecode
    //

    ((BYTE *)creds.pCertificate)[cbRead] = '\0';

    if ( fUUDecode )
    {
        uudecode_cert( creds.pCertificate,
                       &creds.cbCertificate );
    }

    //
    // Whew!  Now that we have safely loaded the keys from disk, get a cred
    // handle based on the certificate/prv key combo
    //

    //
    //  BUGBUG - password field should be Unicode, do a quick conversion
    //  until structure is fixed
    //

    cch = TO_ANSI( pszPassword, achPassword );

    if ( !cch )
    {
        return GetLastError();
    }

    creds.pszPassword = achPassword;

    //
    //  Note we always do the credential check locally even if the server is
    //  remote.  This means the local machine must have the correct security
    //  provider package installed.
    //

#if 0
    if ( !pszServer )
    {
#endif
        scRet = AcquireCredentialsHandleW(  NULL,               // My name (ignored)
                                            SSLSP_NAME_W,       // Package
                                            SECPKG_CRED_INBOUND,// Use
                                            NULL,               // Logon Id (ign.)
                                            &creds,             // auth data
                                            NULL,               // dce-stuff
                                            NULL,               // dce-stuff
                                            &hCreds,            // Handle
                                            &tsExpiry );

        if ( FAILED(scRet) )
        {
            if ( scRet == SEC_E_NOT_OWNER )
            {
                printfids( IDS_BAD_PASSWORD );
            }
            else if ( scRet == SEC_E_SECPKG_NOT_FOUND )
            {
                printfids( IDS_SECPKG_NOT_FOUND );
            }
            else
            {
                printfids( IDS_KEYCHECK_FAILED,
                           scRet );
            }
        }
#if 0
    }
    else
    {
        printf("\nWarning! Bypassing credential check because target is remote\n");
    }
#endif

    //
    //  If we successfully acquired a credential handle, set the secrets
    //

    if ( !FAILED( scRet ))
    {
        if ( !pszServer )
        {
            FreeCredentialsHandle( &hCreds );
        }

        //
        //  Supply the default name if none was supplied
        //

        if ( !pszAddress )
            pszAddress = L"Default";

        //
        //  Set the secrets
        //

        if ( !SetKeySecret( pszServer,
                            L"W3_PUBLIC_KEY_%s",
                            pszAddress,
                            creds.pCertificate,
                            creds.cbCertificate ) ||
             !SetKeySecret( pszServer,
                            L"W3_PRIVATE_KEY_%s",
                            pszAddress,
                            creds.pPrivateKey,
                            creds.cbPrivateKey ) ||
             !SetKeySecret( pszServer,
                            L"W3_KEY_PASSWORD_%s",
                            pszAddress,
                            achPassword,
                            strlen( achPassword ) + 1) )
        {
            printfids( IDS_SETSECRET_FAILED,
                       GetLastError());

            scRet = (SECURITY_STATUS) GetLastError();
        }
        else
        {
            WCHAR InstalledKeys[16384];
            WCHAR * pchKeys;

            *InstalledKeys = L'\0';

            //
            //  Ok if this fails, it may not exist yet
            //

            if ( TsGetSecretW( W3_SSL_KEY_LIST_SECRET,
                               &pchKeys ))
            {
                wcscpy( InstalledKeys, pchKeys );
            }

            wcscat( InstalledKeys, pszAddress );
            wcscat( InstalledKeys, L"," );

#if DBG
            printf("New list: %S\n", InstalledKeys);
#endif

            if ( !SetKeySecret( pszServer,
                                L"W3_KEY_LIST",
                                pszAddress,
                                InstalledKeys,
                                (wcslen( InstalledKeys ) + 1) * sizeof(WCHAR)))
            {
#if DBG
                printf("Warning: failed to set key list data, error %d\n");
#endif
                scRet = (SECURITY_STATUS) GetLastError();
            }
        }
    }

    //
    // Zero out and free the key data memory, on success or fail
    //

    ZeroMemory( creds.pPrivateKey, creds.cbPrivateKey );
    ZeroMemory( creds.pCertificate, creds.cbCertificate );
    ZeroMemory( achPassword, cch );
    ZeroMemory( pszPassword, cch );

    LocalFree( creds.pPrivateKey );
    LocalFree( creds.pCertificate );

    //
    // Tell the caller about it.
    //

    return( scRet );

}

BOOL
SetKeySecret(
    WCHAR * pszServer,
    WCHAR * pszFormat,
    WCHAR * pszAddress,
    VOID *  pvData,
    DWORD   cbData
    )
{
    BOOL                  fResult;
    NTSTATUS              ntStatus;
    LSA_UNICODE_STRING    unicodeName;
    LSA_UNICODE_STRING    unicodeSecret;
    LSA_UNICODE_STRING    unicodeServer;
    LSA_HANDLE            hPolicy;
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    WCHAR                 achSecretName[MAX_PATH+1];
    CHAR                  buff[MAX_PATH+1];


    //
    //  Open a policy to the remote LSA
    //

    InitializeObjectAttributes( &ObjectAttributes,
                                NULL,
                                0L,
                                NULL,
                                NULL );

    if ( pszServer )
    {
        unicodeServer.Buffer        = pszServer;
        unicodeServer.Length        = wcslen( pszServer ) * sizeof(WCHAR);
        unicodeServer.MaximumLength = unicodeServer.Length + sizeof(WCHAR);
    }

    ntStatus = LsaOpenPolicy( pszServer ? &unicodeServer : NULL,
                              &ObjectAttributes,
                              POLICY_ALL_ACCESS,
                              &hPolicy );

    if ( !NT_SUCCESS( ntStatus ) )
    {
        SetLastError( LsaNtStatusToWinError( ntStatus ) );

        TO_ANSI( pszServer, buff );

        printfids(IDS_FAILED_OPENING_SERVER,
                  buff,
                  GetLastError() );

        return FALSE;
    }

    //
    //  Build the secret name
    //

    wsprintfW( achSecretName,
               pszFormat,
               pszAddress );

    unicodeSecret.Buffer        = pvData;
    unicodeSecret.Length        = (USHORT) cbData;
    unicodeSecret.MaximumLength = (USHORT) cbData;

    unicodeName.Buffer        = achSecretName;
    unicodeName.Length        = wcslen( achSecretName ) * sizeof(WCHAR);
    unicodeName.MaximumLength = unicodeName.Length + sizeof(WCHAR);

    //
    //  Query the secret value.
    //

    ntStatus = LsaStorePrivateData( hPolicy,
                                    &unicodeName,
                                    pvData ? &unicodeSecret : NULL );

    fResult = NT_SUCCESS(ntStatus);

    //
    //  Cleanup & exit.
    //

    LsaClose( hPolicy );

    if ( !fResult )
        SetLastError( LsaNtStatusToWinError( ntStatus ));

    return fResult;

}   // SetKeySecret

VOID
printfids(
    DWORD ids,
    ...
    )
{
    CHAR szBuff[2048];
    CHAR szString[2048];
    va_list  argList;

    //
    //  Try and load the string
    //

    if ( !LoadString( GetModuleHandle( NULL ),
                      ids,
                      szString,
                      sizeof( szString ) ))
    {
        printf( "Error loading string ID %d\n",
                ids );

        return;
    }

    va_start( argList, ids );
    vsprintf( szBuff, szString, argList );
    va_end( argList );

    printf( szBuff );
}

const int pr2six[256]={
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
    52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
    10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
    28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64
};

//
//  We have to squirt a record into the decoded stream
//

#define CERT_RECORD            13
#define CERT_SIZE_HIBYTE        2       //  Index into record of record size
#define CERT_SIZE_LOBYTE        3

unsigned char abCertHeader[] = {0x30, 0x82,           // Record
                                0x00, 0x00,           // Size of cert + buff
                                0x04, 0x0b, 0x63, 0x65,// Cert record data
                                0x72, 0x74, 0x69, 0x66,
                                0x69, 0x63, 0x61, 0x74,
                                0x65 };

VOID uudecode_cert(char   * bufcoded,
                   DWORD  * pcbDecoded )
{
    int nbytesdecoded;
    char *bufin = bufcoded;
    unsigned char *bufout = bufcoded;
    unsigned char *pbuf;
    int nprbytes;
    char * beginbuf = bufcoded;

    /* Strip leading whitespace. */

    while(*bufcoded==' ' ||
          *bufcoded == '\t' ||
          *bufcoded == '\r' ||
          *bufcoded == '\n' )
    {
          bufcoded++;
    }

    //
    //  If there is a beginning '---- ....' then skip the first line
    //

    if ( bufcoded[0] == '-' && bufcoded[1] == '-' )
    {
        bufin = strchr( bufcoded, '\n' );

        if ( bufin )
        {
            bufin++;
            bufcoded = bufin;
        }
        else
        {
            bufin = bufcoded;
        }
    }
    else
    {
        bufin = bufcoded;
    }

    //
    //  Strip all cr/lf from the block
    //

    pbuf = bufin;
    while ( *pbuf )
    {
        if ( *pbuf == '\r' || *pbuf == '\n' )
        {
            memmove( pbuf, pbuf+1, strlen( pbuf + 1) + 1 );
        }
        else
        {
            pbuf++;
        }
    }

    /* Figure out how many characters are in the input buffer.
     * If this would decode into more bytes than would fit into
     * the output buffer, adjust the number of input bytes downwards.
     */

    while(pr2six[*(bufin++)] <= 63);
    nprbytes = bufin - bufcoded - 1;
    nbytesdecoded = ((nprbytes+3)/4) * 3;

    bufin  = bufcoded;

    while (nprbytes > 0) {
        *(bufout++) =
            (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
        *(bufout++) =
            (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
        *(bufout++) =
            (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
        bufin += 4;
        nprbytes -= 4;
    }

    if(nprbytes & 03) {
        if(pr2six[bufin[-2]] > 63)
            nbytesdecoded -= 2;
        else
            nbytesdecoded -= 1;
    }

    //
    //  Now we need to add a new wrapper sequence around the certificate
    //  indicating this is a certificate
    //

    memmove( beginbuf + sizeof(abCertHeader),
             beginbuf,
             nbytesdecoded );

    memcpy( beginbuf,
            abCertHeader,
            sizeof(abCertHeader) );

    //
    //  The beginning record size is the total number of bytes decoded plus
    //  the number of bytes in the certificate header
    //

    beginbuf[CERT_SIZE_HIBYTE] = (BYTE) (((USHORT)nbytesdecoded+CERT_RECORD) >> 8);
    beginbuf[CERT_SIZE_LOBYTE] = (BYTE) ((USHORT)nbytesdecoded+CERT_RECORD);

    nbytesdecoded += sizeof(abCertHeader);

    if ( pcbDecoded )
        *pcbDecoded = nbytesdecoded;
}

BOOL
TsGetSecretW(
    WCHAR *       pszSecretName,
    WCHAR * *     ppchValue
    )
/*++
    Description:

        Retrieves the specified unicode secret

        Note we're loose with the allocated buffer since we're a simple
        command line app.

    Arguments:

        pszSecretName - LSA Secret to retrieve
        ppchValue - Receives pointer to allocated buffer

    Returns:
        TRUE on success and FALSE if any failure.

--*/
{
    NTSTATUS              ntStatus;
    LSA_UNICODE_STRING *  punicodePassword = NULL;
    LSA_UNICODE_STRING    unicodeSecret;
    LSA_HANDLE            hPolicy;
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;


    //
    //  Open a policy to the remote LSA
    //

    InitializeObjectAttributes( &ObjectAttributes,
                                NULL,
                                0L,
                                NULL,
                                NULL );

    ntStatus = LsaOpenPolicy( NULL,
                              &ObjectAttributes,
                              POLICY_ALL_ACCESS,
                              &hPolicy );

    if ( !NT_SUCCESS( ntStatus ) )
    {
        SetLastError( LsaNtStatusToWinError( ntStatus ) );
        return FALSE;
    }

    unicodeSecret.Buffer        = pszSecretName;
    unicodeSecret.Length        = wcslen( pszSecretName ) * sizeof(WCHAR);
    unicodeSecret.MaximumLength = unicodeSecret.Length + sizeof(WCHAR);

    //
    //  Query the secret value.
    //

    ntStatus = LsaRetrievePrivateData( hPolicy,
                                       &unicodeSecret,
                                       &punicodePassword );

    if( NT_SUCCESS(ntStatus) )
    {
        *ppchValue = (WCHAR *) punicodePassword->Buffer;

        return TRUE;
    }

    return FALSE;

}   // TsGetSecretW

DWORD
DeleteAll(
    WCHAR * pszServer
    )
{
    WCHAR * pchKeys;
    WCHAR * pszAddress;

    if ( !TsGetSecretW( L"W3_KEY_LIST",
                        &pchKeys ))
    {
        printfids( IDS_NO_KEYS_INSTALLED );
        return NO_ERROR;
    }

#if DBG
    printf("Installed keys: %S\n", pchKeys);
#endif

    pszAddress = pchKeys;
    while ( pchKeys = wcschr( pchKeys, L',' ))
    {
        //
        //  Ignore empty segments
        //

        if ( *pszAddress != L',' )
        {
            *pchKeys = L'\0';

#if DBG
            printf("deleting %S\n", pszAddress );
#endif

            //
            //  Nuke the secrets
            //

            SetKeySecret( pszServer,
                          L"W3_PUBLIC_KEY_%s",
                          pszAddress,
                          NULL,
                          0 );
            SetKeySecret( pszServer,
                          L"W3_PRIVATE_KEY_%s",
                          pszAddress,
                          NULL,
                          0 );
            SetKeySecret( pszServer,
                          L"W3_KEY_PASSWORD_%s",
                          pszAddress,
                          NULL,
                          0 );
        }

        pchKeys++;
        pszAddress = pchKeys;
    }

    //
    //  Now delete the list key
    //


    if ( !SetKeySecret( pszServer,
                        L"W3_KEY_LIST",
                        L"",
                        NULL,
                        0 ))
    {
#if DBG
        printf("Warning: failed to set key list data, error %d\n");
#endif
        return GetLastError();
    }

    printfids( IDS_DELETE_SUCCESSFUL );

    return NO_ERROR;
}
