/*

Copyright 2004 by the Massachusetts Institute of Technology

All rights reserved.

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the Massachusetts
Institute of Technology (M.I.T.) not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

*/

#define _WIN32_DCOM
#include <windows.h>
#include <netfw.h>
#include <objbase.h>
#include <oleauto.h>
#include "afsicf.h"

//#define TESTMAIN

#ifdef TESTMAIN
#include<stdio.h>
#pragma comment(lib,"ole32.lib")
#pragma comment(lib,"oleaut32.lib")
#define DEBUGOUT(x) printf x
#else
#define DEBUGOUT(x)
#endif

/* an IPv4, enabled port with global scope */
struct global_afs_port_type {
    LPWSTR	name;
	LONG	port;
	NET_FW_IP_PROTOCOL protocol;
};

typedef struct global_afs_port_type global_afs_port_t;

global_afs_port_t afs_clientPorts[] = {
	{ L"AFS CacheManager Callback (UDP)", 7001, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS CacheManager Callback (TCP)", 7001, NET_FW_IP_PROTOCOL_TCP }
};

global_afs_port_t afs_serverPorts[] = {
	{ L"AFS File Server (UDP)", 7000, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS File Server (TCP)", 7000, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS User & Group Database (UDP)", 7002, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS User & Group Database (TCP)", 7002, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Volume Location Database (UDP)", 7003, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Volume Location Database (TCP)", 7003, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS/Kerberos Authentication (UDP)", 7004, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS/Kerberos Authentication (TCP)", 7004, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Volume Mangement (UDP)", 7005, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Volume Mangement (TCP)", 7005, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Error Interpretation (UDP)", 7006, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Error Interpretation (TCP)", 7006, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Basic Overseer (UDP)", 7007, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Basic Overseer (TCP)", 7007, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Server-to-server Updater (UDP)", 7008, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Server-to-server Updater (TCP)", 7008, NET_FW_IP_PROTOCOL_TCP },
	{ L"AFS Remote Cache Manager (UDP)", 7009, NET_FW_IP_PROTOCOL_UDP },
	{ L"AFS Remote Cache Manager (TCP)", 7009, NET_FW_IP_PROTOCOL_TCP }
};

HRESULT icf_OpenFirewallProfile(INetFwProfile ** fwProfile) {
    HRESULT hr = S_OK;
    INetFwMgr* fwMgr = NULL;
    INetFwPolicy* fwPolicy = NULL;

    *fwProfile = NULL;

    // Create an instance of the firewall settings manager.
    hr = CoCreateInstance(
            __uuidof(NetFwMgr),
            NULL,
            CLSCTX_INPROC_SERVER,
            __uuidof(INetFwMgr),
            reinterpret_cast<void**>(static_cast<INetFwMgr**>(&fwMgr))
            );
    if (FAILED(hr))
    {
		DEBUGOUT(("Can't create fwMgr\n"));
        goto error;
    }

    // Retrieve the local firewall policy.
    hr = fwMgr->get_LocalPolicy(&fwPolicy);
    if (FAILED(hr))
    {
		DEBUGOUT(("Cant get local policy\n"));
        goto error;
    }

    // Retrieve the firewall profile currently in effect.
    hr = fwPolicy->get_CurrentProfile(fwProfile);
    if (FAILED(hr))
    {
		DEBUGOUT(("Can't get current profile\n"));
        goto error;
    }

error:

    // Release the local firewall policy.
    if (fwPolicy != NULL)
    {
        fwPolicy->Release();
    }

    // Release the firewall settings manager.
    if (fwMgr != NULL)
    {
        fwMgr->Release();
    }

    return hr;
}

HRESULT icf_CheckAndAddPorts(INetFwProfile * fwProfile, global_afs_port_t * ports, int nPorts) {
	INetFwOpenPorts * fwPorts = NULL;
	INetFwOpenPort * fwPort = NULL;
	HRESULT hr;
	HRESULT rhr = S_OK; /* return value */

	hr = fwProfile->get_GloballyOpenPorts(&fwPorts);
	if (FAILED(hr)) {
		// Abort!
		DEBUGOUT(("Can't get globallyOpenPorts\n"));
		rhr = hr;
		goto cleanup;
	}

	// go through the supplied ports
	for (int i=0; i<nPorts; i++) {
		VARIANT_BOOL vbEnabled;
		BSTR bstName = NULL;
		BOOL bCreate = FALSE;
		fwPort = NULL;

		hr = fwPorts->Item(ports[i].port, ports[i].protocol, &fwPort);
		if (SUCCEEDED(hr)) {
			DEBUGOUT(("Found port for %S\n",ports[i].name));
            hr = fwPort->get_Enabled(&vbEnabled);
			if (SUCCEEDED(hr)) {
				if ( vbEnabled == VARIANT_FALSE ) {
					hr = fwPort->put_Enabled(VARIANT_TRUE);
					if (FAILED(hr)) {
						// failed. Mark as failure. Don't try to create the port either.
						rhr = hr;
					}
				} // else we are fine
			} else {
                // Something is wrong with the port.
				// We try to create a new one thus overriding this faulty one.
				bCreate = TRUE;
			}
			fwPort->Release();
			fwPort = NULL;
		} else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
			DEBUGOUT(("Port not found for %S\n", ports[i].name));
			bCreate = TRUE;
		}

		if (bCreate) {
			DEBUGOUT(("Trying to create port %S\n",ports[i].name));
			hr = CoCreateInstance(
				__uuidof(NetFwOpenPort),
				NULL,
				CLSCTX_INPROC_SERVER,
				__uuidof(INetFwOpenPort),
				reinterpret_cast<void**>
					(static_cast<INetFwOpenPort**>(&fwPort))
				);

			if (FAILED(hr)) {
				DEBUGOUT(("Can't create port\n"));
                rhr = hr;
			} else {
				DEBUGOUT(("Created port\n"));
				hr = fwPort->put_IpVersion( NET_FW_IP_VERSION_ANY );
				if (FAILED(hr)) {
					DEBUGOUT(("Can't set IpVersion\n"));
					rhr = hr;
					goto abandon_port;
				}

				hr = fwPort->put_Port( ports[i].port );
				if (FAILED(hr)) {
					DEBUGOUT(("Can't set Port\n"));
					rhr = hr;
					goto abandon_port;
				}

				hr = fwPort->put_Protocol( ports[i].protocol );
				if (FAILED(hr)) {
					DEBUGOUT(("Can't set Protocol\n"));
					rhr = hr;
					goto abandon_port;
				}

				hr = fwPort->put_Scope( NET_FW_SCOPE_ALL );
				if (FAILED(hr)) {
					DEBUGOUT(("Can't set Scope\n"));
					rhr = hr;
					goto abandon_port;
				}

				bstName = SysAllocString( ports[i].name );

				if (SysStringLen(bstName) == 0) {
					rhr = E_OUTOFMEMORY;
				} else {
					hr = fwPort->put_Name( bstName );
					if (FAILED(hr)) {
						DEBUGOUT(("Can't set Name\n"));
						rhr = hr;
						SysFreeString( bstName );
						goto abandon_port;
					}
				}

				SysFreeString( bstName );

				hr = fwPorts->Add( fwPort );
				if (FAILED(hr)) {
					DEBUGOUT(("Can't add port\n"));
					rhr = hr;
				} else
					DEBUGOUT(("Added port\n"));

abandon_port:
				fwPort->Release();
			}
		}
	} // loop through ports

	fwPorts->Release();

cleanup:

	if (fwPorts != NULL)
		fwPorts->Release();

	return rhr;
}

long icf_CheckAndAddAFSPorts(int portset) {
	HRESULT hr;
	BOOL coInitialized = FALSE;
	INetFwProfile * fwProfile = NULL;
	global_afs_port_t * ports;
	int nports;
	long code = 0;

	if (portset == AFS_PORTSET_CLIENT) {
		ports = afs_clientPorts;
		nports = sizeof(afs_clientPorts) / sizeof(*afs_clientPorts);
	} else if (portset == AFS_PORTSET_SERVER) {
		ports = afs_serverPorts;
		nports = sizeof(afs_serverPorts) / sizeof(*afs_serverPorts);
	} else
		return 1; /* Invalid port set */

	hr = CoInitializeEx(
        NULL,
        COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE
        );

	if (SUCCEEDED(hr) || RPC_E_CHANGED_MODE == hr)
    {
       coInitialized = TRUE;
    }
	// not necessarily catastrophic if the call failed.  We'll try to
	// continue as if it succeeded.

    hr = icf_OpenFirewallProfile(&fwProfile);
	if (FAILED(hr)) {
		// Ok. That didn't work.  This could be because the machine we
		// are running on doesn't have Windows Firewall.  We'll return
		// a failure to the caller, which shouldn't be taken to mean
		// it's catastrophic.
		DEBUGOUT(("Can't open Firewall profile\n"));
		code = 1;
		goto cleanup;
	}

	// Now that we have a firewall profile, we can start checking
	// and adding the ports that we want.
	hr = icf_CheckAndAddPorts(fwProfile, ports, nports);
	if (FAILED(hr))
		code = 1;

cleanup:
	if (coInitialized) {
		CoUninitialize();
	}

	return code;
}


#ifdef TESTMAIN
int main(int argc, char **argv) {
	printf("Starting...\n");
    if (icf_CheckAndAddAFSPorts(AFS_PORTSET_CLIENT))
		printf("Failed\n");
	else
		printf("Succeeded\n");
	printf("Done\n");
	return 0;
}
#endif