A fork of the NoNonsenseForum, we use as bugtracker http://camendesign.com/nononsense_forum https://github.com/Kroc/NoNonsenseForum
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.

334 lines
20KB

  1. <?php //bootstraps the forum
  2. /* ====================================================================================================================== */
  3. /* NoNonsense Forum v26 © Copyright (CC-BY) Kroc Camen 2010-2015
  4. licenced under Creative Commons Attribution 3.0 <creativecommons.org/licenses/by/3.0/deed.en_GB>
  5. you may do whatever you want to this code as long as you give credit to Kroc Camen, <camendesign.com>
  6. *//*
  7. what gets defined here:
  8. const / var attribs description
  9. --------------------------------------------------------------------------------------------------------------------
  10. key: b boolean (true / false)
  11. / ends with a slash
  12. // begins and ends with a slash
  13. ? OS-dependent slashes (use `DIRECTORY_SEPERATOR` to concatenate)
  14. U URL-encoded. use for HTML, do not use for server-side paths
  15. --------------------------------------------------------------------------------------------------------------------
  16. FORUM_ROOT ? full server path to NNF's folder
  17. FORUM_LIB / ? full server path to the 'lib' folder
  18. FORUM_PATH // U relative URL from the web-root, to NNF
  19. if NNF is at root, this would be "/", otherwise the "/sub-folder/" NNF is within
  20. HTACCESS b if the ".htaccess" file is present and enabled or not
  21. -- everything in 'config.php' (if present) and 'config.default.php' --
  22. FORUM_URL fully-qualified domain URL, e.g. "http://forum.camendesign.com"
  23. PAGE page-number given in the querystring -- not necessarily a valid page number!
  24. PATH / current sub-forum the viewer is in
  25. this is often used to test if the user is in a sub-forum or not
  26. PATH_URL / U URL-encoded version of `PATH` for use in constructing URLs
  27. PATH_DIR // ? relative server path from NNF's root (`FORUM_ROOT`) to the current sub-forum
  28. SUBFORUM the name of the current sub-forum (regardless of nesting), not URL-encoded
  29. FORM_SUBMIT b if an input form has been submitted (new-thread / reply / delete / append)
  30. NAME username given
  31. PASS password given
  32. AUTH b if the username / password are correct
  33. AUTH_HTTP b if the authentication was via HTTP_AUTH *and* was correct
  34. (will be false if the username / password were wrong, even if HTTP_AUTH was used)
  35. FORUM_LOCK the contents of 'locked.txt' which sets restrictions on the forum / sub-forums
  36. see section 5 in the README file
  37. $MODS array of the names of moderators for the whole forum, and the current sub-forum
  38. $MEMBERS array of the names of members for the current sub-forum
  39. IS_ADMIN b if the current viewer is the site admin (first name in 'mods.txt')
  40. IS_MOD b if the current viewer is a moderator for the current forum
  41. IS_MEMBER b if the current viewer is a member of the current forum
  42. THEME_ROOT / ? full server path to the currently selected theme
  43. -- everything in 'theme.php' (some dynamic strings for the default language) --
  44. -- everything in 'theme.config.php' (if present) and 'theme.config.default.php' --
  45. -- depending on `THEME_LANGS`, the contents of the 'lang.*.php' files --
  46. LANG currently user-selected language, '' for default
  47. DATE_FORMAT the human-readable date format of the currently user-selected language
  48. THEME_TITLE the `sprintf`-formatted `<title>` string of index / thread pages in the selected language
  49. THEME_TITLE_PAGENO the `sprintf`-formatted optional page-number portion of the title, in the selected lang.
  50. THEME_TITLE_APPEND the `sprintf`-formatted `<title>` string for the append page, in the selected language
  51. THEME_TITLE_DELETE the `sprintf`-formatted `<title>` string for the delete page, in the selected language
  52. THEME_REPLYNO the `sprintf`-formatted string for post numbering in threads, in the selected language
  53. THEME_RE the `sprintf`-formatted prefix for reply titles (e.g. "RE[1]:..."), in the selected lang.
  54. THEME_APPENDED the plain-text markup divider inserted when appending posts, in the forum's default lang.
  55. THEME_DEL_USER the HTML message used when a user deletes their own post, in the forum's default language
  56. THEME_DEL_MOD the HTML message used when a mod deletes a post, in the forum's default langugae
  57. THEME_HTML_ERROR the HTML message used when a post is corrupt (malformed HTML), in the forum's default lang.
  58. */
  59. /* server configuration
  60. ====================================================================================================================== */
  61. //default UTF-8 throughout
  62. mb_internal_encoding ('UTF-8');
  63. mb_regex_encoding ('UTF-8');
  64. //attempt to fix a small regex backtrace limit in PHP<5.3.7 that might cause the blockquote markup processing to fail
  65. //source: <www.kavoir.com/2009/12/php-regular-expression-matching-input-subject-string-length-limit.html>
  66. @ini_set ('pcre.backtrack_limit', 1000000);
  67. //full server path for absolute references, this includes the any sub-folders NNF might be in
  68. define ('FORUM_ROOT', dirname (__FILE__));
  69. //location of the 'lib' folder, full server path
  70. define ('FORUM_LIB', FORUM_ROOT.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR);
  71. //correct PHP version?
  72. if (version_compare (PHP_VERSION, '5.2.3') < 0) require FORUM_LIB.'error_phpver.php';
  73. //if Apache is being used, check Apache version
  74. if (function_exists ('apache_get_version')) if (!preg_match (
  75. //depending on the `ServerTokens` directive, the Apache version string might be nothing more than "Apache",
  76. //allow this, but if a version number is present detect v2.1-99+
  77. //<php.net/manual/en/function.apache-get-version.php#75591>
  78. //also note that the string "NOYB" (None Of Your Business) is surprisingly common and we need to allow this through
  79. //(with thanks to folderol and Zegnat for reporting)
  80. '/noyb|apache(?:\/(?:2(?:\.[1-9]|\.[1-9][0-9]+)?|[3-9]|[1-9][0-9]+)?)?/i', apache_get_version ())
  81. ) require FORUM_LIB.'error_apachever.php';
  82. //shared / library code
  83. require_once FORUM_LIB.'utf8safe.php'; //import the websafe (sanitised I/O) functions
  84. require_once FORUM_LIB.'domtemplate/domtemplate.php'; //import the templating engine
  85. require_once FORUM_LIB.'functions.php'; //import NNF's shared functions
  86. //location of NNF relative to the webroot, i.e. if NNF is in a sub-folder or not
  87. //we URL-encode this as it’s never used for server-side paths, `FORUM_ROOT` / `FORUM_LIB` are for that
  88. define ('FORUM_PATH', safeURL (str_replace (
  89. array ('\\', '//'), '/', //- replace Windows forward-slash with backslash
  90. dirname ($_SERVER['SCRIPT_NAME']).'/' //- always starts with a slash and ends in one
  91. )));
  92. /* site configuration
  93. ---------------------------------------------------------------------------------------------------------------------- */
  94. //try set the forum owner’s personal config ('config.php'), if it exists
  95. @(include './config.php');
  96. //include the defaults: (for anything missing from the user’s config)
  97. //see that file for descriptions of the different available options
  98. @(include './config.default.php') or require FORUM_LIB.'error_configdefault.php';
  99. //PHP 5.3 issues a warning if the timezone is not set when using date commands
  100. //(`FORUM_TIMEZONE` is set in the config and defaults to 'UTC')
  101. date_default_timezone_set (FORUM_TIMEZONE);
  102. //the full URL of the site is dependent on HTTPS configuration, so we wait until now to define it
  103. define ('FORUM_URL', 'http'. //base URL to produce hyperlinks throughout:
  104. (FORUM_HTTPS || @$_SERVER['HTTPS'] == 'on' ? 's' : ''). //- if HTTPS is enforced, links in RSS will use it
  105. '://'.$_SERVER['HTTP_HOST']
  106. );
  107. //is the htaccess working properly?
  108. //('.htaccess' sets this variable for us)
  109. define ('HTACCESS', (bool) @$_SERVER['HTTP_HTACCESS']);
  110. //if '.htaccess' is missing or disabled, and the 'users' folder is in an insecure location, warn the site admin to move it
  111. if (!HTACCESS && FORUM_USERS == 'users') require FORUM_LIB.'error_htaccess.php';
  112. /* common input
  113. ---------------------------------------------------------------------------------------------------------------------- */
  114. //most pages allow for a page number; note that this is merely the user-input, it is not necessarily a valid page number!
  115. define ('PAGE', preg_match ('/^[1-9][0-9]*$/', @$_GET['page']) ? (int) $_GET['page'] : false);
  116. //all our pages use 'path' (often optional) to specify the sub-forum being viewed, so this is done here
  117. define ('PATH', preg_match ('/^(?:[^\.\/&]+\/)+$/', @$_GET['path']) ? $_GET['path'] : '');
  118. //a shorthand for when PATH is used in URL construction for HTML use
  119. define ('PATH_URL', safeURL (PATH));
  120. //for serverside use, like `chdir` / `unlink` (must replace the URL forward-slashes with backslashes on Windows)
  121. define ('PATH_DIR', !PATH ? DIRECTORY_SEPARATOR : DIRECTORY_SEPARATOR.str_replace ('/', DIRECTORY_SEPARATOR, PATH));
  122. //if we are in nested sub-folders, the name of the current sub-forum, exluding the rest
  123. //(not used in URLs, so we use `PATH` instead of `PATH_URL`)
  124. define ('SUBFORUM', @end (explode ('/', trim (PATH, '/'))));
  125. //deny access to some folders
  126. //TODO: this should generate a 403, but we don't have a 403 page designed yet
  127. foreach (array ('users/', 'lib/', 'themes/', 'cgi-bin/') as $_) if (stripos ($_, PATH) === 0) die ();
  128. //we have to change directory for `is_dir` to work, see <uk3.php.net/manual/en/function.is-dir.php#70005>
  129. //being in the right directory is also assumed for reading 'mods.txt' and when generating the RSS (`indexRSS`)
  130. //(oddly with `chdir` the path must end in a slash)
  131. @chdir (FORUM_ROOT.PATH_DIR) or die ('Invalid path');
  132. //TODO: that should generate a 404, but we can't create a 404 in PHP that will send the server's provided 404 page.
  133. // I may revist this if I create an NNF-provided 404 page
  134. //was an input form submitted? (used to determine form error checking; this doesn't apply to the sign-in button)
  135. define ('FORM_SUBMIT', (isset ($_POST['x'], $_POST['y']) || isset ($_POST['submit_x'], $_POST['submit_y'])));
  136. /* access control
  137. ====================================================================================================================== */
  138. /* name / password authorisation:
  139. ---------------------------------------------------------------------------------------------------------------------- */
  140. //for HTTP authentication (sign-in):
  141. //- CGI workaround <orangejuiceliberationfront.com/http-auth-with-php-in-cgi-mode-e-g-on-dreamhost/>
  142. if (@$_SERVER['HTTP_AUTHORIZATION']) list ($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode (
  143. ':', base64_decode (substr ($_SERVER['HTTP_AUTHORIZATION'], 6))
  144. );
  145. //all pages can accept a name / password when committing actions (new thread / reply &c.)
  146. //in the case of HTTP authentication (sign in), these are provided in the request header instead
  147. define ('NAME', mb_substr (@$_SERVER['PHP_AUTH_USER'] ? @$_SERVER['PHP_AUTH_USER'] : @$_POST['username'], 0, SIZE_NAME));
  148. define ('PASS', mb_substr (@$_SERVER['PHP_AUTH_PW'] ? @$_SERVER['PHP_AUTH_PW'] : @$_POST['password'], 0, SIZE_PASS));
  149. if (( //if HTTP authentication is used, we don’t need to validate the form fields
  150. @$_SERVER['PHP_AUTH_USER'] && @$_SERVER['PHP_AUTH_PW']
  151. ) || ( //if an input form was submitted:
  152. FORM_SUBMIT &&
  153. //are the name and password non-blank?
  154. NAME && PASS &&
  155. //the email check is a fake hidden field in the form to try and fool spam bots
  156. isset ($_POST['email']) && @$_POST['email'] == 'example@abc.com'
  157. )) {
  158. //users are stored as text files based on the hash of the given name
  159. $name = hash ('sha512', strtolower (NAME));
  160. $user = FORUM_ROOT.DIRECTORY_SEPARATOR.FORUM_USERS.DIRECTORY_SEPARATOR."$name.txt";
  161. //create the user, if new:
  162. //- if registrations are allowed (`FORUM_NEWBIES` is true)
  163. //- you can’t create new users with the HTTP_AUTH sign in
  164. if (FORUM_NEWBIES && !isset ($_SERVER['PHP_AUTH_USER']) && !file_exists ($user))
  165. file_put_contents ($user, hash ('sha512', $name.PASS)) or require FORUM_LIB.'error_permissions.php'
  166. ;
  167. //does password match?
  168. define ('AUTH', @file_get_contents ($user) == hash ('sha512', $name.PASS));
  169. //if signed in with HTTP_AUTH, confirm that it’s okay to use
  170. //(e.g. the user could still have given the wrong password with HTTP_AUTH)
  171. define ('AUTH_HTTP', @$_SERVER['PHP_AUTH_USER'] ? AUTH : false);
  172. //if the user clicked the sign-in button to authenticate, do a 303 redirect to the same URL to 'eat' the
  173. //form-submission so that if they click the back-button, they don't get prompted to "resubmit the form data"
  174. if (@$_POST['signin'] && AUTH_HTTP) header (
  175. 'Location: '.FORUM_URL.$_SERVER['REQUEST_URI'], true, 301
  176. );
  177. } else {
  178. define ('AUTH', false);
  179. define ('AUTH_HTTP', false);
  180. }
  181. /* access rights
  182. ---------------------------------------------------------------------------------------------------------------------- */
  183. //get the lock status of the current forum we’re in:
  184. //"threads" - only users in "mods.txt" / "members.txt" can start threads, but anybody can reply
  185. //"news" - as above, but the forum is listed by original posting date (descending), not last-reply date
  186. //"posts" - only users in "mods.txt" / "members.txt" can start threads or reply
  187. define ('FORUM_LOCK', trim (@file_get_contents ('locked.txt')));
  188. //get the list of moderators:
  189. //(`file` returns NULL if the file doesn’t exist; casting that to an array creates an array with a blank element, and
  190. // `array_filter` removes blank elements, including blank lines in the text file; we could use the `FILE_SKIP_EMPTY_LINES`
  191. // flag, but `array_filter` kills two birds with one stone since we don’t have to check if the file exists beforehand.)
  192. $MODS = array (
  193. //'mods.txt' on root for mods on all sub-forums
  194. 'GLOBAL'=> array_filter ((array) @file (FORUM_ROOT.DIRECTORY_SEPARATOR.'mods.txt', FILE_IGNORE_NEW_LINES)),
  195. //if in a sub-forum, the local 'mods.txt'
  196. 'LOCAL' => PATH ? array_filter ((array) @file ('mods.txt', FILE_IGNORE_NEW_LINES)) : array ()
  197. );
  198. //get the list (if any) of users allowed to access this current forum
  199. $MEMBERS = array_filter ((array) @file ('members.txt', FILE_IGNORE_NEW_LINES));
  200. //is the current user the site admin? (first name in the root 'mods.txt')
  201. define ('IS_ADMIN', AUTH && isAdmin (NAME));
  202. //is the current user a moderator in this forum?
  203. define ('IS_MOD', AUTH && isMod (NAME));
  204. //is the current user a member of this forum?
  205. define ('IS_MEMBER', AUTH && isMember (NAME));
  206. /* theme & translation
  207. ====================================================================================================================== */
  208. /* load the theme configuration
  209. ---------------------------------------------------------------------------------------------------------------------- */
  210. //shorthand to the server-side location of the particular theme folder (this gets used a lot)
  211. define ('THEME_ROOT', FORUM_ROOT.DIRECTORY_SEPARATOR.'themes'.DIRECTORY_SEPARATOR.FORUM_THEME.DIRECTORY_SEPARATOR);
  212. //load the theme-specific functions
  213. @(include THEME_ROOT.'theme.php') or require FORUM_LIB.'error_theme.php';
  214. //load the user’s theme configuration, if it exists
  215. @(include THEME_ROOT.'theme.config.php');
  216. //include the theme defaults
  217. @(include THEME_ROOT.'theme.config.default.php') or require FORUM_LIB.'error_configtheme.php';
  218. /* load translations and select one
  219. ---------------------------------------------------------------------------------------------------------------------- */
  220. //include the language translations
  221. foreach (explode (' ', THEME_LANGS) as $lang) @include THEME_ROOT."lang.$lang.php";
  222. //get / set the language to use
  223. //(note that the actual translation of the HTML is done in `prepareTemplate` in 'lib/functions.php')
  224. define ('LANG',
  225. //if the language selector has been used to choose a language:
  226. isset ($_POST['lang']) && setcookie (
  227. //- set the language cookie for 1 year
  228. "lang", $_POST['lang'], time ()+60*60*24*365, FORUM_PATH, $_SERVER['HTTP_HOST'], FORUM_HTTPS
  229. ) ? $_POST['lang']
  230. //otherwise, does a cookie already exist to set the language?
  231. : ( //validate that the language in the cookie actually exists!
  232. array_key_exists (@$_COOKIE['lang'], $LANG)
  233. ? @$_COOKIE['lang']
  234. : (
  235. //otherwise, try detect the language sent by the browser:
  236. $lang = @array_shift (array_intersect (
  237. //- find language codes in the HTTP header and compare with the theme provided languages
  238. preg_replace ('/^([a-z0-9-]+).*/i', '$1', explode (',', $_SERVER['HTTP_ACCEPT_LANGUAGE'])),
  239. explode (' ', THEME_LANGS)
  240. ))
  241. ? $lang
  242. //all else failing, use the default language
  243. : THEME_LANG
  244. )
  245. )
  246. );
  247. //for curtness, and straight-forward compatibility with older versions of NNF, we shorthand these translations;
  248. //the defaults (`LANG`='') are defined in 'theme.php' and overrided if the user selects a language ('lang.*.php')
  249. //(the purpose of each of these constants are described in the list at the top of this page)
  250. @define ('DATE_FORMAT', $LANG[LANG]['date_format']);
  251. @define ('THEME_TITLE', $LANG[LANG]['title']);
  252. @define ('THEME_TITLE_PAGENO', $LANG[LANG]['title_pagenum']);
  253. @define ('THEME_TITLE_APPEND', $LANG[LANG]['title_append']);
  254. @define ('THEME_TITLE_DELETE', $LANG[LANG]['title_delete']);
  255. @define ('THEME_REPLYNO', $LANG[LANG]['replynum']);
  256. //these texts get permenantly inserted into the RSS, so we don't refer to the user-selected language
  257. //but the default language set for the whole forum
  258. @define ('THEME_RE', $LANG[THEME_LANG]['re']);
  259. @define ('THEME_APPENDED', $LANG[THEME_LANG]['appended']);
  260. @define ('THEME_DEL_USER', $LANG[THEME_LANG]['delete_user']);
  261. @define ('THEME_DEL_MOD', $LANG[THEME_LANG]['delete_mod']);
  262. @define ('THEME_HTML_ERROR', $LANG[THEME_LANG]['corrupted']);
  263. /* send HTTP headers
  264. ====================================================================================================================== */
  265. //stop browsers caching, so you don’t have to refresh every time to see changes
  266. header ('Cache-Control: no-cache', true);
  267. header ('Expires: 0', true);
  268. //if enabled, enforce HTTPS
  269. if (FORUM_HTTPS) if (@$_SERVER['HTTPS'] == 'on') {
  270. //if forced-HTTPS is on and a HTTPS connection is being used, send the 30-day HSTS header
  271. //see <en.wikipedia.org/wiki/Strict_Transport_Security> for more details
  272. header ('Strict-Transport-Security: max-age=2592000');
  273. } else {
  274. //if forced-HTTPS is on and a HTTPS connection is not being used, redirect to the HTTPS version of the current page
  275. //(we don’t die here so that should the redirect be ignored, the HTTP version of the page will still be given)
  276. header ('Location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], true, 301);
  277. }
  278. //if the sign-in button was clicked, (and they're not already signed-in), invoke a HTTP_AUTH request in the browser:
  279. //the browser will pop up a login box itself (no HTML involved) and continue to send the name & password with each request
  280. if (!AUTH_HTTP && isset ($_POST['signin'])) {
  281. header ('WWW-Authenticate: Basic');
  282. header ('HTTP/1.0 401 Unauthorized');
  283. //we don't die here so that if they cancel the login prompt, they shouldn't get a blank page
  284. }
  285. ?>