PostfixAdmin - web based virtual user administration interface for Postfix mail servers https://postfixadmin.github.io/postfixadmin/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
5.4 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
  1. <?php
  2. /**
  3. * Turn on sanitisation of all data by default so it's not possible for XSS flaws to occur in PFA
  4. */
  5. class PFASmarty {
  6. public static $instance = null;
  7. /**
  8. * @var Smarty
  9. */
  10. protected $template;
  11. public static function getInstance() {
  12. if (self::$instance) {
  13. return self::$instance;
  14. }
  15. self::$instance = new PFASmarty();
  16. return self::$instance;
  17. }
  18. private function __construct() {
  19. $CONF = Config::getInstance()->getAll();
  20. $theme = '';
  21. if (isset($CONF['theme']) && is_dir(dirname(__FILE__) . "/../templates/" . $CONF['theme'])) {
  22. $theme = $CONF['theme'];
  23. }
  24. $this->template = new Smarty();
  25. $template_dir = __DIR__ . '/../templates/' . $theme;
  26. if (!is_dir($template_dir)) {
  27. $template_dir = __DIR__ . '/../templates/';
  28. }
  29. $this->template->setTemplateDir($template_dir);
  30. // if it's not present or writeable, smarty should just not cache.
  31. $templates_c = dirname(__FILE__) . '/../templates_c';
  32. if (is_dir($templates_c) && is_writeable($templates_c)) {
  33. $this->template->setCompileDir($templates_c);
  34. } else {
  35. # unfortunately there's no sane way to just disable compiling of templates
  36. clearstatcache(); // just incase someone just fixed it; on their next refresh it should work.
  37. error_log("ERROR: directory $templates_c doesn't exist or isn't writeable for the webserver");
  38. die("ERROR: the templates_c directory doesn't exist or isn't writeable for the webserver");
  39. }
  40. $this->template->registerPlugin('function', 'htmlentities', 'htmlentities');
  41. $this->configureTheme('');// default to something.
  42. }
  43. /**
  44. * @param string $rel_path - relative path for referenced css etc dependencies - e.g. users/edit.php needs '../' else, it's ''.
  45. */
  46. public function configureTheme(string $rel_path = '') {
  47. $CONF = Config::getInstance()->getAll();
  48. // see: https://github.com/postfixadmin/postfixadmin/issues/410
  49. // ignore $CONF['theme_css'] if it points to css/default.css and we have css/bootstrap.css.
  50. if ($CONF['theme_css'] == 'css/default.css' && is_file(__DIR__ . '/../public/css/bootstrap.css')) {
  51. // silently upgrade to bootstrap, css/default.css does not exist.
  52. $CONF['theme_css'] = 'css/bootstrap.css';
  53. }
  54. $CONF['theme_css'] = $rel_path . htmlentities($CONF['theme_css']);
  55. if (!empty($CONF['theme_custom_css'])) {
  56. $CONF['theme_custom_css'] = $rel_path . htmlentities($CONF['theme_custom_css']);
  57. }
  58. if (array_key_exists('theme_favicon', $CONF)) {
  59. $CONF['theme_favicon'] = $rel_path . htmlentities($CONF['theme_favicon']);
  60. }
  61. $CONF['theme_logo'] = $rel_path . htmlentities($CONF['theme_logo']);
  62. $this->assign('rel_path', $rel_path);
  63. $this->assign('CONF', $CONF);
  64. }
  65. /**
  66. * @param string $key
  67. * @param mixed $value
  68. * @param bool $sanitise
  69. */
  70. public function assign($key, $value, $sanitise = true) {
  71. $this->template->assign("RAW_$key", $value);
  72. if ($sanitise == false) {
  73. return $this->template->assign($key, $value);
  74. }
  75. $clean = $this->sanitise($value);
  76. /* we won't run the key through sanitise() here... some might argue we should */
  77. return $this->template->assign($key, $clean);
  78. }
  79. /**
  80. * @param string $template
  81. * @return void
  82. */
  83. public function display($template) {
  84. $CONF = Config::getInstance()->getAll();
  85. $this->assign('PALANG', $CONF['__LANG'] ?? []);
  86. $this->assign('url_domain', '');
  87. $this->assign('version', $CONF['version'] ?? 'unknown');
  88. $this->assign('boolconf_alias_domain', Config::bool('alias_domain'));
  89. $this->assign('authentication_has_role', array('global_admin' => authentication_has_role('global-admin'), 'admin' => authentication_has_role('admin'), 'user' => authentication_has_role('user')));
  90. header("Expires: Sun, 16 Mar 2003 05:00:00 GMT");
  91. header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  92. header("Cache-Control: no-store, no-cache, must-revalidate");
  93. header("Cache-Control: post-check=0, pre-check=0", false);
  94. header("Pragma: no-cache");
  95. header("Content-Type: text/html; charset=UTF-8");
  96. $this->template->setConfigDir(__DIR__ . '/../configs');
  97. $this->template->display($template);
  98. unset($_SESSION['flash']); # cleanup flash messages
  99. }
  100. /**
  101. * Recursive cleaning of data, using htmlentities - this assumes we only ever output to HTML and we're outputting in UTF-8 charset
  102. *
  103. * @param mixed $data - array or primitive type; objects not supported.
  104. * @return mixed $data
  105. * */
  106. public function sanitise($data) {
  107. if (is_object($data) || is_null($data)) {
  108. return $data; // can't handle
  109. }
  110. if (!is_array($data)) {
  111. return htmlentities($data, ENT_QUOTES, 'UTF-8', false);
  112. }
  113. $clean = array();
  114. foreach ($data as $key => $value) {
  115. /* as this is a nested data structure it's more likely we'll output the key too (at least in my opinion, so we'll sanitise it too */
  116. $clean[$this->sanitise($key)] = $this->sanitise($value);
  117. }
  118. return $clean;
  119. }
  120. }