/*++

Copyright (c) 2000  Microsoft Corporation

Abstract:

    Port Proxy Helper.

--*/
#include "precomp.h"

GUID g_PpGuid = PORTPROXY_GUID;

#define KEY_PORTS L"System\\CurrentControlSet\\Services\\PortProxy"

typedef enum {
    V4TOV4,
    V4TOV6,
    V6TOV4,
    V6TOV6
} PPTYPE, *PPPTYPE;

typedef struct {
    PWCHAR Token;
    PWCHAR ListenFamily;
    PWCHAR ConnectFamily;
    PWCHAR KeyString;
} PPTYPEINFO, *PPPTYPEINFO;

#define IPV4_STR L"IPv4"
#define IPV6_STR L"IPv6"

PPTYPEINFO PpTypeInfo[] = {
    { CMD_V4TOV4, IPV4_STR, IPV4_STR, KEY_PORTS L"\\" CMD_V4TOV4 },
    { CMD_V4TOV6, IPV4_STR, IPV6_STR, KEY_PORTS L"\\" CMD_V4TOV6 },
    { CMD_V6TOV4, IPV6_STR, IPV4_STR, KEY_PORTS L"\\" CMD_V6TOV4 },
    { CMD_V6TOV6, IPV6_STR, IPV6_STR, KEY_PORTS L"\\" CMD_V6TOV6 },
};

//
// Port Proxy commands.
//
FN_HANDLE_CMD PpHandleReset;

FN_HANDLE_CMD PpHandleDelV4ToV4;
FN_HANDLE_CMD PpHandleDelV4ToV6;
FN_HANDLE_CMD PpHandleDelV6ToV4;
FN_HANDLE_CMD PpHandleDelV6ToV6;

FN_HANDLE_CMD PpHandleAddSetV4ToV4;
FN_HANDLE_CMD PpHandleAddSetV4ToV6;
FN_HANDLE_CMD PpHandleAddSetV6ToV4;
FN_HANDLE_CMD PpHandleAddSetV6ToV6;

FN_HANDLE_CMD PpHandleShowAll;
FN_HANDLE_CMD PpHandleShowV4ToV4;
FN_HANDLE_CMD PpHandleShowV4ToV6;
FN_HANDLE_CMD PpHandleShowV6ToV4;
FN_HANDLE_CMD PpHandleShowV6ToV6;

CMD_ENTRY  g_PpAddCmdTable[] =
{
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_ADD_V4TOV4, PpHandleAddSetV4ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_ADD_V4TOV6, PpHandleAddSetV4ToV6),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_ADD_V6TOV4, PpHandleAddSetV6ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_ADD_V6TOV6, PpHandleAddSetV6ToV6),
};

CMD_ENTRY  g_PpDelCmdTable[] =
{
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_DEL_V4TOV4, PpHandleDelV4ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_DEL_V4TOV6, PpHandleDelV4ToV6),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_DEL_V6TOV4, PpHandleDelV6ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_DEL_V6TOV6, PpHandleDelV6ToV6),
};

CMD_ENTRY  g_PpSetCmdTable[] =
{
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SET_V4TOV4, PpHandleAddSetV4ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SET_V4TOV6, PpHandleAddSetV4ToV6),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SET_V6TOV4, PpHandleAddSetV6ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SET_V6TOV6, PpHandleAddSetV6ToV6),
};

CMD_ENTRY g_PpShowCmdTable[] =
{
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SHOW_ALL,    PpHandleShowAll),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SHOW_V4TOV4, PpHandleShowV4ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SHOW_V4TOV6, PpHandleShowV4ToV6),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SHOW_V6TOV4, PpHandleShowV6ToV4),
    CREATE_UNDOCUMENTED_CMD_ENTRY(PP_SHOW_V6TOV6, PpHandleShowV6ToV6),
};

CMD_GROUP_ENTRY g_PpCmdGroups[] =
{
    CREATE_CMD_GROUP_ENTRY(GROUP_ADD,    g_PpAddCmdTable),
    CREATE_CMD_GROUP_ENTRY(GROUP_DELETE, g_PpDelCmdTable),
    CREATE_CMD_GROUP_ENTRY(GROUP_SHOW,   g_PpShowCmdTable),
    CREATE_CMD_GROUP_ENTRY(GROUP_SET,    g_PpSetCmdTable),
};

ULONG   g_ulNumPpCmdGroups = sizeof(g_PpCmdGroups)/sizeof(CMD_GROUP_ENTRY);

CMD_ENTRY g_PpTopCmds[] =
{
    CREATE_CMD_ENTRY(IPV6_RESET, PpHandleReset),
};

ULONG g_ulNumPpTopCmds = sizeof(g_PpTopCmds)/sizeof(CMD_ENTRY);

DWORD
WINAPI
PpStartHelper(
    IN CONST GUID *pguidParent,
    IN DWORD dwParentVersion
    )
{
    DWORD dwErr;
    NS_CONTEXT_ATTRIBUTES attMyAttributes;

    ZeroMemory(&attMyAttributes, sizeof(attMyAttributes));

    attMyAttributes.pwszContext = L"portproxy";
    attMyAttributes.guidHelper  = g_PpGuid;
    attMyAttributes.dwVersion   = PORTPROXY_HELPER_VERSION;
    attMyAttributes.dwFlags     = CMD_FLAG_LOCAL | CMD_FLAG_ONLINE;
    attMyAttributes.pfnDumpFn   = PpDump;
    attMyAttributes.ulNumTopCmds= g_ulNumPpTopCmds;
    attMyAttributes.pTopCmds    = (CMD_ENTRY (*)[])&g_PpTopCmds;
    attMyAttributes.ulNumGroups = g_ulNumPpCmdGroups;
    attMyAttributes.pCmdGroups  = (CMD_GROUP_ENTRY (*)[])&g_PpCmdGroups;

    dwErr = RegisterContext( &attMyAttributes );

    return dwErr;
}

VOID
ShowPorts(
    IN PPTYPE Type,
    IN FORMAT Format
    )
{
    ULONG Status, i, ListenChars, ConnectBytes, dwType;
    HKEY hType, hProto;
    WCHAR ListenBuffer[256], *ListenAddress, *ListenPort;
    WCHAR ConnectAddress[256], *ConnectPort;

    Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, PpTypeInfo[Type].KeyString, 0, 
                          KEY_QUERY_VALUE, &hType);
    if (Status != NO_ERROR) {
        return;
    }
    Status = RegOpenKeyEx(hType, TOKEN_VALUE_TCP, 0,
                          KEY_QUERY_VALUE, &hProto);
    if (Status == NO_ERROR) {
        for (i=0; ; i++) {
            ListenChars = sizeof(ListenBuffer)/sizeof(WCHAR);
            ConnectBytes = sizeof(ConnectAddress);
            Status = RegEnumValueW(hProto, i, ListenBuffer, &ListenChars, 
                                   NULL, &dwType, (PVOID)ConnectAddress, 
                                   &ConnectBytes);
            if (Status != NO_ERROR) {
                break;
            }

            if (dwType != REG_SZ) {
                continue;
            }

            ListenPort = wcschr(ListenBuffer, L'/');
            if (ListenPort) {
                //
                // Replace slash with NULL, so we have 2 strings to pass
                // to getaddrinfo.
                //
                ListenAddress = ListenBuffer;
                *ListenPort++ = L'\0';
            } else {
                //
                // If the address data didn't include a connect address
                // use "*".
                //
                ListenAddress = L"*";
                ListenPort = ListenBuffer;
            }

            ConnectPort = wcschr(ConnectAddress, L'/');
            if (ConnectPort) {
                //
                // Replace slash with NULL, so we have 2 strings to pass
                // to getaddrinfo.
                //
                *ConnectPort++ = L'\0';
            } else {
                //
                // If the address data didn't include a connect port
                // number, use the same port as the listen port number.
                //
                ConnectPort = ListenPort;
            }

            if (Format == FORMAT_NORMAL) {
                if (i==0) {
                    // DisplayMessage(g_hModule, MSG_PORT_PROXY_HEADER,
                    // PpTypeInfo[Type].ListenFamily,
                    // PpTypeInfo[Type].ConnectFamily);
                }
                // DisplayMessage(g_hModule, MSG_PORT_PROXY, ListenAddress,
                // ListenPort, ConnectAddress, ConnectPort);
            } else {
                DisplayMessageT(DMP_ADD_PORT_PROXY, PpTypeInfo[Type].Token);
                DisplayMessageT(DMP_STRING_ARG, TOKEN_LISTENPORT, ListenPort);
                DisplayMessageT(DMP_STRING_ARG, TOKEN_CONNECTADDRESS, ConnectAddress);
                DisplayMessageT(DMP_STRING_ARG, TOKEN_CONNECTPORT, ConnectPort);
                DisplayMessageT(DMP_NEWLINE);
            }
        }
        RegCloseKey(hProto);
    }
    RegCloseKey(hType);
}

ULONG
PpHandleShowV4ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    ShowPorts(V4TOV4, FORMAT_NORMAL);

    return STATUS_SUCCESS; 
}

ULONG
PpHandleShowV6ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    ShowPorts(V6TOV4, FORMAT_NORMAL);

    return STATUS_SUCCESS;
}

ULONG
PpHandleShowV4ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    ShowPorts(V4TOV6, FORMAT_NORMAL);

    return STATUS_SUCCESS;
}

ULONG
PpHandleShowV6ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    ShowPorts(V6TOV6, FORMAT_NORMAL);

    return STATUS_SUCCESS;
}

ULONG
PpHandleShowAll(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    ShowPorts(V4TOV4, FORMAT_NORMAL);
    ShowPorts(V4TOV6, FORMAT_NORMAL);
    ShowPorts(V6TOV4, FORMAT_NORMAL);
    ShowPorts(V6TOV6, FORMAT_NORMAL);

    return STATUS_SUCCESS;
}

PWCHAR
PpFormValue(
    IN PWCHAR Address,
    IN PWCHAR Port
    )
{
    ULONG Length;
    PWCHAR Value;

    Length = wcslen(Address) + wcslen(Port) + 2;
    Value = MALLOC(Length * sizeof(WCHAR));

    swprintf(Value, L"%s/%s", Address, Port);

    return Value;
}

TOKEN_VALUE g_ProtocolEnum[] = {{ TOKEN_VALUE_TCP, IPPROTO_TCP }};

ULONG
PpHandleAddSetPort(
    IN PPTYPE Type,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc
    )
{
    ULONG Status, i;
    TAG_TYPE Tags[] = {{TOKEN_LISTENPORT,     NS_REQ_PRESENT, FALSE},
                       {TOKEN_CONNECTADDRESS, NS_REQ_ZERO,    FALSE},
                       {TOKEN_CONNECTPORT,    NS_REQ_ZERO,    FALSE},
                       {TOKEN_LISTENADDRESS,  NS_REQ_ZERO,    FALSE},
                       {TOKEN_PROTOCOL,       NS_REQ_ZERO,    FALSE}};
    ULONG TagType[sizeof(Tags)/sizeof(TAG_TYPE)];
    PWCHAR ListenAddress = NULL, ListenPort = NULL;
    PWCHAR ConnectAddress = NULL, ConnectPort = NULL;
    PWCHAR ProtocolString = NULL;
    ULONG Protocol = IPPROTO_TCP;
    PWCHAR KeyValue, KeyData;
    HKEY hType, hProto;

    if ((Type == V4TOV4) || (Type == V6TOV6)) {
        Tags[1].dwRequired = NS_REQ_PRESENT;
    }

    Status = PreprocessCommand(g_hModule,
                           Argv,
                           CurrentIndex,
                           Argc,
                           Tags,
                           sizeof(Tags)/sizeof(TAG_TYPE),
                           0,
                           sizeof(Tags)/sizeof(TAG_TYPE),
                           TagType);

    for (i=0; (Status == NO_ERROR) && (i < Argc-CurrentIndex); i++) {
        switch (TagType[i]) {
        case 0: // LISTENPORT
            ListenPort = Argv[CurrentIndex + i];
            break;

        case 1: // CONNECTADDRESS
            ConnectAddress = Argv[CurrentIndex + i];
            break;

        case 2: // CONNECTPORT
            ConnectPort = Argv[CurrentIndex + i];
            break;

        case 3: // LISTENADDRESS
            ListenAddress = Argv[CurrentIndex + i];
            break;

        case 4: // PROTOCOL
            Status = MatchEnumTag(NULL,
                                  Argv[CurrentIndex + i],
                                  NUM_TOKENS_IN_TABLE(g_ProtocolEnum),
                                  g_ProtocolEnum,
                                  (PULONG)&Protocol);
            if (Status != NO_ERROR) {
                Status = ERROR_INVALID_PARAMETER;
            }
            ProtocolString = Argv[CurrentIndex + i];
            break;

        default:
            Status = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (Status != NO_ERROR) {
        return Status;
    }

    if (ConnectAddress == NULL) {
        ConnectAddress = L"localhost";
    }
    if (ListenAddress == NULL) {
        ListenAddress = L"*";
    }    
    if (ProtocolString == NULL) {
        ProtocolString = TOKEN_VALUE_TCP;
    }
    if (ConnectPort == NULL) {
        ConnectPort = ListenPort;
    }    

    Status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, PpTypeInfo[Type].KeyString, 0,
                            NULL, 0, KEY_ALL_ACCESS, NULL, &hType, NULL);
    if (Status != NO_ERROR) {
        return Status;
    }

    Status = RegCreateKeyEx(hType, ProtocolString, 0, 
                            NULL, 0, KEY_ALL_ACCESS, NULL, &hProto, NULL);
    if (Status != NO_ERROR) {
        RegCloseKey(hType);
        return Status;
    }

    KeyValue = PpFormValue(ListenAddress, ListenPort);
    KeyData = PpFormValue(ConnectAddress, ConnectPort);
    if (KeyValue && KeyData) {
        Status = RegSetValueEx(hProto, KeyValue, 0, REG_SZ, (PVOID)KeyData,
                               wcslen(KeyData) * sizeof(WCHAR));
        FREE(KeyValue);
    }

    RegCloseKey(hProto);
    RegCloseKey(hType);

    Ip6to4PokeService();

    return Status;
}

ULONG
PpHandleDeletePort(
    IN PPTYPE Type,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc
    )
{
    ULONG Status, i;
    TAG_TYPE Tags[] = {{TOKEN_LISTENPORT,     NS_REQ_PRESENT, FALSE},
                       {TOKEN_LISTENADDRESS,  NS_REQ_ZERO,    FALSE},
                       {TOKEN_PROTOCOL,       NS_REQ_ZERO,    FALSE}};
    ULONG TagType[sizeof(Tags)/sizeof(TAG_TYPE)];
    PWCHAR ListenAddress = NULL, ListenPort = NULL;
    PWCHAR ConnectAddress = NULL, ConnectPort = NULL;
    ULONG Protocol;
    PWCHAR ProtocolString = NULL;
    HKEY hType, hProto;
    PWCHAR Value;

    Status = PreprocessCommand(g_hModule,
                           Argv,
                           CurrentIndex,
                           Argc,
                           Tags,
                           sizeof(Tags)/sizeof(TAG_TYPE),
                           0,
                           sizeof(Tags)/sizeof(TAG_TYPE),
                           TagType);

    for (i=0; (Status == NO_ERROR) && (i < Argc-CurrentIndex); i++) {
        switch (TagType[i]) {
        case 0: // LISTENPORT
            ListenPort = Argv[CurrentIndex + i];
            break;

        case 1: // LISTENADDRESS
            ListenAddress = Argv[CurrentIndex + i];
            break;

        case 2: // PROTOCOL
            Status = MatchEnumTag(NULL,
                                  Argv[CurrentIndex + i],
                                  NUM_TOKENS_IN_TABLE(g_ProtocolEnum),
                                  g_ProtocolEnum,
                                  (PULONG)&Protocol);
            if (Status != NO_ERROR) {
                Status = ERROR_INVALID_PARAMETER;
            }
            ProtocolString = Argv[CurrentIndex + i];
            break;

        default:
            Status = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (Status != NO_ERROR) {
        return Status;
    }

    if (ListenAddress == NULL) {
        ListenAddress = L"*";
    }

    if (ProtocolString == NULL) {
        ProtocolString = TOKEN_VALUE_TCP;
    }

    Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, PpTypeInfo[Type].KeyString, 0, 
                          KEY_ALL_ACCESS, &hType);
    if (Status != NO_ERROR) {
        return Status;
    }

    Status = RegOpenKeyEx(hType, ProtocolString, 0, KEY_ALL_ACCESS, &hProto);
    if (Status != NO_ERROR) {
        RegCloseKey(hType);
        return Status;
    }

    Value = PpFormValue(ListenAddress, ListenPort);
    if (Value) {
        Status = RegDeleteValue(hProto, Value);
        FREE(Value);
    }

    RegCloseKey(hProto);
    RegCloseKey(hType);

    Ip6to4PokeService();

    return Status;
}

ULONG
PpHandleDelV4ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleDeletePort(V4TOV4, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleDelV4ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleDeletePort(V4TOV6, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleDelV6ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleDeletePort(V6TOV4, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleDelV6ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleDeletePort(V6TOV6, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleAddSetV4ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleAddSetPort(V4TOV4, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleAddSetV4ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleAddSetPort(V4TOV6, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleAddSetV6ToV4(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleAddSetPort(V6TOV4, Argv, CurrentIndex, Argc);
}

ULONG
PpHandleAddSetV6ToV6(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    return PpHandleAddSetPort(V6TOV6, Argv, CurrentIndex, Argc);
}

VOID
PpReset(
    IN PPTYPE Type
    )
{
    HKEY hType;
    ULONG Status;

    Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, PpTypeInfo[Type].KeyString, 0, 
                          KEY_ALL_ACCESS, &hType);
    if (Status != NO_ERROR) {
        return;
    }

    SHDeleteKey(hType, TOKEN_VALUE_TCP);

    RegCloseKey(hType);

    Ip6to4PokeService();
}

ULONG
PpHandleReset(
    IN LPCWSTR MachineName,
    IN LPWSTR *Argv,
    IN ULONG CurrentIndex,
    IN ULONG Argc,
    IN ULONG Flags,
    IN LPCVOID Data,
    OUT BOOL *Done
    )
{
    PpReset(V4TOV4);
    PpReset(V4TOV6);
    PpReset(V6TOV4);
    PpReset(V6TOV6);

    return STATUS_SUCCESS;
}

DWORD
WINAPI
PpDump(
    IN LPCWSTR pwszRouter,
    IN OUT LPWSTR *ppwcArguments,
    IN DWORD dwArgCount,
    IN LPCVOID pvData
    )
{
    // DisplayMessage(g_hModule, DMP_PP_HEADER_COMMENTS);
    DisplayMessageT(DMP_PP_PUSHD);

    ShowPorts(V4TOV4, FORMAT_DUMP);
    ShowPorts(V4TOV6, FORMAT_DUMP);
    ShowPorts(V6TOV4, FORMAT_DUMP);
    ShowPorts(V6TOV6, FORMAT_DUMP);

    DisplayMessageT(DMP_PP_POPD);
    // DisplayMessage(g_hModule, DMP_PP_FOOTER_COMMENTS);

    return NO_ERROR;
}