text: /*
   * 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
  
  #if defined(WIN32) && !defined(__CYGWIN__)
  #include <windows.h>
  #ifndef INVALID_FILE_ATTRIBUTES
  #define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
  #endif
  #endif
  
  #ifndef HAVE_STAT
  #  ifdef HAVE__STAT
       /* MS C library seems to define stat and _stat. The definition
        *         is identical. Still, mapping them to each other causes a warning. */
  #    ifndef _MSC_VER
  #      define stat(x,y) _stat(x,y)
  #    endif
  #    define HAVE_STAT
  #  endif
  #endif
  
  #include <libxml/xmlmemory.h>
  #include <libxml/tree.h>
  #include <libxml/uri.h>
  #include "xslt.h"
  #include "xsltInternals.h"
  #include "xsltutils.h"
  #include "extensions.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;
  
      xsltInitGlobals();
  
      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) {
      xsltInitGlobals();
      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 dependency 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 defined(WIN32) && !defined(__CYGWIN__)
      DWORD dwAttrs;
  
      dwAttrs = GetFileAttributes(path); 
      if (dwAttrs != INVALID_FILE_ATTRIBUTES) {
          if (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) {
              return 2;
  		}
      }
  #endif
  
      if (stat(path, &stat_buffer) == -1)
          return 0;
  
  #ifdef S_ISDIR
      if (S_ISDIR(stat_buffer.st_mode)) {
          return 2;
      }
  #endif
  #endif
      return 1;
  }
  
  static int
  xsltCheckWritePath(xsltSecurityPrefsPtr sec,
  		   xsltTransformContextPtr ctxt,
  		   const char *path)
  {
      int ret;
      xsltSecurityCheck check;
      char *directory;
  
      check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_WRITE_FILE);
      if (check != NULL) {
  	ret = check(sec, ctxt, path);
  	if (ret == 0) {
  	    xsltTransformError(ctxt, NULL, NULL,
  			       "File write for %s refused\n", path);
  	    return(0);
  	}
      }
  
      directory = xmlParserGetDirectory (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",
  				       path);
  		    xmlFree(directory);
  		    return(0);
  		}
  	    }
  	    ret = xsltCheckWritePath(sec, ctxt, directory);
  	    if (ret == 1)
  		ret = mkdir(directory, 0755);
  	}
  	xmlFree(directory);
  	if (ret < 0)
  	    return(ret);
      }
  
      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) {
          uri = xmlCreateURI();
  	if (uri == NULL) {
  	    xsltTransformError(ctxt, NULL, NULL,
  	     "xsltCheckWrite: out of memory for %s\n", URL);
  	    return(-1);
  	}
  	uri->path = (char *)xmlStrdup(URL);
      }
      if ((uri->scheme == NULL) ||
  	(xmlStrEqual(BAD_CAST uri->scheme, BAD_CAST "file"))) {
  
  #if defined(WIN32) && !defined(__CYGWIN__)
      if ((uri->path)&&(uri->path[0]=='/')&&
          (uri->path[1]!='\0')&&(uri->path[2]==':'))
      ret = xsltCheckWritePath(sec, ctxt, uri->path+1);
      else
  #endif
  
  	/*
  	 * Check if we are allowed to write this file
  	 */
  	ret = xsltCheckWritePath(sec, ctxt, uri->path);
  	if (ret <= 0) {
  	    xmlFreeURI(uri);
  	    return(ret);
  	}
      } 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, (const char *)URL);
  	    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, (const char *)URL);
  	    if (ret == 0) {
  		xsltTransformError(ctxt, NULL, NULL,
  			     "Network file read for %s refused\n", URL);
  		xmlFreeURI(uri);
  		return(0);
  	    }
  	}
      }
      xmlFreeURI(uri);
      return(1);
  }
  
  