blob: b766cf7f979fdaeacd72e35261857863f7b9b937 [file] [log] [blame]
/*
* 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);
}