/*
 * security.c: Implementation of the XSLT security framework
 *
 * See Copyright for the status of this software.
 *
 * daniel@veillard.com
 */

#define IN_LIBXSLT
#include "libxslt.h"

#include <string.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif
#ifdef HAVE_NAN_H
#include <nan.h>
#endif
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif

#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "security.h"


struct _xsltSecurityPrefs {
    xsltSecurityCheck readFile;
    xsltSecurityCheck createFile;
    xsltSecurityCheck createDir;
    xsltSecurityCheck readNet;
    xsltSecurityCheck writeNet;
};

static xsltSecurityPrefsPtr xsltDefaultSecurityPrefs = NULL;

/************************************************************************
 *									*
 *			Module interfaces				*
 *									*
 ************************************************************************/

/**
 * xsltNewSecurityPrefs:
 *
 * Create a new security preference block
 *
 * Returns a pointer to the new block or NULL in case of error
 */
xsltSecurityPrefsPtr
xsltNewSecurityPrefs(void) {
    xsltSecurityPrefsPtr ret;

    ret = (xsltSecurityPrefsPtr) xmlMalloc(sizeof(xsltSecurityPrefs));
    if (ret == NULL) {
	xsltTransformError(NULL, NULL, NULL,
		"xsltNewSecurityPrefs : malloc failed\n");
	return(NULL);
    }
    memset(ret, 0, sizeof(xsltSecurityPrefs));
    return(ret);
}

/**
 * xsltFreeSecurityPrefs:
 * @sec:  the security block to free
 *
 * Free up a security preference block
 */
void
xsltFreeSecurityPrefs(xsltSecurityPrefsPtr sec) {
    if (sec == NULL)
	return;
    xmlFree(sec);
}

/**
 * xsltSetSecurityPrefs:
 * @sec:  the security block to update
 * @option:  the option to update
 * @func:  the user callback to use for this option
 *
 * Update the security option to use the new callback checking function
 *
 * Returns -1 in case of error, 0 otherwise
 */
int
xsltSetSecurityPrefs(xsltSecurityPrefsPtr sec, xsltSecurityOption option,
                     xsltSecurityCheck func) {
    if (sec == NULL)
	return(-1);
    switch (option) {
        case XSLT_SECPREF_READ_FILE:
            sec->readFile = func; return(0);
        case XSLT_SECPREF_WRITE_FILE:
            sec->createFile = func; return(0);
        case XSLT_SECPREF_CREATE_DIRECTORY:
            sec->createDir = func; return(0);
        case XSLT_SECPREF_READ_NETWORK:
            sec->readNet = func; return(0);
        case XSLT_SECPREF_WRITE_NETWORK:
            sec->writeNet = func; return(0);
    }
    return(-1);
}

/**
 * xsltGetSecurityPrefs:
 * @sec:  the security block to update
 * @option:  the option to lookup
 *
 * Lookup the security option to get the callback checking function
 *
 * Returns NULL if not found, the function otherwise
 */
xsltSecurityCheck
xsltGetSecurityPrefs(xsltSecurityPrefsPtr sec, xsltSecurityOption option) {
    if (sec == NULL)
	return(NULL);
    switch (option) {
        case XSLT_SECPREF_READ_FILE:
            return(sec->readFile);
        case XSLT_SECPREF_WRITE_FILE:
            return(sec->createFile);
        case XSLT_SECPREF_CREATE_DIRECTORY:
            return(sec->createDir);
        case XSLT_SECPREF_READ_NETWORK:
            return(sec->readNet);
        case XSLT_SECPREF_WRITE_NETWORK:
            return(sec->writeNet);
    }
    return(NULL);
}

/**
 * xsltSetDefaultSecurityPrefs:
 * @sec:  the security block to use
 *
 * Set the default security preference application-wide
 */
void
xsltSetDefaultSecurityPrefs(xsltSecurityPrefsPtr sec) {
    xsltDefaultSecurityPrefs = sec;
}

/**
 * xsltGetDefaultSecurityPrefs:
 *
 * Get the default security preference application-wide
 *
 * Returns the current xsltSecurityPrefsPtr in use or NULL if none
 */
xsltSecurityPrefsPtr
xsltGetDefaultSecurityPrefs(void) {
    return(xsltDefaultSecurityPrefs);
}

/**
 * xsltSetCtxtSecurityPrefs:
 * @sec:  the security block to use
 * @ctxt:  an XSLT transformation context
 *
 * Set the security preference for a specific transformation
 *
 * Returns -1 in case of error, 0 otherwise
 */
int                    
xsltSetCtxtSecurityPrefs(xsltSecurityPrefsPtr sec,
	                 xsltTransformContextPtr ctxt) {
    if (ctxt == NULL)
	return(-1);
    ctxt->sec = (void *) sec;
    return(0);
}


/**
 * xsltSecurityAllow:
 * @sec:  the security block to use
 * @ctxt:  an XSLT transformation context
 * @value:  unused
 *
 * Function used to always allow an operation
 *
 * Returns 1 always
 */
int
xsltSecurityAllow(xsltSecurityPrefsPtr sec ATTRIBUTE_UNUSED,
	          xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED,
		  const char *value ATTRIBUTE_UNUSED) {
    return(1);
}

/**
 * xsltSecurityForbid:
 * @sec:  the security block to use
 * @ctxt:  an XSLT transformation context
 * @value:  unused
 *
 * Function used to always forbid an operation
 *
 * Returns 0 always
 */
int
xsltSecurityForbid(xsltSecurityPrefsPtr sec ATTRIBUTE_UNUSED,
	          xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED,
		  const char *value ATTRIBUTE_UNUSED) {
    return(0);
}

/************************************************************************
 *									*
 *			Internal interfaces				*
 *									*
 ************************************************************************/

/**
 * xsltCheckFilename
 * @path:  the path to check
 *
 * function checks to see if @path is a valid source
 * (file, socket...) for XML.
 *
 * TODO: remove at some point !!!
 * Local copy of xmlCheckFilename to avoid a hard dependancy on
 * a new version of libxml2 
 *
 * if stat is not available on the target machine,
 * returns 1.  if stat fails, returns 0 (if calling
 * stat on the filename fails, it can't be right).
 * if stat succeeds and the file is a directory,
 * returns 2.  otherwise returns 1.
 */

static int
xsltCheckFilename (const char *path)
{
#ifdef HAVE_STAT
    struct stat stat_buffer;

    if (stat(path, &stat_buffer) == -1)
        return 0;

#ifdef S_ISDIR
    if (S_ISDIR(stat_buffer.st_mode)) {
        return 2;
    }
#endif
#endif
    return 1;
}

/**
 * xsltCheckWrite:
 * @sec:  the security options
 * @ctxt:  an XSLT transformation context
 * @URL:  the resource to be written
 *
 * Check if the resource is allowed to be written, if necessary makes
 * some preliminary work like creating directories
 *
 * Return 1 if write is allowed, 0 if not and -1 in case or error.
 */
int
xsltCheckWrite(xsltSecurityPrefsPtr sec,
	       xsltTransformContextPtr ctxt, const xmlChar *URL) {
    int ret;
    xmlURIPtr uri;
    xsltSecurityCheck check;

    uri = xmlParseURI((const char *)URL);
    if (uri == NULL) {
	xsltTransformError(ctxt, NULL, NULL,
	 "xsltDocumentElem: URL parsing failed for %s\n",
			 URL);
	return(-1);
    }
    if ((uri->scheme == NULL) ||
	(xmlStrEqual(BAD_CAST uri->scheme, BAD_CAST "file"))) {
	char *directory;

	/*
	 * Check if we are allowed to write this file
	 */
	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_WRITE_FILE);
	if (check != NULL) {
	    ret = check(sec, ctxt, uri->path);
	    if (ret == 0) {
		xsltTransformError(ctxt, NULL, NULL,
			     "File write for %s refused\n", URL);
		xmlFreeURI(uri);
		return(0);
	    }
	}

	directory = xmlParserGetDirectory (uri->path);
	if (directory != NULL) {
	    ret = xsltCheckFilename(directory);
	    if (ret == 0) {
		/*
		 * The directory doesn't exist check for creation
		 */
		check = xsltGetSecurityPrefs(sec,
				 XSLT_SECPREF_CREATE_DIRECTORY);
		if (check != NULL) {
		    ret = check(sec, ctxt, directory);
		    if (ret == 0) {
			xsltTransformError(ctxt, NULL, NULL,
					 "Directory creation for %s refused\n",
					 URL);
			xmlFree(directory);
			xmlFreeURI(uri);
			return(0);
		    }
		}
		ret = xsltCheckWrite(sec, ctxt, (const xmlChar *)directory);
		if (ret == 1)
		    ret = mkdir(directory, 0755);
		if (ret < 0)
		    return(ret);
	    }
	    xmlFree(directory);
	}
    } else {
	/*
	 * Check if we are allowed to write this network resource
	 */
	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_WRITE_NETWORK);
	if (check != NULL) {
	    ret = check(sec, ctxt, uri->path);
	    if (ret == 0) {
		xsltTransformError(ctxt, NULL, NULL,
			     "File write for %s refused\n", URL);
		xmlFreeURI(uri);
		return(0);
	    }
	}
    }
    xmlFreeURI(uri);
    return(1);
}


/**
 * xsltCheckRead:
 * @sec:  the security options
 * @ctxt: an XSLT transformation context
 * @URL:  the resource to be read
 *
 * Check if the resource is allowed to be read
 *
 * Return 1 if read is allowed, 0 if not and -1 in case or error.
 */
int
xsltCheckRead(xsltSecurityPrefsPtr sec,
	      xsltTransformContextPtr ctxt, const xmlChar *URL) {
    int ret;
    xmlURIPtr uri;
    xsltSecurityCheck check;

    uri = xmlParseURI((const char *)URL);
    if (uri == NULL) {
	xsltTransformError(ctxt, NULL, NULL,
	 "xsltCheckRead: URL parsing failed for %s\n",
			 URL);
	return(-1);
    }
    if ((uri->scheme == NULL) ||
	(xmlStrEqual(BAD_CAST uri->scheme, BAD_CAST "file"))) {

	/*
	 * Check if we are allowed to read this file
	 */
	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_READ_FILE);
	if (check != NULL) {
	    ret = check(sec, ctxt, uri->path);
	    if (ret == 0) {
		xsltTransformError(ctxt, NULL, NULL,
			     "Local file read for %s refused\n", URL);
		xmlFreeURI(uri);
		return(0);
	    }
	}
    } else {
	/*
	 * Check if we are allowed to write this network resource
	 */
	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_READ_NETWORK);
	if (check != NULL) {
	    ret = check(sec, ctxt, uri->path);
	    if (ret == 0) {
		xsltTransformError(ctxt, NULL, NULL,
			     "Network file read for %s refused\n", URL);
		xmlFreeURI(uri);
		return(0);
	    }
	}
    }
    xmlFreeURI(uri);
    return(1);
}

