
/*
 * esx_util.c: utility functions for the VMware ESX driver
 *
 * Copyright (C) 2010 Red Hat, Inc.
 * Copyright (C) 2009 Matthias Bolte <matthias.bolte@googlemail.com>
 * Copyright (C) 2009 Maximilian Wilhelm <max@rfc2324.org>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include <config.h>

#include <c-ctype.h>
#include <netdb.h>

#include "internal.h"
#include "datatypes.h"
#include "qparams.h"
#include "util.h"
#include "memory.h"
#include "logging.h"
#include "uuid.h"
#include "esx_private.h"
#include "esx_util.h"

#define VIR_FROM_THIS VIR_FROM_ESX



int
esxUtil_ParseUri(esxUtil_ParsedUri **parsedUri, xmlURIPtr uri)
{
    int result = -1;
    struct qparam_set *queryParamSet = NULL;
    struct qparam *queryParam = NULL;
    int i;
    int noVerify;
    int autoAnswer;
    char *tmp;
    char *saveptr;

    if (parsedUri == NULL || *parsedUri != NULL) {
        ESX_VI_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument"));
        return -1;
    }

    if (VIR_ALLOC(*parsedUri) < 0) {
        virReportOOMError();
        return -1;
    }

#ifdef HAVE_XMLURI_QUERY_RAW
    queryParamSet = qparam_query_parse(uri->query_raw);
#else
    queryParamSet = qparam_query_parse(uri->query);
#endif

    if (queryParamSet == NULL) {
        goto cleanup;
    }

    for (i = 0; i < queryParamSet->n; i++) {
        queryParam = &queryParamSet->p[i];

        if (STRCASEEQ(queryParam->name, "transport")) {
            VIR_FREE((*parsedUri)->transport);

            (*parsedUri)->transport = strdup(queryParam->value);

            if ((*parsedUri)->transport == NULL) {
                virReportOOMError();
                goto cleanup;
            }

            if (STRNEQ((*parsedUri)->transport, "http") &&
                STRNEQ((*parsedUri)->transport, "https")) {
                ESX_ERROR(VIR_ERR_INVALID_ARG,
                          _("Query parameter 'transport' has unexpected value "
                            "'%s' (should be http|https)"),
                          (*parsedUri)->transport);
                goto cleanup;
            }
        } else if (STRCASEEQ(queryParam->name, "vcenter")) {
            VIR_FREE((*parsedUri)->vCenter);

            (*parsedUri)->vCenter = strdup(queryParam->value);

            if ((*parsedUri)->vCenter == NULL) {
                virReportOOMError();
                goto cleanup;
            }
        } else if (STRCASEEQ(queryParam->name, "no_verify")) {
            if (virStrToLong_i(queryParam->value, NULL, 10, &noVerify) < 0 ||
                (noVerify != 0 && noVerify != 1)) {
                ESX_ERROR(VIR_ERR_INVALID_ARG,
                          _("Query parameter 'no_verify' has unexpected value "
                            "'%s' (should be 0 or 1)"), queryParam->value);
                goto cleanup;
            }

            (*parsedUri)->noVerify = noVerify != 0;
        } else if (STRCASEEQ(queryParam->name, "auto_answer")) {
            if (virStrToLong_i(queryParam->value, NULL, 10, &autoAnswer) < 0 ||
                (autoAnswer != 0 && autoAnswer != 1)) {
                ESX_ERROR(VIR_ERR_INVALID_ARG,
                          _("Query parameter 'auto_answer' has unexpected "
                            "value '%s' (should be 0 or 1)"), queryParam->value);
                goto cleanup;
            }

            (*parsedUri)->autoAnswer = autoAnswer != 0;
        } else if (STRCASEEQ(queryParam->name, "proxy")) {
            /* Expected format: [<type>://]<hostname>[:<port>] */
            (*parsedUri)->proxy = true;
            (*parsedUri)->proxy_type = CURLPROXY_HTTP;
            VIR_FREE((*parsedUri)->proxy_hostname);
            (*parsedUri)->proxy_port = 1080;

            if ((tmp = STRSKIP(queryParam->value, "http://")) != NULL) {
                (*parsedUri)->proxy_type = CURLPROXY_HTTP;
            } else if ((tmp = STRSKIP(queryParam->value, "socks://")) != NULL ||
                       (tmp = STRSKIP(queryParam->value, "socks5://")) != NULL) {
                (*parsedUri)->proxy_type = CURLPROXY_SOCKS5;
            } else if ((tmp = STRSKIP(queryParam->value, "socks4://")) != NULL) {
                (*parsedUri)->proxy_type = CURLPROXY_SOCKS4;
            } else if ((tmp = STRSKIP(queryParam->value, "socks4a://")) != NULL) {
                (*parsedUri)->proxy_type = CURLPROXY_SOCKS4A;
            } else if ((tmp = strstr(queryParam->value, "://")) != NULL) {
                *tmp = '\0';

                ESX_ERROR(VIR_ERR_INVALID_ARG,
                          _("Query parameter 'proxy' contains unexpected "
                            "type '%s' (should be (http|socks(|4|4a|5))"),
                          queryParam->value);
                goto cleanup;
            } else {
                tmp = queryParam->value;
            }

            (*parsedUri)->proxy_hostname = strdup(tmp);

            if ((*parsedUri)->proxy_hostname == NULL) {
                virReportOOMError();
                goto cleanup;
            }

            if ((tmp = strchr((*parsedUri)->proxy_hostname, ':')) != NULL) {
                if (tmp == (*parsedUri)->proxy_hostname) {
                    ESX_ERROR(VIR_ERR_INVALID_ARG, "%s",
                              _("Query parameter 'proxy' doesn't contain a "
                                "hostname"));
                    goto cleanup;
                }

                *tmp++ = '\0';

                if (virStrToLong_i(tmp, NULL, 10,
                                   &(*parsedUri)->proxy_port) < 0 ||
                    (*parsedUri)->proxy_port < 1 ||
                    (*parsedUri)->proxy_port > 65535) {
                    ESX_ERROR(VIR_ERR_INVALID_ARG,
                              _("Query parameter 'proxy' has unexpected port"
                                "value '%s' (should be [1..65535])"), tmp);
                    goto cleanup;
                }
            }
        } else {
            VIR_WARN("Ignoring unexpected query parameter '%s'",
                     queryParam->name);
        }
    }

    /* Expected format: [/]<datacenter>/<computeresource>[/<hostsystem>] */
    if (uri->path != NULL) {
        tmp = strdup(uri->path);

        if (tmp == NULL) {
            virReportOOMError();
            goto cleanup;
        }

        if (esxVI_String_DeepCopyValue(&(*parsedUri)->path_datacenter,
                                       strtok_r(tmp, "/", &saveptr)) < 0 ||
            esxVI_String_DeepCopyValue(&(*parsedUri)->path_computeResource,
                                       strtok_r(NULL, "/", &saveptr)) < 0 ||
            esxVI_String_DeepCopyValue(&(*parsedUri)->path_hostSystem,
                                       strtok_r(NULL, "", &saveptr)) < 0) {
            VIR_FREE(tmp);
            goto cleanup;
        }

        VIR_FREE(tmp);
    }

    if ((*parsedUri)->transport == NULL) {
        (*parsedUri)->transport = strdup("https");

        if ((*parsedUri)->transport == NULL) {
            virReportOOMError();
            goto cleanup;
        }
    }

    result = 0;

  cleanup:
    if (result < 0) {
        esxUtil_FreeParsedUri(parsedUri);
    }

    if (queryParamSet != NULL) {
        free_qparam_set(queryParamSet);
    }

    return result;
}




void
esxUtil_FreeParsedUri(esxUtil_ParsedUri **parsedUri)
{
    if (parsedUri == NULL || *parsedUri == NULL) {
        return;
    }

    VIR_FREE((*parsedUri)->transport);
    VIR_FREE((*parsedUri)->vCenter);
    VIR_FREE((*parsedUri)->proxy_hostname);
    VIR_FREE((*parsedUri)->path_datacenter);
    VIR_FREE((*parsedUri)->path_computeResource);
    VIR_FREE((*parsedUri)->path_hostSystem);

    VIR_FREE(*parsedUri);
}



int
esxUtil_ParseVirtualMachineIDString(const char *id_string, int *id)
{
    /* Try to parse an integer from the complete string. */
    if (virStrToLong_i(id_string, NULL, 10, id) == 0) {
        return 0;
    }

    /*
     * If that fails try to parse an integer from the string tail
     * assuming the naming scheme Virtual Center seems to use.
     */
    if (STRPREFIX(id_string, "vm-")) {
        if (virStrToLong_i(id_string + 3, NULL, 10, id) == 0) {
            return 0;
        }
    }

    return -1;
}



int
esxUtil_ParseDatastorePath(const char *datastorePath, char **datastoreName,
                           char **directoryName, char **directoryAndFileName)
{
    int result = -1;
    char *copyOfDatastorePath = NULL;
    char *tmp = NULL;
    char *saveptr = NULL;
    char *preliminaryDatastoreName = NULL;
    char *preliminaryDirectoryAndFileName = NULL;
    char *preliminaryFileName = NULL;

    if ((datastoreName != NULL && *datastoreName != NULL) ||
        (directoryName != NULL && *directoryName != NULL) ||
        (directoryAndFileName != NULL && *directoryAndFileName != NULL)) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument"));
        return -1;
    }

    if (esxVI_String_DeepCopyValue(&copyOfDatastorePath, datastorePath) < 0) {
        goto cleanup;
    }

    /* Expected format: '[<datastore>] <path>' where <path> is optional */
    if ((tmp = STRSKIP(copyOfDatastorePath, "[")) == NULL || *tmp == ']' ||
        (preliminaryDatastoreName = strtok_r(tmp, "]", &saveptr)) == NULL) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Datastore path '%s' doesn't have expected format "
                    "'[<datastore>] <path>'"), datastorePath);
        goto cleanup;
    }

    if (datastoreName != NULL &&
        esxVI_String_DeepCopyValue(datastoreName,
                                   preliminaryDatastoreName) < 0) {
        goto cleanup;
    }

    preliminaryDirectoryAndFileName = strtok_r(NULL, "", &saveptr);

    if (preliminaryDirectoryAndFileName == NULL) {
        preliminaryDirectoryAndFileName = (char *)"";
    } else {
        preliminaryDirectoryAndFileName +=
          strspn(preliminaryDirectoryAndFileName, " ");
    }

    if (directoryAndFileName != NULL &&
        esxVI_String_DeepCopyValue(directoryAndFileName,
                                   preliminaryDirectoryAndFileName) < 0) {
        goto cleanup;
    }

    if (directoryName != NULL) {
        /* Split <path> into <directory>/<file> */
        preliminaryFileName = strrchr(preliminaryDirectoryAndFileName, '/');

        if (preliminaryFileName != NULL) {
            *preliminaryFileName++ = '\0';
        }

        if (esxVI_String_DeepCopyValue(directoryName,
                                       preliminaryDirectoryAndFileName) < 0) {
            goto cleanup;
        }
    }

    result = 0;

  cleanup:
    if (result < 0) {
        if (datastoreName != NULL) {
            VIR_FREE(*datastoreName);
        }

        if (directoryName != NULL) {
            VIR_FREE(*directoryName);
        }

        if (directoryAndFileName != NULL) {
            VIR_FREE(*directoryAndFileName);
        }
    }

    VIR_FREE(copyOfDatastorePath);

    return result;
}



int
esxUtil_ResolveHostname(const char *hostname,
                        char *ipAddress, size_t ipAddress_length)
{
    struct addrinfo hints;
    struct addrinfo *result = NULL;
    int errcode;

    memset(&hints, 0, sizeof (hints));

    hints.ai_flags = AI_ADDRCONFIG;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;

    errcode = getaddrinfo(hostname, NULL, &hints, &result);

    if (errcode != 0) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("IP address lookup for host '%s' failed: %s"), hostname,
                  gai_strerror(errcode));
        return -1;
    }

    if (result == NULL) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("No IP address for host '%s' found: %s"), hostname,
                  gai_strerror(errcode));
        return -1;
    }

    errcode = getnameinfo(result->ai_addr, result->ai_addrlen, ipAddress,
                          ipAddress_length, NULL, 0, NI_NUMERICHOST);

    if (errcode != 0) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Formating IP address for host '%s' failed: %s"), hostname,
                  gai_strerror(errcode));
        freeaddrinfo(result);
        return -1;
    }

    freeaddrinfo(result);

    return 0;
}



int
esxUtil_GetConfigString(virConfPtr conf, const char *name, char **string,
                        bool optional)
{
    virConfValuePtr value;

    *string = NULL;
    value = virConfGetValue(conf, name);

    if (value == NULL) {
        if (optional) {
            return 0;
        }

        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Missing essential config entry '%s'"), name);
        return -1;
    }

    if (value->type != VIR_CONF_STRING) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Config entry '%s' must be a string"), name);
        return -1;
    }

    if (value->str == NULL) {
        if (optional) {
            return 0;
        }

        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Missing essential config entry '%s'"), name);
        return -1;
    }

    *string = strdup(value->str);

    if (*string == NULL) {
        virReportOOMError();
        return -1;
    }

    return 0;
}



int
esxUtil_GetConfigUUID(virConfPtr conf, const char *name, unsigned char *uuid,
                      bool optional)
{
    virConfValuePtr value;

    value = virConfGetValue(conf, name);

    if (value == NULL) {
        if (optional) {
            return 0;
        } else {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Missing essential config entry '%s'"), name);
            return -1;
        }
    }

    if (value->type != VIR_CONF_STRING) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Config entry '%s' must be a string"), name);
        return -1;
    }

    if (value->str == NULL) {
        if (optional) {
            return 0;
        } else {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Missing essential config entry '%s'"), name);
            return -1;
        }
    }

    if (virUUIDParse(value->str, uuid) < 0) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Could not parse UUID from string '%s'"), value->str);
        return -1;
    }

    return 0;
}



int
esxUtil_GetConfigLong(virConfPtr conf, const char *name, long long *number,
                      long long default_, bool optional)
{
    virConfValuePtr value;

    *number = default_;
    value = virConfGetValue(conf, name);

    if (value == NULL) {
        if (optional) {
            return 0;
        } else {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Missing essential config entry '%s'"), name);
            return -1;
        }
    }

    if (value->type == VIR_CONF_STRING) {
        if (value->str == NULL) {
            if (optional) {
                return 0;
            } else {
                ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                          _("Missing essential config entry '%s'"), name);
                return -1;
            }
        }

        if (STREQ(value->str, "unlimited")) {
            *number = -1;
        } else if (virStrToLong_ll(value->str, NULL, 10, number) < 0) {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Config entry '%s' must represent an integer value"),
                      name);
            return -1;
        }
    } else {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Config entry '%s' must be a string"), name);
        return -1;
    }

    return 0;
}



int
esxUtil_GetConfigBoolean(virConfPtr conf, const char *name, bool *boolean_,
                         bool default_, bool optional)
{
    virConfValuePtr value;

    *boolean_ = default_;
    value = virConfGetValue(conf, name);

    if (value == NULL) {
        if (optional) {
            return 0;
        } else {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Missing essential config entry '%s'"), name);
            return -1;
        }
    }

    if (value->type == VIR_CONF_STRING) {
        if (value->str == NULL) {
            if (optional) {
                return 0;
            } else {
                ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                          _("Missing essential config entry '%s'"), name);
                return -1;
            }
        }

        if (STRCASEEQ(value->str, "true")) {
            *boolean_ = 1;
        } else if (STRCASEEQ(value->str, "false")) {
            *boolean_ = 0;
        } else {
            ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                      _("Config entry '%s' must represent a boolean value "
                        "(true|false)"), name);
            return -1;
        }
    } else {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Config entry '%s' must be a string"), name);
        return -1;
    }

    return 0;
}



int
esxUtil_ReformatUuid(const char *input, char *output)
{
    unsigned char uuid[VIR_UUID_BUFLEN];

    if (virUUIDParse(input, uuid) < 0) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Could not parse UUID from string '%s'"),
                  input);
        return -1;
    }

    virUUIDFormat(uuid, output);

    return 0;
}



char *
esxUtil_EscapeHex(const char *string, char escape, const char *special)
{
    char *escaped = NULL;
    size_t length = 1; /* 1 byte for termination */
    const char *tmp1 = string;
    char *tmp2;

    /* Calculate length of escaped string */
    while (*tmp1 != '\0') {
        if (*tmp1 == escape || strspn(tmp1, special) > 0) {
            length += 2;
        }

        ++tmp1;
        ++length;
    }

    if (VIR_ALLOC_N(escaped, length) < 0) {
        virReportOOMError();
        return NULL;
    }

    tmp1 = string; /* reading from this one */
    tmp2 = escaped; /* writing to this one */

    /* Escape to 'cXX' where c is the escape char and X is a hex digit */
    while (*tmp1 != '\0') {
        if (*tmp1 == escape || strspn(tmp1, special) > 0) {
            *tmp2++ = escape;

            snprintf(tmp2, 3, "%02x", (unsigned int)*tmp1);

            tmp2 += 2;
        } else {
            *tmp2++ = *tmp1;
        }

        ++tmp1;
    }

    *tmp2 = '\0';

    return escaped;
}



int
esxUtil_UnescapeHex(char *string, char escape)
{
    char *tmp1 = string; /* reading from this one */
    char *tmp2 = string; /* writing to this one */

    /* Unescape from 'cXX' where c is the escape char and X is a hex digit */
    while (*tmp1 != '\0') {
        if (*tmp1 == escape) {
            if (!c_isxdigit(tmp1[1]) || !c_isxdigit(tmp1[2])) {
                return -1;
            }

            *tmp2++ = virHexToBin(tmp1[1]) * 16 + virHexToBin(tmp1[2]);
            tmp1 += 3;
        } else {
            *tmp2++ = *tmp1++;
        }
    }

    *tmp2 = '\0';

    return 0;
}



char *
esxUtil_EscapeBase64(const char *string)
{
    /* 'normal' characters don't get base64 encoded */
    static const char *normal =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'(),. _-";

    /* VMware uses ',' instead of the path separator '/' in the base64 alphabet */
    static const char *base64 =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";

    virBuffer buffer = VIR_BUFFER_INITIALIZER;
    const char *tmp1 = string;
    size_t length;
    unsigned char c1, c2, c3;

    /* Escape sequences of non-'normal' characters as base64 without padding */
    while (*tmp1 != '\0') {
        length = strspn(tmp1, normal);

        if (length > 0) {
            virBufferAdd(&buffer, tmp1, length);

            tmp1 += length;
        } else {
            length = strcspn(tmp1, normal);

            virBufferAddChar(&buffer, '+');

            while (length > 0) {
                c1 = *tmp1++;
                c2 = length > 1 ? *tmp1++ : 0;
                c3 = length > 2 ? *tmp1++ : 0;

                virBufferAddChar(&buffer, base64[(c1 >> 2) & 0x3f]);
                virBufferAddChar(&buffer, base64[((c1 << 4) + (c2 >> 4)) & 0x3f]);

                if (length > 1) {
                    virBufferAddChar(&buffer, base64[((c2 << 2) + (c3 >> 6)) & 0x3f]);
                }

                if (length > 2) {
                    virBufferAddChar(&buffer, base64[c3 & 0x3f]);
                }

                length -= length > 3 ? 3 : length;
            }

            if (*tmp1 != '\0') {
                virBufferAddChar(&buffer, '-');
            }
        }
    }

    if (virBufferError(&buffer)) {
        virReportOOMError();
        virBufferFreeAndReset(&buffer);

        return NULL;
    }

    return virBufferContentAndReset(&buffer);
}



void
esxUtil_ReplaceSpecialWindowsPathChars(char *string)
{
    /* '/' and '\\' are missing on purpose */
    static const char *specials = "\"*<>:|?";

    char *tmp = string;
    size_t length;

    while (*tmp != '\0') {
        length = strspn(tmp, specials);

        while (length > 0) {
            *tmp++ = '_';
            --length;
        }

        if (*tmp != '\0') {
            ++tmp;
        }
    }
}



char *
esxUtil_EscapeDatastoreItem(const char *string)
{
    char *replaced = strdup(string);
    char *escaped1;
    char *escaped2 = NULL;

    if (replaced == NULL) {
        virReportOOMError();
        return NULL;
    }

    esxUtil_ReplaceSpecialWindowsPathChars(replaced);

    escaped1 = esxUtil_EscapeHexPercent(replaced);

    if (escaped1 == NULL) {
        goto cleanup;
    }

    escaped2 = esxUtil_EscapeBase64(escaped1);

  cleanup:
    VIR_FREE(replaced);
    VIR_FREE(escaped1);

    return escaped2;
}



char *
esxUtil_ConvertToUTF8(const char *encoding, const char *string)
{
    char *result = NULL;
    xmlCharEncodingHandlerPtr handler;
    xmlBufferPtr input;
    xmlBufferPtr utf8;

    handler = xmlFindCharEncodingHandler(encoding);

    if (handler == NULL) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("libxml2 doesn't handle %s encoding"), encoding);
        return NULL;
    }

    input = xmlBufferCreateStatic((char *)string, strlen(string));
    utf8 = xmlBufferCreate();

    if (xmlCharEncInFunc(handler, utf8, input) < 0) {
        ESX_ERROR(VIR_ERR_INTERNAL_ERROR,
                  _("Could not convert from %s to UTF-8 encoding"), encoding);
        goto cleanup;
    }

    result = (char *)utf8->content;
    utf8->content = NULL;

  cleanup:
    xmlCharEncCloseFunc(handler);
    xmlBufferFree(input);
    xmlBufferFree(utf8);

    return result;
}
