]> git.mxchange.org Git - friendica.git/blob - library/Smarty/libs/sysplugins/smarty_security.php
Add Smarty to Composer
[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      * If empty access to all static classes is allowed.
60      * If set to 'none' none is allowed.
61      *
62      * @var array
63      */
64     public $static_classes = array();
65     /**
66      * This is an array of trusted PHP functions.
67      * If empty all functions are allowed.
68      * To disable all PHP functions set $php_functions = null.
69      *
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 modifiers.
81      * If empty all modifiers are allowed.
82      * To disable all modifier set $modifiers = null.
83      *
84      * @var array
85      */
86     public $php_modifiers = array(
87         'escape',
88         'count'
89     );
90     /**
91      * This is an array of allowed tags.
92      * If empty no restriction by allowed_tags.
93      *
94      * @var array
95      */
96     public $allowed_tags = array();
97     /**
98      * This is an array of disabled tags.
99      * If empty no restriction by disabled_tags.
100      *
101      * @var array
102      */
103     public $disabled_tags = array();
104     /**
105      * This is an array of allowed modifier plugins.
106      * If empty no restriction by allowed_modifiers.
107      *
108      * @var array
109      */
110     public $allowed_modifiers = array();
111     /**
112      * This is an array of disabled modifier plugins.
113      * If empty no restriction by disabled_modifiers.
114      *
115      * @var array
116      */
117     public $disabled_modifiers = array();
118     /**
119      * This is an array of trusted streams.
120      * If empty all streams are allowed.
121      * To disable all streams set $streams = null.
122      *
123      * @var array
124      */
125     public $streams = array('file');
126     /**
127      * + flag if constants can be accessed from template
128      *
129      * @var boolean
130      */
131     public $allow_constants = true;
132     /**
133      * + flag if super globals can be accessed from template
134      *
135      * @var boolean
136      */
137     public $allow_super_globals = true;
138
139     /**
140      * Cache for $resource_dir lookup
141      *
142      * @var array
143      */
144     protected $_resource_dir = null;
145     /**
146      * Cache for $template_dir lookup
147      *
148      * @var array
149      */
150     protected $_template_dir = null;
151     /**
152      * Cache for $config_dir lookup
153      *
154      * @var array
155      */
156     protected $_config_dir = null;
157     /**
158      * Cache for $secure_dir lookup
159      *
160      * @var array
161      */
162     protected $_secure_dir = null;
163     /**
164      * Cache for $php_resource_dir lookup
165      *
166      * @var array
167      */
168     protected $_php_resource_dir = null;
169     /**
170      * Cache for $trusted_dir lookup
171      *
172      * @var array
173      */
174     protected $_trusted_dir = null;
175
176     /**
177      * @param Smarty $smarty
178      */
179     public function __construct($smarty)
180     {
181         $this->smarty = $smarty;
182     }
183
184     /**
185      * Check if PHP function is trusted.
186      *
187      * @param  string $function_name
188      * @param  object $compiler compiler object
189      *
190      * @return boolean                 true if function is trusted
191      * @throws SmartyCompilerException if php function is not trusted
192      */
193     public function isTrustedPhpFunction($function_name, $compiler)
194     {
195         if (isset($this->php_functions) && (empty($this->php_functions) || in_array($function_name, $this->php_functions))) {
196             return true;
197         }
198
199         $compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting");
200
201         return false; // should not, but who knows what happens to the compiler in the future?
202     }
203
204     /**
205      * Check if static class is trusted.
206      *
207      * @param  string $class_name
208      * @param  object $compiler compiler object
209      *
210      * @return boolean                 true if class is trusted
211      * @throws SmartyCompilerException if static class is not trusted
212      */
213     public function isTrustedStaticClass($class_name, $compiler)
214     {
215         if (isset($this->static_classes) && (empty($this->static_classes) || in_array($class_name, $this->static_classes))) {
216             return true;
217         }
218
219         $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting");
220
221         return false; // should not, but who knows what happens to the compiler in the future?
222     }
223
224     /**
225      * Check if PHP modifier is trusted.
226      *
227      * @param  string $modifier_name
228      * @param  object $compiler compiler object
229      *
230      * @return boolean                 true if modifier is trusted
231      * @throws SmartyCompilerException if modifier is not trusted
232      */
233     public function isTrustedPhpModifier($modifier_name, $compiler)
234     {
235         if (isset($this->php_modifiers) && (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))) {
236             return true;
237         }
238
239         $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting");
240
241         return false; // should not, but who knows what happens to the compiler in the future?
242     }
243
244     /**
245      * Check if tag is trusted.
246      *
247      * @param  string $tag_name
248      * @param  object $compiler compiler object
249      *
250      * @return boolean                 true if tag is trusted
251      * @throws SmartyCompilerException if modifier is not trusted
252      */
253     public function isTrustedTag($tag_name, $compiler)
254     {
255         // check for internal always required tags
256         if (in_array($tag_name, array('assign', 'call', 'private_filter', 'private_block_plugin', 'private_function_plugin', 'private_object_block_function',
257                                       'private_object_function', 'private_registered_function', 'private_registered_block', 'private_special_variable', 'private_print_expression', 'private_modifier'))
258         ) {
259             return true;
260         }
261         // check security settings
262         if (empty($this->allowed_tags)) {
263             if (empty($this->disabled_tags) || !in_array($tag_name, $this->disabled_tags)) {
264                 return true;
265             } else {
266                 $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", $compiler->lex->taglineno);
267             }
268         } elseif (in_array($tag_name, $this->allowed_tags) && !in_array($tag_name, $this->disabled_tags)) {
269             return true;
270         } else {
271             $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", $compiler->lex->taglineno);
272         }
273
274         return false; // should not, but who knows what happens to the compiler in the future?
275     }
276
277     /**
278      * Check if modifier plugin is trusted.
279      *
280      * @param  string $modifier_name
281      * @param  object $compiler compiler object
282      *
283      * @return boolean                 true if tag is trusted
284      * @throws SmartyCompilerException if modifier is not trusted
285      */
286     public function isTrustedModifier($modifier_name, $compiler)
287     {
288         // check for internal always allowed modifier
289         if (in_array($modifier_name, array('default'))) {
290             return true;
291         }
292         // check security settings
293         if (empty($this->allowed_modifiers)) {
294             if (empty($this->disabled_modifiers) || !in_array($modifier_name, $this->disabled_modifiers)) {
295                 return true;
296             } else {
297                 $compiler->trigger_template_error("modifier '{$modifier_name}' disabled by security setting", $compiler->lex->taglineno);
298             }
299         } elseif (in_array($modifier_name, $this->allowed_modifiers) && !in_array($modifier_name, $this->disabled_modifiers)) {
300             return true;
301         } else {
302             $compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting", $compiler->lex->taglineno);
303         }
304
305         return false; // should not, but who knows what happens to the compiler in the future?
306     }
307
308     /**
309      * Check if stream is trusted.
310      *
311      * @param  string $stream_name
312      *
313      * @return boolean         true if stream is trusted
314      * @throws SmartyException if stream is not trusted
315      */
316     public function isTrustedStream($stream_name)
317     {
318         if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) {
319             return true;
320         }
321
322         throw new SmartyException("stream '{$stream_name}' not allowed by security setting");
323     }
324
325     /**
326      * Check if directory of file resource is trusted.
327      *
328      * @param  string $filepath
329      *
330      * @return boolean         true if directory is trusted
331      * @throws SmartyException if directory is not trusted
332      */
333     public function isTrustedResourceDir($filepath)
334     {
335         $_template = false;
336         $_config = false;
337         $_secure = false;
338
339         $_template_dir = $this->smarty->getTemplateDir();
340         $_config_dir = $this->smarty->getConfigDir();
341
342         // check if index is outdated
343         if ((!$this->_template_dir || $this->_template_dir !== $_template_dir)
344             || (!$this->_config_dir || $this->_config_dir !== $_config_dir)
345             || (!empty($this->secure_dir) && (!$this->_secure_dir || $this->_secure_dir !== $this->secure_dir))
346         ) {
347             $this->_resource_dir = array();
348             $_template = true;
349             $_config = true;
350             $_secure = !empty($this->secure_dir);
351         }
352
353         // rebuild template dir index
354         if ($_template) {
355             $this->_template_dir = $_template_dir;
356             foreach ($_template_dir as $directory) {
357                 $directory = realpath($directory);
358                 $this->_resource_dir[$directory] = true;
359             }
360         }
361
362         // rebuild config dir index
363         if ($_config) {
364             $this->_config_dir = $_config_dir;
365             foreach ($_config_dir as $directory) {
366                 $directory = realpath($directory);
367                 $this->_resource_dir[$directory] = true;
368             }
369         }
370
371         // rebuild secure dir index
372         if ($_secure) {
373             $this->_secure_dir = $this->secure_dir;
374             foreach ((array) $this->secure_dir as $directory) {
375                 $directory = realpath($directory);
376                 $this->_resource_dir[$directory] = true;
377             }
378         }
379
380         $_filepath = realpath($filepath);
381         $directory = dirname($_filepath);
382         $_directory = array();
383         while (true) {
384             // remember the directory to add it to _resource_dir in case we're successful
385             $_directory[$directory] = true;
386             // test if the directory is trusted
387             if (isset($this->_resource_dir[$directory])) {
388                 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup
389                 $this->_resource_dir = array_merge($this->_resource_dir, $_directory);
390
391                 return true;
392             }
393             // abort if we've reached root
394             if (($pos = strrpos($directory, DS)) === false || !isset($directory[1])) {
395                 break;
396             }
397             // bubble up one level
398             $directory = substr($directory, 0, $pos);
399         }
400
401         // give up
402         throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
403     }
404
405     /**
406      * Check if URI (e.g. {fetch} or {html_image}) is trusted
407      * To simplify things, isTrustedUri() resolves all input to "{$PROTOCOL}://{$HOSTNAME}".
408      * So "http://username:password@hello.world.example.org:8080/some-path?some=query-string"
409      * is reduced to "http://hello.world.example.org" prior to applying the patters from {@link $trusted_uri}.
410      *
411      * @param  string $uri
412      *
413      * @return boolean         true if URI is trusted
414      * @throws SmartyException if URI is not trusted
415      * @uses $trusted_uri for list of patterns to match against $uri
416      */
417     public function isTrustedUri($uri)
418     {
419         $_uri = parse_url($uri);
420         if (!empty($_uri['scheme']) && !empty($_uri['host'])) {
421             $_uri = $_uri['scheme'] . '://' . $_uri['host'];
422             foreach ($this->trusted_uri as $pattern) {
423                 if (preg_match($pattern, $_uri)) {
424                     return true;
425                 }
426             }
427         }
428
429         throw new SmartyException("URI '{$uri}' not allowed by security setting");
430     }
431
432     /**
433      * Check if directory of file resource is trusted.
434      *
435      * @param  string $filepath
436      *
437      * @return boolean         true if directory is trusted
438      * @throws SmartyException if PHP directory is not trusted
439      */
440     public function isTrustedPHPDir($filepath)
441     {
442         if (empty($this->trusted_dir)) {
443             throw new SmartyException("directory '{$filepath}' not allowed by security setting (no trusted_dir specified)");
444         }
445
446         // check if index is outdated
447         if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) {
448             $this->_php_resource_dir = array();
449
450             $this->_trusted_dir = $this->trusted_dir;
451             foreach ((array) $this->trusted_dir as $directory) {
452                 $directory = realpath($directory);
453                 $this->_php_resource_dir[$directory] = true;
454             }
455         }
456
457         $_filepath = realpath($filepath);
458         $directory = dirname($_filepath);
459         $_directory = array();
460         while (true) {
461             // remember the directory to add it to _resource_dir in case we're successful
462             $_directory[] = $directory;
463             // test if the directory is trusted
464             if (isset($this->_php_resource_dir[$directory])) {
465                 // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup
466                 $this->_php_resource_dir = array_merge($this->_php_resource_dir, $_directory);
467
468                 return true;
469             }
470             // abort if we've reached root
471             if (($pos = strrpos($directory, DS)) === false || !isset($directory[2])) {
472                 break;
473             }
474             // bubble up one level
475             $directory = substr($directory, 0, $pos);
476         }
477
478         throw new SmartyException("directory '{$_filepath}' not allowed by security setting");
479     }
480 }