/* Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifdef WIN32
/*
 * Functions to handle interacting with the Win32 registry
 */

/*
 * Apache registry key structure
 *
 * Apache's registry information is stored in the HKEY_LOCAL_MACHINE
 * key, under
 *
 *  HKLM\SOFTWARE\Apache Group\Apache\version
 *
 * These keys are defined in this file. The definition of the "version" part
 * will need updating each time Apache moves from beta to non-beta or from a
 * release to a development or beta version.
 */

/* To allow for multiple services, store the configuration file's full path
 * under each service entry:
 *
 * HKLM\System\CurrentControlSet\Services\[service name]\Parameters\ConfPath
 *
 * The default configuration path (for console apache) is still stored:
 * 
 * HKLM\Software\[Vendor]\[Software]\[Version]\ServerRoot
 */

#include <windows.h>
#include <stdio.h>

#include "httpd.h"
#include "http_log.h"
#include "service.h"

/* Define where the Apache values are stored in the registry. In general
 * VERSION will be the same across all beta releases for a particular
 * major release, but will change when the final release is made.
 */

/* Define where the Apache values are stored in the registry. 
 *
 * If you are looking here to roll the tarball, you didn't need to visit.
 * registry.c now picks up the version from include/httpd.h
 */

#define REGKEY "SOFTWARE\\" SERVER_BASEVENDOR "\\" SERVER_BASEPRODUCT "\\" SERVER_BASEREVISION

#define SERVICEKEYPRE  "System\\CurrentControlSet\\Services\\"
#define SERVICEKEYPOST "\\Parameters"

/*
 * The Windows API registry key functions don't set the last error
 * value (the windows equivalent of errno). So we need to set it
 * with SetLastError() before calling the aplog_error() function.
 * Because this is common, let's have a macro.
 */
#define do_error(rv,fmt,arg) do { \
	SetLastError(rv); \
	ap_log_error(APLOG_MARK, APLOG_WIN32ERROR|APLOG_ERR, NULL, fmt,arg); \
    } while (0);

/*
 * Get the data for registry key value. This is a generic function that
 * can either get a value into a caller-supplied buffer, or it can
 * allocate space for the value from the pass-in pool. It will normally
 * be used by other functions within this file to get specific key values
 * (e.g. registry_get_server_root()). This function returns a number of
 * different error statuses, allowing the caller to differentiate
 * between a key or value not existing and other kinds of errors. Depending
 * on the type of data being obtained the caller can then either ignore
 * the key-not-existing error, or treat it as a real error.
 *
 * If ppValue is NULL, allocate space for the value and return it in
 * *pValue. The return value is the number of bytes in the value.
 * The first argument is the pool to use to allocate space for the value.
 *
 * If pValue is not NULL, assume it is a buffer of nSizeValue bytes,
 * and write the value into the buffer. The return value is the number
 * of bytes in the value (so if the return value is greater than
 * the supplied nSizeValue, the caller knows that *pValue is truncated).
 * The pool argument is ignored.
 *
 * The return value is the number of bytes in the successfully retreived
 * key if everything worked, or:
 *
 *  -1 the key does not exists
 *  -2 if out of memory during the function
 *  -3 if the buffer specified by *pValue/nSizeValue was not large enough 
 *     for the value.
 *  -4 if an error occurred
 *
 * If the return value is negative a message will be logged to the error
 * log (aplog_error) function. If the return value is -2, -3 or -4 the message
 * will be logged at priority "error", while if the return value is -1 the
 * message will be logged at priority "warning".
 */

static int ap_registry_get_key_int(pool *p, char *key, char *name, char *pBuffer, int nSizeBuffer, char **ppValue)
{
    long rv;
    HKEY hKey;
    char *pValue;
    int nSize;
    int retval;

    rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                      key,
		      0,
		      KEY_READ,
		      &hKey);

    if (rv == ERROR_FILE_NOT_FOUND) {
	ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,
        "Registry does not contain key %s",key);
	return -1;
    }
    if (rv != ERROR_SUCCESS) {
        do_error(rv, "RegOpenKeyEx HKLM\\%s",key);
	return -4;
    }

    if (pBuffer == NULL) {
	/* Find the size required for the data by passing NULL as the buffer
	 * pointer. On return nSize will contain the size required for the
	 * buffer if the return value is ERROR_SUCCESS.
	 */
	rv = RegQueryValueEx(hKey, 
			     name,		/* key name */
			     NULL,		/* reserved */
			     NULL,		/* type */
			     NULL,		/* for value */
			     &nSize);		/* for size of "value" */

	if (rv != ERROR_SUCCESS) {
	    do_error(rv, "RegQueryValueEx(key %s)", key);
	    return -1;
	}

	pValue = ap_palloc(p, nSize);
	*ppValue = pValue;
	if (!pValue) {
	    /* Eek, out of memory, probably not worth trying to carry on,
	     * but let's give it a go
	     */
	    ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,NULL,
		"Error getting registry key: out of memory");
	    return -2;
	}
    }
    else {
	/* Get the value into the existing buffer of length nSizeBuffer */
	pValue = pBuffer;
	nSize = nSizeBuffer;
    }

    rv = RegQueryValueEx(hKey, 
			 name,		/* key name */
			 NULL,		/* reserved */
			 NULL,		/* type */
			 pValue,		/* for value */
			 &nSize);		/* for size of "value" */

    retval = 0;	    /* Return value */

    if (rv == ERROR_FILE_NOT_FOUND) {
	ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,
        "Registry does not contain value %s\\%s", key, name);
	retval = -1;
    }
    else if (rv == ERROR_MORE_DATA) {
	/* This should only happen if we got passed a pre-existing buffer
	 * (pBuffer, nSizeBuffer). But I suppose it could also happen if we
	 * allocate a buffer if another process changed the length of the
	 * value since we found out its length above. Umm.
	 */
	ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,NULL,
	    "Error getting registry value %s: buffer not big enough", key);
	retval = -3;
    }
    else if (rv != ERROR_SUCCESS) {
	do_error(rv, "RegQueryValueEx(key %s)", key);
	retval = -4;
    }

    rv = RegCloseKey(hKey);
    if (rv != ERROR_SUCCESS) {
    do_error(rv, "RegCloseKey HKLM\\%s", key);
	if (retval == 0) {
	    /* Keep error status from RegQueryValueEx, if any */
	    retval = -4;  
	}
    }

    return retval < 0 ? retval : nSize;
}

/*
 * Get the server root from the registry into 'dir' which is
 * size bytes long. Returns 0 if the server root was found
 * or if the serverroot key does not exist (in which case
 * dir will contain an empty string), or -1 if there was
 * an error getting the key.
 */

API_EXPORT(int) ap_registry_get_server_root(pool *p, char *dir, int size)
{
    int rv;

    rv = ap_registry_get_key_int(p, REGKEY, "ServerRoot", dir, size, NULL);
    if (rv < 0) {
	dir[0] = '\0';
    }

    return (rv < 0) ? -1 : 0;
}

API_EXPORT(char *) ap_get_service_key(char *display_name)
{
    char *key, *service_name;
    
    if (display_name == NULL)
        return strdup("");

    service_name = get_service_name(display_name);
    
    key = malloc(strlen(SERVICEKEYPRE) +
                 strlen(service_name) +
                 strlen(SERVICEKEYPOST) + 1);

    sprintf(key,"%s%s%s", SERVICEKEYPRE, service_name, SERVICEKEYPOST);

    return(key);
}

/**********************************************************************
 * The rest of this file deals with storing keys or values in the registry
 */

char *ap_registry_parse_key(int index, char *key)
{
    char *head = key, *skey;
    int i;
    
    if(!key)
        return(NULL);

    for(i = 0; i <= index; i++)
    {
        if(key && key[0] == '\\')
            key++;
        if (!key)
            return(NULL);
        head = key;
        key = strchr(head, '\\');
    }

    if(!key)
        return(strdup(head));
    *key = '\0';
    skey = strdup(head);
    *key = '\\';
    return(skey);
}

/*
 * ap_registry_create_apache_key() creates the Apache registry key
 * (HLKM\SOFTWARE\Apache Group\Apache\version, as defined at the start
 * of this file), if it does not already exist. It will be called by
 * ap_registry_store_key_int() if it cannot open this key. This 
 * function is intended to be called by ap_registry_store_key_int() if
 * the Apache key does not exist when it comes to store a data item.
 *
 * Returns 0 on success or -1 on error. If -1 is returned, the error will
 * already have been logged.
 */

static int ap_registry_create_key(char *longkey)
{
    int index;
    HKEY hKey;
    HKEY hKeyNext;
    int retval;
    int rv;
    char *key;

    hKey = HKEY_LOCAL_MACHINE;
    index = 0;
    retval = 0;

    /* Walk the tree, creating at each stage if necessary */
    while (key=ap_registry_parse_key(index,longkey)) {
	int result;

	rv = RegCreateKeyEx(hKey,
			    key,         /* subkey */
			    0,	         /* reserved */
			    NULL,        /* class */
			    REG_OPTION_NON_VOLATILE,
			    KEY_WRITE,
			    NULL,
			    &hKeyNext,
			    &result);
	if (rv != ERROR_SUCCESS) {
	    do_error(rv, "RegCreateKeyEx(%s)", longkey);
	    retval = -4;
	}

	/* Close the old key */
	rv = RegCloseKey(hKey);
	if (rv != ERROR_SUCCESS) {
	    do_error(rv, "RegCloseKey", NULL);
	    if (retval == 0) {
		/* Keep error status from RegCreateKeyEx, if any */
		retval = -4;  
	    }
	}

	if (retval) {
	    break;
	}

    free(key);
	hKey = hKeyNext;
	index++;
    }

    if (!key) {
	/* Close the final key we opened, if we walked the entire
	 * tree
	 */
	rv = RegCloseKey(hKey);
	if (rv != ERROR_SUCCESS) {
	    do_error(rv, "RegCloseKey", NULL);
	    if (retval == 0) {
		/* Keep error status from RegCreateKeyEx, if any */
		retval = -4;  
	    }
	}
    }
    else
        free(key);

    return retval;
}

/*
 * ap_registry_store_key_int() stores a value name and value under the
 * Apache registry key. If the Apache key does not exist it is created
 * first. This function is intended to be called from a wrapper function
 * in this file to set particular data values, such as 
 * ap_registry_set_server_root() below.
 *
 * Returns 0 if the value name and data was stored successfully, or
 * returns -1 if the Apache key does not exist (since we try to create 
 * this key, this should never happen), or -4 if any other error occurred
 * (these values are consistent with ap_registry_get_key_int()).
 * If the return value is negative then the error will already have been
 * logged via aplog_error().
 */

static int ap_registry_store_key_int(char *key, char *name, DWORD type, void *value, int value_size)
{
    long rv;
    HKEY hKey;
    int retval;

    rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		      key,
		      0,
	 	      KEY_WRITE,
		      &hKey);

    if (rv == ERROR_FILE_NOT_FOUND) {
	/* Key could not be opened -- try to create it 
	 */
        if (ap_registry_create_key(key) < 0) {
	    /* Creation failed (error already reported) */
	    return -4;
	}
	
	/* Now it has been created we should be able to open it
	 */
	rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		  key,
		  0,
	 	  KEY_WRITE,
		  &hKey);

	if (rv == ERROR_FILE_NOT_FOUND) {
            ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,
                         "Registry does not contain key %s after creation",key);
	    return -1;
	}
    }

    if (rv != ERROR_SUCCESS) {
        do_error(rv, "RegOpenKeyEx HKLM\\%s", key);
        return -4;
    }

    /* Now set the value and data */
    rv = RegSetValueEx(hKey, 
                       name,	/* value key name */
		       0,	/* reserved */
		       type,	/* type */
		       value,	/* value data */
		       (DWORD)value_size); /* for size of "value" */

    retval = 0;	    /* Return value */

    if (rv != ERROR_SUCCESS) {
	do_error(rv, "RegQueryValueEx(key %s)", key);
	retval = -4;
    }
    else {
	ap_log_error(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,NULL,
	    "Registry stored HKLM\\" REGKEY "\\%s value %s", key, 
	    type == REG_SZ ? value : "(not displayable)");
    }

    /* Make sure we close the key even if there was an error storing
     * the data
     */
    rv = RegCloseKey(hKey);
    if (rv != ERROR_SUCCESS) {
        do_error(rv, "RegCloseKey HKLM\\%s", key);
        if (retval == 0) {
            /* Keep error status from RegQueryValueEx, if any */
            retval = -4;  
        }
    }

    return retval;
}

/*
 * Sets the serverroot value within the registry. Returns 0 on success
 * or -1 on error. If -1 is return the error will already have been
 * logged via aplog_error().
 */

int ap_registry_set_server_root(char *dir)
{
    int rv;

    rv = ap_registry_store_key_int(REGKEY, "ServerRoot", REG_SZ, dir, strlen(dir)+1);

    return rv < 0 ? -1 : 0;
}

/* Creates and fills array pointed to by parray with the requested registry string
 *
 * Returns 0 on success, machine specific error code on error 
 */
int ap_registry_get_array(pool *p, char *key, char *name, 
                          array_header **pparray)
{
    char *pValue;
    char *tmp;
    char **newelem;
    int ret;
    int nSize = 0;

    ret = ap_registry_get_key_int(p, key, name, NULL, 0, &pValue);
    if (ret < 0)
        return ret;

    tmp = pValue;
    if ((ret > 2) && (tmp[0] || tmp[1]))
        nSize = 1;    /* Element Count */
    while ((tmp < pValue + ret) && (tmp[0] || tmp[1]))
    {
        if (!tmp[0])
            ++nSize;
        ++tmp;
    }

    *pparray = ap_make_array(p, nSize, sizeof(char *));
    tmp = pValue;
    if (tmp[0] || tmp[1]) {
        newelem = (char **) ap_push_array(*pparray);
        *newelem = tmp;
    }
    while ((tmp < pValue + ret) && (tmp[0] || tmp[1]))
    {
        if (!tmp[0]) {
            newelem = (char **) ap_push_array(*pparray);
            *newelem = tmp + 1;
        }
        ++tmp;
    }
    
    return nSize;
}

int ap_registry_get_service_args(pool *p, int *argc, char ***argv, char *display_name)
{
    int ret;
    array_header *parray;
    char *key = ap_get_service_key(display_name);
    ret = ap_registry_get_array(p, key, "ConfigArgs", &parray);
    if (ret > 0) {
        *argc = parray->nelts;
        *argv = (char**) parray->elts;
    }
    else {
        *argc = 0;
        *argv = NULL;
    }
    free(key);
    return ret;
}

int ap_registry_store_array(pool *p, char *key, char *name,
                            int nelts, char **elts)
{
    int  bufsize, i;
    char *buf, *tmp;

    bufsize = 1; /* For trailing second null */
    for (i = 0; i < nelts; ++i)
    {
        bufsize += strlen(elts[i]) + 1;
    }
    if (!nelts) 
        ++bufsize;

    buf = ap_palloc(p, bufsize);
    tmp = buf;
    for (i = 0; i < nelts; ++i)
    {
        strcpy(tmp, elts[i]);
        tmp += strlen(elts[i]) + 1;
    }
    if (!nelts) 
        *(tmp++) = '\0';
    *(tmp++) = '\0'; /* Trailing second null */

    return ap_registry_store_key_int(key, name, REG_MULTI_SZ, buf, tmp - buf);
}

int ap_registry_set_service_args(pool *p, int argc, char **argv, char *display_name)
{
    int ret;
    char *key = ap_get_service_key(display_name);
    ret = ap_registry_store_array(p, key, "ConfigArgs", argc, argv);
    free(key);
    return ret;
}

#endif /* WIN32 */
