1/*
2 * security.c: Implementation of the XSLT security framework
3 *
4 * See Copyright for the status of this software.
5 *
6 * daniel@veillard.com
7 */
8
9#define IN_LIBXSLT
10#include "libxslt.h"
11
12#include <string.h>
13
14#ifdef HAVE_SYS_TYPES_H
15#include <sys/types.h>
16#endif
17#ifdef HAVE_SYS_STAT_H
18#include <sys/stat.h>
19#endif
20
21#ifdef HAVE_MATH_H
22#include <math.h>
23#endif
24#ifdef HAVE_FLOAT_H
25#include <float.h>
26#endif
27#ifdef HAVE_IEEEFP_H
28#include <ieeefp.h>
29#endif
30#ifdef HAVE_NAN_H
31#include <nan.h>
32#endif
33#ifdef HAVE_CTYPE_H
34#include <ctype.h>
35#endif
36
37#if defined(WIN32) && !defined(__CYGWIN__)
38#include <windows.h>
39#ifndef INVALID_FILE_ATTRIBUTES
40#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
41#endif
42#endif
43
44#ifndef HAVE_STAT
45#  ifdef HAVE__STAT
46     /* MS C library seems to define stat and _stat. The definition
47      *         is identical. Still, mapping them to each other causes a warning. */
48#    ifndef _MSC_VER
49#      define stat(x,y) _stat(x,y)
50#    endif
51#    define HAVE_STAT
52#  endif
53#endif
54
55#include <libxml/xmlmemory.h>
56#include <libxml/tree.h>
57#include <libxml/uri.h>
58#include "xslt.h"
59#include "xsltInternals.h"
60#include "xsltutils.h"
61#include "extensions.h"
62#include "security.h"
63
64
65struct _xsltSecurityPrefs {
66    xsltSecurityCheck readFile;
67    xsltSecurityCheck createFile;
68    xsltSecurityCheck createDir;
69    xsltSecurityCheck readNet;
70    xsltSecurityCheck writeNet;
71};
72
73static xsltSecurityPrefsPtr xsltDefaultSecurityPrefs = NULL;
74
75/************************************************************************
76 *									*
77 *			Module interfaces				*
78 *									*
79 ************************************************************************/
80
81/**
82 * xsltNewSecurityPrefs:
83 *
84 * Create a new security preference block
85 *
86 * Returns a pointer to the new block or NULL in case of error
87 */
88xsltSecurityPrefsPtr
89xsltNewSecurityPrefs(void) {
90    xsltSecurityPrefsPtr ret;
91
92    xsltInitGlobals();
93
94    ret = (xsltSecurityPrefsPtr) xmlMalloc(sizeof(xsltSecurityPrefs));
95    if (ret == NULL) {
96	xsltTransformError(NULL, NULL, NULL,
97		"xsltNewSecurityPrefs : malloc failed\n");
98	return(NULL);
99    }
100    memset(ret, 0, sizeof(xsltSecurityPrefs));
101    return(ret);
102}
103
104/**
105 * xsltFreeSecurityPrefs:
106 * @sec:  the security block to free
107 *
108 * Free up a security preference block
109 */
110void
111xsltFreeSecurityPrefs(xsltSecurityPrefsPtr sec) {
112    if (sec == NULL)
113	return;
114    xmlFree(sec);
115}
116
117/**
118 * xsltSetSecurityPrefs:
119 * @sec:  the security block to update
120 * @option:  the option to update
121 * @func:  the user callback to use for this option
122 *
123 * Update the security option to use the new callback checking function
124 *
125 * Returns -1 in case of error, 0 otherwise
126 */
127int
128xsltSetSecurityPrefs(xsltSecurityPrefsPtr sec, xsltSecurityOption option,
129                     xsltSecurityCheck func) {
130    xsltInitGlobals();
131    if (sec == NULL)
132	return(-1);
133    switch (option) {
134        case XSLT_SECPREF_READ_FILE:
135            sec->readFile = func; return(0);
136        case XSLT_SECPREF_WRITE_FILE:
137            sec->createFile = func; return(0);
138        case XSLT_SECPREF_CREATE_DIRECTORY:
139            sec->createDir = func; return(0);
140        case XSLT_SECPREF_READ_NETWORK:
141            sec->readNet = func; return(0);
142        case XSLT_SECPREF_WRITE_NETWORK:
143            sec->writeNet = func; return(0);
144    }
145    return(-1);
146}
147
148/**
149 * xsltGetSecurityPrefs:
150 * @sec:  the security block to update
151 * @option:  the option to lookup
152 *
153 * Lookup the security option to get the callback checking function
154 *
155 * Returns NULL if not found, the function otherwise
156 */
157xsltSecurityCheck
158xsltGetSecurityPrefs(xsltSecurityPrefsPtr sec, xsltSecurityOption option) {
159    if (sec == NULL)
160	return(NULL);
161    switch (option) {
162        case XSLT_SECPREF_READ_FILE:
163            return(sec->readFile);
164        case XSLT_SECPREF_WRITE_FILE:
165            return(sec->createFile);
166        case XSLT_SECPREF_CREATE_DIRECTORY:
167            return(sec->createDir);
168        case XSLT_SECPREF_READ_NETWORK:
169            return(sec->readNet);
170        case XSLT_SECPREF_WRITE_NETWORK:
171            return(sec->writeNet);
172    }
173    return(NULL);
174}
175
176/**
177 * xsltSetDefaultSecurityPrefs:
178 * @sec:  the security block to use
179 *
180 * Set the default security preference application-wide
181 */
182void
183xsltSetDefaultSecurityPrefs(xsltSecurityPrefsPtr sec) {
184
185    xsltDefaultSecurityPrefs = sec;
186}
187
188/**
189 * xsltGetDefaultSecurityPrefs:
190 *
191 * Get the default security preference application-wide
192 *
193 * Returns the current xsltSecurityPrefsPtr in use or NULL if none
194 */
195xsltSecurityPrefsPtr
196xsltGetDefaultSecurityPrefs(void) {
197    return(xsltDefaultSecurityPrefs);
198}
199
200/**
201 * xsltSetCtxtSecurityPrefs:
202 * @sec:  the security block to use
203 * @ctxt:  an XSLT transformation context
204 *
205 * Set the security preference for a specific transformation
206 *
207 * Returns -1 in case of error, 0 otherwise
208 */
209int
210xsltSetCtxtSecurityPrefs(xsltSecurityPrefsPtr sec,
211	                 xsltTransformContextPtr ctxt) {
212    if (ctxt == NULL)
213	return(-1);
214    ctxt->sec = (void *) sec;
215    return(0);
216}
217
218
219/**
220 * xsltSecurityAllow:
221 * @sec:  the security block to use
222 * @ctxt:  an XSLT transformation context
223 * @value:  unused
224 *
225 * Function used to always allow an operation
226 *
227 * Returns 1 always
228 */
229int
230xsltSecurityAllow(xsltSecurityPrefsPtr sec ATTRIBUTE_UNUSED,
231	          xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED,
232		  const char *value ATTRIBUTE_UNUSED) {
233    return(1);
234}
235
236/**
237 * xsltSecurityForbid:
238 * @sec:  the security block to use
239 * @ctxt:  an XSLT transformation context
240 * @value:  unused
241 *
242 * Function used to always forbid an operation
243 *
244 * Returns 0 always
245 */
246int
247xsltSecurityForbid(xsltSecurityPrefsPtr sec ATTRIBUTE_UNUSED,
248	          xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED,
249		  const char *value ATTRIBUTE_UNUSED) {
250    return(0);
251}
252
253/************************************************************************
254 *									*
255 *			Internal interfaces				*
256 *									*
257 ************************************************************************/
258
259/**
260 * xsltCheckFilename
261 * @path:  the path to check
262 *
263 * function checks to see if @path is a valid source
264 * (file, socket...) for XML.
265 *
266 * TODO: remove at some point !!!
267 * Local copy of xmlCheckFilename to avoid a hard dependency on
268 * a new version of libxml2
269 *
270 * if stat is not available on the target machine,
271 * returns 1.  if stat fails, returns 0 (if calling
272 * stat on the filename fails, it can't be right).
273 * if stat succeeds and the file is a directory,
274 * returns 2.  otherwise returns 1.
275 */
276
277static int
278xsltCheckFilename (const char *path)
279{
280#ifdef HAVE_STAT
281    struct stat stat_buffer;
282#if defined(WIN32) && !defined(__CYGWIN__)
283    DWORD dwAttrs;
284
285    dwAttrs = GetFileAttributesA(path);
286    if (dwAttrs != INVALID_FILE_ATTRIBUTES) {
287        if (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) {
288            return 2;
289		}
290    }
291#endif
292
293    if (stat(path, &stat_buffer) == -1)
294        return 0;
295
296#ifdef S_ISDIR
297    if (S_ISDIR(stat_buffer.st_mode)) {
298        return 2;
299    }
300#endif
301#endif
302    return 1;
303}
304
305static int
306xsltCheckWritePath(xsltSecurityPrefsPtr sec,
307		   xsltTransformContextPtr ctxt,
308		   const char *path)
309{
310    int ret;
311    xsltSecurityCheck check;
312    char *directory;
313
314    check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_WRITE_FILE);
315    if (check != NULL) {
316	ret = check(sec, ctxt, path);
317	if (ret == 0) {
318	    xsltTransformError(ctxt, NULL, NULL,
319			       "File write for %s refused\n", path);
320	    return(0);
321	}
322    }
323
324    directory = xmlParserGetDirectory (path);
325
326    if (directory != NULL) {
327	ret = xsltCheckFilename(directory);
328	if (ret == 0) {
329	    /*
330	     * The directory doesn't exist check for creation
331	     */
332	    check = xsltGetSecurityPrefs(sec,
333					 XSLT_SECPREF_CREATE_DIRECTORY);
334	    if (check != NULL) {
335		ret = check(sec, ctxt, directory);
336		if (ret == 0) {
337		    xsltTransformError(ctxt, NULL, NULL,
338				       "Directory creation for %s refused\n",
339				       path);
340		    xmlFree(directory);
341		    return(0);
342		}
343	    }
344	    ret = xsltCheckWritePath(sec, ctxt, directory);
345	    if (ret == 1)
346		ret = mkdir(directory, 0755);
347	}
348	xmlFree(directory);
349	if (ret < 0)
350	    return(ret);
351    }
352
353    return(1);
354}
355
356/**
357 * xsltCheckWrite:
358 * @sec:  the security options
359 * @ctxt:  an XSLT transformation context
360 * @URL:  the resource to be written
361 *
362 * Check if the resource is allowed to be written, if necessary makes
363 * some preliminary work like creating directories
364 *
365 * Return 1 if write is allowed, 0 if not and -1 in case or error.
366 */
367int
368xsltCheckWrite(xsltSecurityPrefsPtr sec,
369	       xsltTransformContextPtr ctxt, const xmlChar *URL) {
370    int ret;
371    xmlURIPtr uri;
372    xsltSecurityCheck check;
373
374    uri = xmlParseURI((const char *)URL);
375    if (uri == NULL) {
376        uri = xmlCreateURI();
377	if (uri == NULL) {
378	    xsltTransformError(ctxt, NULL, NULL,
379	     "xsltCheckWrite: out of memory for %s\n", URL);
380	    return(-1);
381	}
382	uri->path = (char *)xmlStrdup(URL);
383    }
384    if ((uri->scheme == NULL) ||
385	(xmlStrEqual(BAD_CAST uri->scheme, BAD_CAST "file"))) {
386
387#if defined(WIN32) && !defined(__CYGWIN__)
388    if ((uri->path)&&(uri->path[0]=='/')&&
389        (uri->path[1]!='\0')&&(uri->path[2]==':'))
390    ret = xsltCheckWritePath(sec, ctxt, uri->path+1);
391    else
392#endif
393
394	/*
395	 * Check if we are allowed to write this file
396	 */
397	ret = xsltCheckWritePath(sec, ctxt, uri->path);
398	if (ret <= 0) {
399	    xmlFreeURI(uri);
400	    return(ret);
401	}
402    } else {
403	/*
404	 * Check if we are allowed to write this network resource
405	 */
406	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_WRITE_NETWORK);
407	if (check != NULL) {
408	    ret = check(sec, ctxt, (const char *)URL);
409	    if (ret == 0) {
410		xsltTransformError(ctxt, NULL, NULL,
411			     "File write for %s refused\n", URL);
412		xmlFreeURI(uri);
413		return(0);
414	    }
415	}
416    }
417    xmlFreeURI(uri);
418    return(1);
419}
420
421
422/**
423 * xsltCheckRead:
424 * @sec:  the security options
425 * @ctxt: an XSLT transformation context
426 * @URL:  the resource to be read
427 *
428 * Check if the resource is allowed to be read
429 *
430 * Return 1 if read is allowed, 0 if not and -1 in case or error.
431 */
432int
433xsltCheckRead(xsltSecurityPrefsPtr sec,
434	      xsltTransformContextPtr ctxt, const xmlChar *URL) {
435    int ret;
436    xmlURIPtr uri;
437    xsltSecurityCheck check;
438
439    uri = xmlParseURI((const char *)URL);
440    if (uri == NULL) {
441	xsltTransformError(ctxt, NULL, NULL,
442	 "xsltCheckRead: URL parsing failed for %s\n",
443			 URL);
444	return(-1);
445    }
446    if ((uri->scheme == NULL) ||
447	(xmlStrEqual(BAD_CAST uri->scheme, BAD_CAST "file"))) {
448
449	/*
450	 * Check if we are allowed to read this file
451	 */
452	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_READ_FILE);
453	if (check != NULL) {
454	    ret = check(sec, ctxt, uri->path);
455	    if (ret == 0) {
456		xsltTransformError(ctxt, NULL, NULL,
457			     "Local file read for %s refused\n", URL);
458		xmlFreeURI(uri);
459		return(0);
460	    }
461	}
462    } else {
463	/*
464	 * Check if we are allowed to write this network resource
465	 */
466	check = xsltGetSecurityPrefs(sec, XSLT_SECPREF_READ_NETWORK);
467	if (check != NULL) {
468	    ret = check(sec, ctxt, (const char *)URL);
469	    if (ret == 0) {
470		xsltTransformError(ctxt, NULL, NULL,
471			     "Network file read for %s refused\n", URL);
472		xmlFreeURI(uri);
473		return(0);
474	    }
475	}
476    }
477    xmlFreeURI(uri);
478    return(1);
479}
480
481