]> git.mxchange.org Git - friendica.git/blob - library/Smarty/libs/sysplugins/smarty_security.php
Merge remote branch 'upstream/master'
[friendica.git] / library / Smarty / libs / sysplugins / smarty_security.php
1 <?php
2 /**
3  * Smarty plugin
4  *
5  * @package Smarty
6  * @subpackage Security
7  * @author Uwe Tews
8  */
9  
10 /*
11  * FIXME: Smarty_Security API
12  *      - getter and setter instead of public properties would allow cultivating an internal cache properly
13  *      - current implementation of isTrustedResourceDir() assumes that Smarty::$template_dir and Smarty::$config_dir are immutable
14  *        the cache is killed every time either of the variables change. That means that two distinct Smarty objects with differing
15  *        $template_dir or $config_dir should NOT share the same Smarty_Security instance, 
16  *        as this would lead to (severe) performance penalty! how should this be handled? 
17  */
18
19 /**
20  * This class does contain the security settings
21  */
22 class Smarty_Security {
23
24     /**
25      * This determines how Smarty handles "<?php ... ?>" tags in templates.
26      * possible values:
27      * <ul>
28      *   <li>Smarty::PHP_PASSTHRU -> echo PHP tags as they are</li>
29      *   <li>Smarty::PHP_QUOTE    -> escape tags as entities</li>
30      *   <li>Smarty::PHP_REMOVE   -> remove php tags</li>
31      *   <li>Smarty::PHP_ALLOW    -> execute php tags</li>
32      * </ul>
33      *
34      * @var integer
35      */
36     public $php_handling = Smarty::PHP_PASSTHRU;
37     /**
38      * This is the list of template directories that are considered secure.
39      * $template_dir is in this list implicitly.
40      *
41      * @var array
42      */
43     public $secure_dir = array();
44     /**
45      * This is an array of directories where trusted php scripts reside.
46      * {@link $security} is disabled during their inclusion/execution.
47      *
48      * @var array
49      */
50     public $trusted_dir = array();
51     /**
52      * List of regular expressions (PCRE) that include trusted URIs
53      *
54      * @var array
55      */
56     public $trusted_uri = array();
57     /**
58      * This is an array of trusted static classes.
59      *
60      * If empty access to all static classes is allowed.
61      * If set to 'none' none is allowed.
62      * @var array
63      */
64     public $static_classes = array();
65     /**
66      * This is an array of trusted PHP functions.
67      *
68      * If empty all functions are allowed.
69      * To disable all PHP functions set $php_functions = null.
70      * @var array
71      */
72     public $php_functions = array(
73         'isset', 'empty',
74         'count', 'sizeof',
75         'in_array', 'is_array',
76         'time',
77         'nl2br',
78     );
79     /**
80      * This is an array of trusted PHP modifers.
81      *
82      * If empty all modifiers are allowed.
83      * To disable all modifier set $modifiers = null.
84      * @var array
85      */
86     public $php_modifiers = array(
87         'escape',
88         'count'
89     );
90     /**
91      * This is an array of allowed tags.
92      *
93      * If empty no restriction by allowed_tags.
94      * @var array
95      */
96     public $allowed_tags = array();
97     /**
98      * This is an array of disabled tags.
99      *
100      * If empty no restriction by disabled_tags.
101      * @var array
102      */
103     public $disabled_tags = array();
104     /**
105      * This is an array of allowed modifier plugins.
106      *
107      * If empty no restriction by allowed_modifiers.
108      * @var array
109      */
110     public $allowed_modifiers = array();
111     /**
112      * This is an array of disabled modifier plugins.
113      *
114      * If empty no restriction by disabled_modifiers.
115      * @var array
116      */
117     public $disabled_modifiers = array();
118     /**
119      * This is an array of trusted streams.
120      *
121      * If empty all streams are allowed.
122      * To disable all streams set $streams = null.
123      * @var array
124      */
125     public $streams = array('file');
126     /**
127      * + flag if constants can be accessed from template
128      * @var boolean
129      */
130     public $allow_constants = true;
131     /**
132      * + flag if super globals can be accessed from template
133      * @var boolean
134      */
135     public $allow_super_globals = true;
136
137     /**
138      * Cache for $resource_dir lookups
139      * @var array
140      */
141     protected $_resource_dir = null;
142     /**
143      * Cache for $template_dir lookups
144      * @var array
145      */
146     protected $_template_dir = null;
147     /**
148      * Cache for $config_dir lookups
149      * @var array
150      */
151     protected $_config_dir = null;
152     /**
153      * Cache for $secure_dir lookups
154      * @var array
155      */
156     protected $_secure_dir = null;
157     /**
158      * Cache for $php_resource_dir lookups
159      * @var array
160      */
161     protected $_php_resource_dir = null;
162     /**
163      * Cache for $trusted_dir lookups
164      * @var array
165      */
166     protected $_trusted_dir = null;
167     
168     
169     /**
170      * @param Smarty $smarty
171      */
172     public function __construct($smarty)
173     {
174         $this->smarty = $smarty;
175     }
176     
177     /**
178      * Check if PHP function is trusted.
179      *
180      * @param string $function_name
181      * @param object $compiler compiler object
182      * @return boolean true if function is trusted
183      * @throws SmartyCompilerException if php function is not trusted
184      */
185     public function isTrustedPhpFunction($function_name, $compiler)
186     {
187         if (isset($this->php_functions) && (empty($this->php_functions) || in_array($function_name, $this->php_functions))) {
188             return true;
189         }
190
191         $compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting");
192         return false; // should not, but who knows what happens to the compiler in the future?
193     }
194
195     /**
196      * Check if static class is trusted.
197      *
198      * @param string $class_name
199      * @param object $compiler compiler object
200      * @return boolean true if class is trusted
201      * @throws SmartyCompilerException if static class is not trusted
202      */
203     public function isTrustedStaticClass($class_name, $compiler)
204     {
205         if (isset($this->static_classes) && (empty($this->static_classes) || in_array($class_name, $this->static_classes))) {
206             return true;
207         }
208
209         $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting");
210         return false; // should not, but who knows what happens to the compiler in the future?
211     }
212
213     /**
214      * Check if PHP modifier is trusted.
215      *
216      * @param string $modifier_name
217      * @param object $compiler compiler object
218      * @return boolean true if modifier is trusted
219      * @throws SmartyCompilerException if modifier is not trusted
220      */
221     public function isTrustedPhpModifier($modifier_name, $compiler)
222     {
223         if (isset($this->php_modifiers) && (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))) {
224             return true;
225         }
226
227         $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting");
228         return false; // should not, but who knows what happens to the compiler in the future?
229     }
230
231     /**
232      * Check if tag is trusted.
233      *
234      * @param string $tag_name
235      * @param object $compiler compiler object
236      * @return boolean true if tag is trusted
237      * @throws SmartyCompilerException if modifier is not trusted
238      */
239     public function isTrustedTag($tag_name, $compiler)
240     {
241         // check for internal always required tags
242         if (in_array($tag_name, array('assign', 'call', 'private_filter', 'private_block_plugin', 'private_function_plugin', 'private_object_block_function',
243                     'private_object_function', 'private_registered_function', 'private_registered_block', 'private_special_variable', 'private_print_expression', 'private_modifier'))) {
244             return true;
245         }
246         // check security settings
247         if (empty($this->allowed_tags)) {
248             if (empty($this->disabled_tags) || !in_array($tag_name, $this->disabled_tags)) {
249                 return true;
250             } else {
251                 $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", $compiler->lex->taglineno);
252             }
253         } else if (in_array($tag_name, $this->allowed_tags) && !in_array($tag_name, $this->disabled_tags)) {
254             return true;
255         } else {
256             $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", $compiler->lex->taglineno);
257         }
258         return false; // should not, but who knows what happens to the compiler in the future?
259     }
260
261     /**
262      * Check if modifier plugin is trusted.
263      *
264      * @param string $modifier_name
265      * @param object $compiler compiler object
266      * @return boolean true if tag is trusted
267      * @throws SmartyCompilerException if modifier is not trusted
268      */
269     public function isTrustedModifier($modifier_name, $compiler)
270     {
271         // check for internal always allowed modifier
272         if (in_array($modifier_name, array('default'))) {
273             return true;
274         }
275         // check security settings
276         if (empty($this->allowed_modifiers)) {
277             if (empty($this->disabled_modifiers) || !in_array($modifier_name, $this->disabled_modifiers)) {
278                 return true;
279             } else {
280                 $compiler->trigger_template_error("modifier '{$modifier_name}' disabled by security setting", $compiler->lex->taglineno);
281             }
282         } else if (in_array($modifier_name, $this->allowed_modifiers) && !in_array($modifier_name, $this->disabled_modifiers)) {
283             return true;
284         } else {
285             $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting", $compiler->lex->taglineno);
286         }
287         return false; // should not, but who knows what happens to the compiler in the future?
288     }
289
290     /**
291      * Check if stream is trusted.
292      *
293      * @param string $stream_name
294      * @return boolean true if stream is trusted
295      * @throws SmartyException if stream is not trusted
296      */
297     public function isTrustedStream($stream_name)
298     {
299         if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) {
300             return true;
301         }
302
303         throw new SmartyException("stream '{$stream_name}' not allowed by security setting");
304     }
305
306     /**
307      * Check if directory of file resource is trusted.
308      *
309      * @param string $filepath
310      * @return boolean true if directory is trusted
311      * @throws SmartyException if directory is not trusted
312      */
313     public function isTrustedResourceDir($filepath)
314     {
315         $_template = false;
316         $_config = false;
317         $_secure = false;
318
319         $_template_dir = $this->smarty->getTemplateDir();
320         $_config_dir = $this->smarty->getConfigDir();
321
322         // check if index is outdated
323         if ((!$this->_template_dir || $this->_template_dir !== $_template_dir)
324                 || (!$this->_config_dir || $this->_config_dir !== $_config_dir)
325                 || (!empty($this->secure_dir) && (!$this->_secure_dir || $this->_secure_dir !== $this->secure_dir))
326         ) {
327             $this->_resource_dir = array();
328             $_template = true;
329             $_config = true;
330             $_secure = !empty($this->secure_dir);
331         }
332
333         // rebuild template dir index
334         if ($_template) {
335             $this->_template_dir = $_template_dir;
336             foreach ($_template_dir as $directory) {
337                 $directory = realpath($directory);
338                 $this->_resource_dir[$directory] = true;
339             }
340         }
341
342         // rebuild config dir index
343         if ($_config) {
344             $this->_config_dir = $_config_dir;
345             foreach ($_config_dir as $directory) {
346                 $directory = realpath($directory);
347                 $this->_resource_dir[$directory] = true;
348             }
349         }
350
351         // rebuild secure dir index
352         if ($_secure) {
353             $this->_secure_dir = $this->secure_dir;
354             foreach ((array) $this->secure_dir as $directory) {
355                 $directory = realpath($directory);
356                 $this->_resource_dir[$directory] = true;
357             }
358         }
359
360         $_filepath = realpath($filepath);
361         $directory = dirname($_filepath);
362         $_directory = array();
363         while (true) {
364             // remember the directory to add it to _resource_dir in case we're successful
365             $_directory[$directory] = true;
366             // test if the directory is trusted
367             if (isset($this->_resource_dir[$directory])) {
368                 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookups
369                 $this->_resource_dir = array_merge($this->_resource_dir, $_directory);
370                 return true;
371             }
372             // abort if we've reached root
373             if (($pos = strrpos($directory, DS)) === false || !isset($directory[1])) {
374                 break;
375             }
376             // bubble up one level
377             $directory = substr($directory, 0, $pos);
378         }
379
380         // give up
381         throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
382     }
383     
384     /**
385      * Check if URI (e.g. {fetch} or {html_image}) is trusted
386      *
387      * To simplify things, isTrustedUri() resolves all input to "{$PROTOCOL}://{$HOSTNAME}".
388      * So "http://username:password@hello.world.example.org:8080/some-path?some=query-string"
389      * is reduced to "http://hello.world.example.org" prior to applying the patters from {@link $trusted_uri}.
390      * @param string $uri 
391      * @return boolean true if URI is trusted
392      * @throws SmartyException if URI is not trusted
393      * @uses $trusted_uri for list of patterns to match against $uri
394      */
395     public function isTrustedUri($uri)
396     {
397         $_uri = parse_url($uri);
398         if (!empty($_uri['scheme']) && !empty($_uri['host'])) {
399             $_uri = $_uri['scheme'] . '://' . $_uri['host'];
400             foreach ($this->trusted_uri as $pattern) {
401                 if (preg_match($pattern, $_uri)) {
402                     return true;
403                 }
404             }
405         }
406         
407         throw new SmartyException("URI '{$uri}' not allowed by security setting");
408     }
409     
410     /**
411      * Check if directory of file resource is trusted.
412      *
413      * @param string $filepath
414      * @return boolean true if directory is trusted
415      * @throws SmartyException if PHP directory is not trusted
416      */
417     public function isTrustedPHPDir($filepath)
418     {
419         if (empty($this->trusted_dir)) {
420             throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)");
421         }
422
423         // check if index is outdated
424         if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) {
425             $this->_php_resource_dir = array();
426
427             $this->_trusted_dir = $this->trusted_dir;
428             foreach ((array) $this->trusted_dir as $directory) {
429                 $directory = realpath($directory);
430                 $this->_php_resource_dir[$directory] = true;
431             }
432         }
433
434         $_filepath = realpath($filepath);
435         $directory = dirname($_filepath);
436         $_directory = array();
437         while (true) {
438             // remember the directory to add it to _resource_dir in case we're successful
439             $_directory[] = $directory;
440             // test if the directory is trusted
441             if (isset($this->_php_resource_dir[$directory])) {
442                 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookups
443                 $this->_php_resource_dir = array_merge($this->_php_resource_dir, $_directory);
444                 return true;
445             }
446             // abort if we've reached root
447             if (($pos = strrpos($directory, DS)) === false || !isset($directory[2])) {
448                 break;
449             }
450             // bubble up one level
451             $directory = substr($directory, 0, $pos);
452         }
453
454         throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
455     }
456
457 }
458
459 ?>