weave_utils.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License Version
  6. # 1.1 (the "License"); you may not use this file except in compliance with
  7. # the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. # for the specific language governing rights and limitations under the
  13. # License.
  14. #
  15. # The Initial Developer of the Original Code is balu
  16. #
  17. # Portions created by the Initial Developer are Copyright (C) 2012
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. # Daniel Triendl <daniel@pew.cc>
  22. # Mark Straver <moonchild@palemoon.org>
  23. # Christian Wittmer <chris@computersalat.de>
  24. #
  25. # Alternatively, the contents of this file may be used under the terms of
  26. # either the GNU General Public License Version 2 or later (the "GPL"), or
  27. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28. # in which case the provisions of the GPL or the LGPL are applicable instead
  29. # of those above. If you wish to allow use of your version of this file only
  30. # under the terms of either the GPL or the LGPL, and not to allow others to
  31. # use your version of this file under the terms of the MPL, indicate your
  32. # decision by deleting the provisions above and replace them with the notice
  33. # and other provisions required by the GPL or the LGPL. If you do not delete
  34. # the provisions above, a recipient may use your version of this file under
  35. # the terms of any one of the MPL, the GPL or the LGPL.
  36. #
  37. # ***** END LICENSE BLOCK *****
  38. #Error constants
  39. define ('WEAVE_ERROR_INVALID_PROTOCOL', 1);
  40. define ('WEAVE_ERROR_INCORRECT_CAPTCHA', 2);
  41. define ('WEAVE_ERROR_INVALID_USERNAME', 3);
  42. define ('WEAVE_ERROR_NO_OVERWRITE', 4);
  43. define ('WEAVE_ERROR_USERID_PATH_MISMATCH', 5);
  44. define ('WEAVE_ERROR_JSON_PARSE', 6);
  45. define ('WEAVE_ERROR_MISSING_PASSWORD', 7);
  46. define ('WEAVE_ERROR_INVALID_WBO', 8);
  47. define ('WEAVE_ERROR_BAD_PASSWORD_STRENGTH', 9);
  48. define ('WEAVE_ERROR_INVALID_RESET_CODE', 10);
  49. define ('WEAVE_ERROR_FUNCTION_NOT_SUPPORTED', 11);
  50. define ('WEAVE_ERROR_NO_EMAIL', 12);
  51. define ('WEAVE_ERROR_INVALID_COLLECTION', 13);
  52. define ('WEAVE_ERROR_OVER_QUOTA', 14);
  53. define ('LOG_THE_ERROR', 0);
  54. define ('LOG_QUOTAS', 0);
  55. function log_quota($msg)
  56. {
  57. if ( LOG_QUOTAS == 1 )
  58. {
  59. $datei = fopen("/tmp/FSyncMS-quota.log","a");
  60. // $fmsg = sprintf("$msg\n");
  61. fputs($datei,"$msg\n");
  62. // fputs($datei,"Server ".print_r( $_SERVER, true));
  63. fclose($datei);
  64. }
  65. }
  66. function log_error($msg)
  67. {
  68. if ( LOG_THE_ERROR == 1 )
  69. {
  70. $datei = fopen("/tmp/FSyncMS-error.txt","a");
  71. // $fmsg = sprintf("$msg\n");
  72. fputs($datei,"$msg\n");
  73. // fputs($datei,"Server ".print_r( $_SERVER, true));
  74. fclose($datei);
  75. }
  76. }
  77. function report_problem($message, $code = 503)
  78. {
  79. $headers = array('400' => '400 Bad Request',
  80. '401' => '401 Unauthorized',
  81. '403' => '403 Forbidden',
  82. '404' => '404 Not Found',
  83. '412' => '412 Precondition Failed',
  84. '503' => '503 Service Unavailable');
  85. header('HTTP/1.1 ' . $headers{$code},true,$code);
  86. if ($code == 401)
  87. {
  88. header('WWW-Authenticate: Basic realm="Weave"');
  89. }
  90. log_error($message);
  91. exit(json_encode($message));
  92. }
  93. function fix_utf8_encoding($string)
  94. {
  95. if(mb_detect_encoding($string . " ", 'UTF-8,ISO-8859-1') == 'UTF-8')
  96. return $string;
  97. else
  98. return utf8_encode($string);
  99. }
  100. function get_phpinput()
  101. {
  102. #stupid php being helpful with input data...
  103. $putdata = fopen("php://input", "r");
  104. $string = '';
  105. while ($data = fread($putdata,2048)) {$string .= $data;} //hier will man ein limit einbauen
  106. return $string;
  107. }
  108. function get_json()
  109. {
  110. $jsonstring = get_phpinput();
  111. $json = json_decode(fix_utf8_encoding($jsonstring), true);
  112. if ($json === null)
  113. report_problem(WEAVE_ERROR_JSON_PARSE, 400);
  114. return $json;
  115. }
  116. function validate_username($username)
  117. {
  118. if (!$username)
  119. return false;
  120. if (strlen($username) > 32)
  121. return false;
  122. return preg_match('/[^A-Z0-9._-]/i', $username) ? false : true;
  123. }
  124. function validate_collection($collection)
  125. {
  126. if (!$collection)
  127. return false;
  128. if (strlen($collection) > 32)
  129. return false;
  130. // allow characters '?' and '=' in the collection string which e.g.
  131. // appear if the following request is send from firefox:
  132. // http://<server>/weave/1.1/<user>/storage/clients?full=1
  133. return preg_match('/[^A-Z0-9?=._-]/i', $collection) ? false : true;
  134. }
  135. #user exitsts
  136. function exists_user( $db)
  137. {
  138. #$user = strtolower($user);
  139. try{
  140. if(!$db->exists_user())
  141. return 0;
  142. return 1;
  143. }
  144. catch(Exception $e)
  145. {
  146. header("X-Weave-Backoff: 1800");
  147. report_problem($e->getMessage(), $e->getCode());
  148. }
  149. }
  150. # Gets the username and password out of the http headers, and checks them against the auth
  151. function verify_user($url_user, $db)
  152. {
  153. if (!$url_user || !preg_match('/^[A-Z0-9._-]+$/i', $url_user))
  154. report_problem(WEAVE_ERROR_INVALID_USERNAME, 400);
  155. $auth_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : null;
  156. $auth_pw = array_key_exists('PHP_AUTH_PW', $_SERVER) ? $_SERVER['PHP_AUTH_PW'] : null;
  157. if (is_null($auth_user) || is_null($auth_pw))
  158. {
  159. /* CGI/FCGI auth workarounds */
  160. $auth_str = null;
  161. if (array_key_exists('Authorization', $_SERVER))
  162. /* Standard fastcgi configuration */
  163. $auth_str = $_SERVER['Authorization'];
  164. else if (array_key_exists('AUTHORIZATION', $_SERVER))
  165. /* Alternate fastcgi configuration */
  166. $auth_str = $_SERVER['AUTHORIZATION'];
  167. else if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER))
  168. /* IIS/ISAPI and newer (yet to be released) fastcgi */
  169. $auth_str = $_SERVER['HTTP_AUTHORIZATION'];
  170. else if (array_key_exists('REDIRECT_HTTP_AUTHORIZATION', $_SERVER))
  171. /* mod_rewrite - per-directory internal redirect */
  172. $auth_str = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
  173. if (!is_null($auth_str))
  174. {
  175. /* Basic base64 auth string */
  176. if (preg_match('/Basic\s+(.*)$/', $auth_str))
  177. {
  178. $auth_str = substr($auth_str, 6);
  179. $auth_str = base64_decode($auth_str, true);
  180. if ($auth_str != FALSE) {
  181. $tmp = explode(':', $auth_str);
  182. if (count($tmp) == 2)
  183. {
  184. $auth_user = $tmp[0];
  185. $auth_pw = $tmp[1];
  186. }
  187. }
  188. }
  189. }
  190. }
  191. if ( ! $auth_user || ! $auth_pw) #do this first to avoid the cryptic error message if auth is missing
  192. {
  193. log_error("Auth failed 1 {");
  194. log_error(" User pw: ". $auth_user ." | ". $auth_pw);
  195. log_error(" Url_user: ". $url_user);
  196. log_error("}");
  197. report_problem('Authentication failed', '401');
  198. }
  199. $url_user = strtolower($url_user);
  200. if (strtolower($auth_user) != $url_user)
  201. {
  202. log_error("(140) Missmatch:".strtolower($auth_user)."|".$url_user);
  203. report_problem(WEAVE_ERROR_USERID_PATH_MISMATCH, 400);
  204. }
  205. try
  206. {
  207. $existingHash = $db->get_password_hash();
  208. $hash = WeaveHashFactory::factory();
  209. if ( ! $hash->verify(fix_utf8_encoding($auth_pw), $existingHash) )
  210. {
  211. log_error("Auth failed 2 {");
  212. log_error(" User pw: ". $auth_user ."|".$auth_pw ."|md5:". md5($auth_pw) ."|fix:". fix_utf8_encoding($auth_pw) ."|fix md5 ". md5(fix_utf8_encoding($auth_pw)));
  213. log_error(" Url_user: ".$url_user);
  214. log_error(" Existing hash: ".$existingHash);
  215. log_error("}");
  216. report_problem('Authentication failed', '401');
  217. } else {
  218. if ( $hash->needsUpdate($existingHash) ) {
  219. $db->change_password($hash->hash(fix_utf8_encoding($auth_pw)));
  220. }
  221. }
  222. }
  223. catch(Exception $e)
  224. {
  225. header("X-Weave-Backoff: 1800");
  226. log_error($e->getMessage(), $e->getCode());
  227. report_problem($e->getMessage(), $e->getCode());
  228. }
  229. // Login success - record login time
  230. $db->store_user_login($auth_user);
  231. return true;
  232. }
  233. function check_quota(&$db)
  234. {
  235. // Checks the quota and if over limit, returns "over quota" to the user.
  236. $auth_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : null;
  237. try {
  238. $quota_used = $db->get_storage_total();
  239. // log_quota("Debug quota: ".$auth_user." @ ".$quota_used." KB.");
  240. } catch (Exception $e) {
  241. log_error($e->getMessage(), $e->getCode());
  242. }
  243. if ((defined("MINQUOTA") && MINQUOTA) && (defined("MAXQUOTA") && MAXQUOTA)) {
  244. if ($quota_used > MINQUOTA && $quota_used <= MAXQUOTA) {
  245. // Inform the sync client they are nearing the quota
  246. $quota_remaining = (MAXQUOTA - $quota_used);
  247. $quota_remaining_bytes = $quota_remaining * 1024;
  248. header("X-Weave-Quota-Remaining: ".$quota_remaining_bytes);
  249. log_quota(date("Y-m-d H:i:s")." Nearing quota: ".$auth_user." - ".$quota_remaining." KB remaining.");
  250. if (defined(MINQUOTA_LOG_ERROR_OVER_QUOTA_ENABLE) && MINQUOTA_LOG_ERROR_OVER_QUOTA_ENABLE) {
  251. log_error("Quota warning issued: ".$quota_used."/".MAXQUOTA." KB used.");
  252. }
  253. }
  254. if ($quota_used > MAXQUOTA) {
  255. log_quota(date("Y-m-d H:i:s")." [!!] Over quota: ".$auth_user." @ ".$quota_used." KB.");
  256. // HTTP 400 with body error code 14 means over quota.
  257. report_problem(WEAVE_ERROR_OVER_QUOTA, 400);
  258. }
  259. }
  260. }
  261. function check_timestamp($collection, &$db)
  262. {
  263. if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER)
  264. && $db->get_max_timestamp($collection) > $_SERVER['HTTP_X_IF_UNMODIFIED_SINCE'])
  265. report_problem(WEAVE_ERROR_NO_OVERWRITE, 412);
  266. }
  267. function validate_search_params()
  268. {
  269. $params = array();
  270. $params['parentid'] = (array_key_exists('parentid', $_GET) && mb_strlen($_GET['parentid'], '8bit') <= 64 && strpos($_GET['parentid'], '/') === false) ? $_GET['parentid'] : null;
  271. $params['predecessorid'] = (array_key_exists('predecessorid', $_GET) && mb_strlen($_GET['predecessorid'], '8bit') <= 64 && strpos($_GET['predecessorid'], '/') === false) ? $_GET['predecessorid'] : null;
  272. $params['newer'] = (array_key_exists('newer', $_GET) && is_numeric($_GET['newer'])) ? round($_GET['newer'],2) : null;
  273. $params['older'] = (array_key_exists('older', $_GET) && is_numeric($_GET['older'])) ? round($_GET['older'],2) : null;
  274. $params['sort'] = (array_key_exists('sort', $_GET) && ($_GET['sort'] == 'oldest' || $_GET['sort'] == 'newest' || $_GET['sort'] == 'index')) ? $_GET['sort'] : null;
  275. $params['limit'] = (array_key_exists('limit', $_GET) && is_numeric($_GET['limit']) && $_GET['limit'] > 0) ? (int)$_GET['limit'] : null;
  276. $params['offset'] = (array_key_exists('offset', $_GET) && is_numeric($_GET['offset']) && $_GET['offset'] > 0) ? (int)$_GET['offset'] : null;
  277. $params['ids'] = null;
  278. if (array_key_exists('ids', $_GET))
  279. {
  280. $params['ids'] = array();
  281. foreach(explode(',', $_GET['ids']) as $id)
  282. {
  283. if (mb_strlen($id, '8bit') <= 64 && strpos($id, '/') === false)
  284. $params['ids'][] = $id;
  285. }
  286. }
  287. $params['index_above'] = (array_key_exists('index_above', $_GET) && is_numeric($_GET['index_above']) && $_GET['index_above'] > 0) ? (int)$_GET['index_above'] : null;
  288. $params['index_below'] = (array_key_exists('index_below', $_GET) && is_numeric($_GET['index_below']) && $_GET['index_below'] > 0) ? (int)$_GET['index_below'] : null;
  289. $params['depth'] = (array_key_exists('depth', $_GET) && is_numeric($_GET['depth']) && $_GET['depth'] > 0) ? (int)$_GET['depth'] : null;
  290. return $params;
  291. }
  292. ?>