/* $Id: VBoxManageDHCPServer.cpp 33489 2010-10-27 10:31:41Z vboxsync $ */
/** @file
 * VBoxManage - Implementation of dhcpserver command.
 */

/*
 * Copyright (C) 2006-2010 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#ifndef VBOX_ONLY_DOCS
#include <VBox/com/com.h>
#include <VBox/com/array.h>
#include <VBox/com/ErrorInfo.h>
#include <VBox/com/errorprint.h>
#include <VBox/com/EventQueue.h>

#include <VBox/com/VirtualBox.h>
#endif /* !VBOX_ONLY_DOCS */

#include <iprt/cidr.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/net.h>
#include <iprt/getopt.h>
#include <iprt/ctype.h>

#include <VBox/log.h>

#include "VBoxManage.h"

#ifndef VBOX_ONLY_DOCS
using namespace com;

typedef enum enMainOpCodes
{
    OP_ADD = 1000,
    OP_REMOVE,
    OP_MODIFY
} OPCODE;

static const RTGETOPTDEF g_aDHCPIPOptions[]
    = {
        { "--netname",          't', RTGETOPT_REQ_STRING },  /* we use 't' instead of 'n' to avoid
                                                              * 1. the misspelled "-enable" long option to be treated as 'e' (for -enable) + 'n' (for -netname) + "<the_rest_opt>" (for net name)
                                                              * 2. the misspelled "-netmask" to be treated as 'n' (for -netname) + "<the_rest_opt>" (for net name)
                                                              */
        { "-netname",           't', RTGETOPT_REQ_STRING },     // deprecated (if removed check below)
        { "--ifname",           'f', RTGETOPT_REQ_STRING },  /* we use 'f' instead of 'i' to avoid
                                                              * 1. the misspelled "-disable" long option to be treated as 'd' (for -disable) + 'i' (for -ifname) + "<the_rest_opt>" (for if name)
                                                              */
        { "-ifname",            'f', RTGETOPT_REQ_STRING },     // deprecated
        { "--ip",               'a', RTGETOPT_REQ_STRING },
        { "-ip",                'a', RTGETOPT_REQ_STRING },     // deprecated
        { "--netmask",          'm', RTGETOPT_REQ_STRING },
        { "-netmask",           'm', RTGETOPT_REQ_STRING },     // deprecated
        { "--lowerip",          'l', RTGETOPT_REQ_STRING },
        { "-lowerip",           'l', RTGETOPT_REQ_STRING },     // deprecated
        { "--upperip",          'u', RTGETOPT_REQ_STRING },
        { "-upperip",           'u', RTGETOPT_REQ_STRING },     // deprecated
        { "--enable",           'e', RTGETOPT_REQ_NOTHING },
        { "-enable",            'e', RTGETOPT_REQ_NOTHING },    // deprecated
        { "--disable",          'd', RTGETOPT_REQ_NOTHING },
        { "-disable",           'd', RTGETOPT_REQ_NOTHING }     // deprecated
      };

static int handleOp(HandlerArg *a, OPCODE enmCode, int iStart, int *pcProcessed)
{
    if (a->argc - iStart < 2)
        return errorSyntax(USAGE_DHCPSERVER, "Not enough parameters");

    int index = iStart;
    HRESULT rc;

    const char *pNetName = NULL;
    const char *pIfName = NULL;
    const char * pIp = NULL;
    const char * pNetmask = NULL;
    const char * pLowerIp = NULL;
    const char * pUpperIp = NULL;
    int enable = -1;

    int c;
    RTGETOPTUNION ValueUnion;
    RTGETOPTSTATE GetState;
    RTGetOptInit(&GetState,
                 a->argc,
                 a->argv,
                 g_aDHCPIPOptions,
                 enmCode != OP_REMOVE ? RT_ELEMENTS(g_aDHCPIPOptions) : 4, /* we use only --netname and --ifname for remove*/
                 index,
                 RTGETOPTINIT_FLAGS_NO_STD_OPTS);
    while ((c = RTGetOpt(&GetState, &ValueUnion)))
    {
        switch (c)
        {
            case 't':   // --netname
                if(pNetName)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --netname once.");
                else if (pIfName)
                    return errorSyntax(USAGE_DHCPSERVER, "You can either use a --netname or --ifname for identifying the DHCP server.");
                else
                {
                    pNetName = ValueUnion.psz;
                }
            break;
            case 'f':   // --ifname
                if(pIfName)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --ifname once.");
                else if (pNetName)
                    return errorSyntax(USAGE_DHCPSERVER, "You can either use a --netname or --ipname for identifying the DHCP server.");
                else
                {
                    pIfName = ValueUnion.psz;
                }
            break;
            case 'a':   // -ip
                if(pIp)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --ip once.");
                else
                {
                    pIp = ValueUnion.psz;
                }
            break;
            case 'm':   // --netmask
                if(pNetmask)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --netmask once.");
                else
                {
                    pNetmask = ValueUnion.psz;
                }
            break;
            case 'l':   // --lowerip
                if(pLowerIp)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --lowerip once.");
                else
                {
                    pLowerIp = ValueUnion.psz;
                }
            break;
            case 'u':   // --upperip
                if(pUpperIp)
                    return errorSyntax(USAGE_DHCPSERVER, "You can only specify --upperip once.");
                else
                {
                    pUpperIp = ValueUnion.psz;
                }
            break;
            case 'e':   // --enable
                if(enable >= 0)
                    return errorSyntax(USAGE_DHCPSERVER, "You can specify either --enable or --disable once.");
                else
                {
                    enable = 1;
                }
            break;
            case 'd':   // --disable
                if(enable >= 0)
                    return errorSyntax(USAGE_DHCPSERVER, "You can specify either --enable or --disable once.");
                else
                {
                    enable = 0;
                }
            break;
            case VINF_GETOPT_NOT_OPTION:
                return errorSyntax(USAGE_DHCPSERVER, "unhandled parameter: %s", ValueUnion.psz);
            break;
            default:
                if (c > 0)
                {
                    if (RT_C_IS_GRAPH(c))
                        return errorSyntax(USAGE_DHCPSERVER, "unhandled option: -%c", c);
                    else
                        return errorSyntax(USAGE_DHCPSERVER, "unhandled option: %i", c);
                }
                else if (c == VERR_GETOPT_UNKNOWN_OPTION)
                    return errorSyntax(USAGE_DHCPSERVER, "unknown option: %s", ValueUnion.psz);
                else if (ValueUnion.pDef)
                    return errorSyntax(USAGE_DHCPSERVER, "%s: %Rrs", ValueUnion.pDef->pszLong, c);
                else
                    return errorSyntax(USAGE_DHCPSERVER, "%Rrs", c);
        }
    }

    if(! pNetName && !pIfName)
        return errorSyntax(USAGE_DHCPSERVER, "You need to specify either --netname or --ifname to identify the DHCP server");

    if(enmCode != OP_REMOVE)
    {
        if(enable < 0 || pIp || pNetmask || pLowerIp || pUpperIp)
        {
            if(!pIp)
                return errorSyntax(USAGE_DHCPSERVER, "You need to specify --ip option");

            if(!pNetmask)
                return errorSyntax(USAGE_DHCPSERVER, "You need to specify --netmask option");

            if(!pLowerIp)
                return errorSyntax(USAGE_DHCPSERVER, "You need to specify --lowerip option");

            if(!pUpperIp)
                return errorSyntax(USAGE_DHCPSERVER, "You need to specify --upperip option");
        }
    }

    Bstr NetName;
    if(!pNetName)
    {
        ComPtr<IHost> host;
        CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam()));

        ComPtr<IHostNetworkInterface> hif;
        CHECK_ERROR(host, FindHostNetworkInterfaceByName(Bstr(pIfName).mutableRaw(), hif.asOutParam()));
        if (FAILED(rc))
            return errorArgument("Could not find interface '%s'", pIfName);

        CHECK_ERROR(hif, COMGETTER(NetworkName) (NetName.asOutParam()));
        if (FAILED(rc))
            return errorArgument("Could not get network name for the interface '%s'", pIfName);
    }
    else
    {
        NetName = Bstr(pNetName);
    }

    ComPtr<IDHCPServer> svr;
    rc = a->virtualBox->FindDHCPServerByNetworkName(NetName.mutableRaw(), svr.asOutParam());
    if(enmCode == OP_ADD)
    {
        if (SUCCEEDED(rc))
            return errorArgument("DHCP server already exists");

        CHECK_ERROR(a->virtualBox, CreateDHCPServer(NetName.mutableRaw(), svr.asOutParam()));
        if (FAILED(rc))
            return errorArgument("Failed to create the DHCP server");
    }
    else if (FAILED(rc))
    {
        return errorArgument("DHCP server does not exist");
    }

    if(enmCode != OP_REMOVE)
    {
        if (pIp || pNetmask || pLowerIp || pUpperIp)
        {
            CHECK_ERROR(svr, SetConfiguration (Bstr(pIp).mutableRaw(), Bstr(pNetmask).mutableRaw(), Bstr(pLowerIp).mutableRaw(), Bstr(pUpperIp).mutableRaw()));
            if(FAILED(rc))
                return errorArgument("Failed to set configuration");
        }

        if(enable >= 0)
        {
            CHECK_ERROR(svr, COMSETTER(Enabled) ((BOOL)enable));
        }
    }
    else
    {
        CHECK_ERROR(a->virtualBox, RemoveDHCPServer(svr));
        if(FAILED(rc))
            return errorArgument("Failed to remove server");
    }

    return 0;
}


int handleDHCPServer(HandlerArg *a)
{
    if (a->argc < 1)
        return errorSyntax(USAGE_DHCPSERVER, "Not enough parameters");

    int result;
    int cProcessed;
    if (strcmp(a->argv[0], "modify") == 0)
        result = handleOp(a, OP_MODIFY, 1, &cProcessed);
    else if (strcmp(a->argv[0], "add") == 0)
        result = handleOp(a, OP_ADD, 1, &cProcessed);
    else if (strcmp(a->argv[0], "remove") == 0)
        result = handleOp(a, OP_REMOVE, 1, &cProcessed);
    else
        result = errorSyntax(USAGE_DHCPSERVER, "Invalid parameter '%s'", Utf8Str(a->argv[0]).c_str());

    return result;
}

#endif /* !VBOX_ONLY_DOCS */
