PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/paul/paul.php

https://bitbucket.org/babab/paul
PHP | 436 lines | 356 code | 65 blank | 15 comment | 31 complexity | af3e61299925c544e88b755d30f869df MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright (c) 2012, 2013 Benjamin Althues <benjamin@babab.nl>
  4. *
  5. * Permission to use, copy, modify, and distribute this software for any
  6. * purpose with or without fee is hereby granted, provided that the above
  7. * copyright notice and this permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. */
  17. class paul {
  18. public $base_url;
  19. protected $conf;
  20. protected $db;
  21. protected $secret_key;
  22. public static function error($errormsg)
  23. {
  24. die("<p><strong style=\"color:red\">paul error</strong> $errormsg");
  25. }
  26. public static function requireValidToken()
  27. {
  28. if ($_POST['token'] === $_SESSION['paul']['token']) {
  29. $_SESSION['paul']['token'] = '';
  30. return true;
  31. }
  32. self::error("Invalid token.");
  33. }
  34. public function __construct()
  35. {
  36. global $paul_conf;
  37. include_once 'config.php';
  38. if (isset($paul_conf))
  39. $this->conf = $paul_conf;
  40. else
  41. $this->conf = null;
  42. if (empty($this->conf))
  43. $this->error("Could not load config file. Please copy "
  44. . "'config.example.php' to 'config.php' and edit it.");
  45. $this->_apply_settings(array(
  46. 'base_url', 'redirect_after_login', 'redirect_after_logout',
  47. 'secret_key', 'db_host', 'db_port', 'db_name', 'db_user',
  48. 'db_prefix'
  49. ));
  50. $this->db_pass = '';
  51. if ($this->conf['db_pass'])
  52. $this->db_pass = $this->conf['db_pass'];
  53. if (!$this->db)
  54. $this->db = new dbhandler(
  55. $this->db_name,
  56. $this->db_user,
  57. $this->db_pass,
  58. $this->db_prefix,
  59. $this->db_host,
  60. $this->db_port
  61. );
  62. }
  63. public function create_token()
  64. {
  65. $token = md5($this->secret_key . mt_rand());
  66. $_SESSION['paul']['token'] = $token;
  67. return $token;
  68. }
  69. protected function get_token()
  70. {
  71. return $_SESSION['paul']['token'];
  72. }
  73. private function _apply_settings($settings)
  74. {
  75. foreach($settings as $setting) {
  76. if ($this->conf[$setting])
  77. $this->$setting = $this->conf[$setting];
  78. if (empty($this->$setting))
  79. self::error("Could not load the $setting setting " .
  80. "from config file.");
  81. }
  82. }
  83. }
  84. class dbhandler {
  85. private $db_prefix;
  86. private $db_conn;
  87. private $db_query;
  88. private $db_res;
  89. public function __construct($db_name, $db_user, $db_pass, $db_prefix,
  90. $db_host = 'localhost', $db_port = 3306)
  91. {
  92. $this->db_prefix = $db_prefix;
  93. $this->db_conn = mysql_connect($db_host.':'.$db_port,
  94. $db_user, $db_pass);
  95. if (!$this->db_conn)
  96. paul::error("Connection error: ". mysql_error());
  97. if (!mysql_select_db($db_name, $this->db_conn)) {
  98. paul::error("Error connecting to database '" . $db_name .
  99. "': ". mysql_error());
  100. }
  101. return $this;
  102. }
  103. public function query($query)
  104. {
  105. $q = str_replace('_T_', $this->db_prefix, $query);
  106. if (!$this->db_query = mysql_query($q, $this->db_conn))
  107. paul::error("Query error: ". mysql_error());
  108. return $this;
  109. }
  110. public function fetch()
  111. {
  112. $this->db_res = array();
  113. if ($this->db_query) {
  114. while ($row = mysql_fetch_assoc($this->db_query))
  115. $this->db_res[] = $row;
  116. }
  117. if (!empty($this->db_res))
  118. return $this->db_res;
  119. else
  120. return false;
  121. }
  122. public function qfetch($query)
  123. {
  124. return $this->query($query)->fetch();
  125. }
  126. public function qfetch_first($query)
  127. {
  128. $rows = $this->query($query . ' LIMIT 1')->fetch();
  129. if (isset($rows[0]))
  130. return $rows[0];
  131. else
  132. return false;
  133. }
  134. public function escape($input)
  135. {
  136. return mysql_real_escape_string(strip_tags($input));
  137. }
  138. }
  139. final class user extends paul
  140. {
  141. private $username;
  142. private $salt;
  143. private $password;
  144. public function __construct()
  145. {
  146. parent::__construct();
  147. }
  148. public function add($username, $password)
  149. {
  150. $this->username = $username;
  151. $this->_makesalt();
  152. $this->_makepassword($password);
  153. if ($this->user_exists())
  154. return false;
  155. $q = "INSERT INTO _T_users (username, password, salt,
  156. last_seen, last_ip)
  157. VALUES (
  158. '".$this->db->escape($this->username)."',
  159. '".$this->db->escape($this->password)."',
  160. '".$this->db->escape($this->salt)."',
  161. '" . time() . "',
  162. '" . htmlentities($_SERVER['REMOTE_ADDR']) . "'
  163. )";
  164. $this->db->query($q);
  165. return true;
  166. }
  167. public function authenticate_form()
  168. {
  169. if (empty($_POST))
  170. return false;
  171. if ($_SESSION['paul']['logged_in'])
  172. return false;
  173. $this->requireValidToken();
  174. $_SESSION['paul']['error'] = '';
  175. $_SESSION['paul']['logged_in'] = false;
  176. $_SESSION['paul']['logged_in_with_password'] = false;
  177. $this->username = filter_input(INPUT_POST, 'username',
  178. FILTER_SANITIZE_STRING);
  179. $password = filter_input(INPUT_POST, 'password',
  180. FILTER_SANITIZE_STRING);
  181. if (isset($_POST['password2'])) {
  182. $password2 = filter_input(INPUT_POST, 'password2',
  183. FILTER_SANITIZE_STRING);
  184. if ($password !== $password2) {
  185. $_SESSION['paul']['error'] = 'Passwords do not match, please '
  186. . 'try again.';
  187. $url = "$this->base_url/?cmd=register";
  188. header("Location: $url");
  189. exit;
  190. }
  191. if ($this->add($this->username, $password)) {
  192. $_SESSION['paul']['username'] = $this->username;
  193. $_SESSION['paul']['logged_in'] = true;
  194. return true;
  195. }
  196. else {
  197. $_SESSION['paul']['error'] = 'That username is already taken, please '
  198. . 'try another one.';
  199. $_SESSION['paul']['username_inp'] = $this->username;
  200. $url = "$this->base_url/?cmd=register $this->username";
  201. header("Location: $url");
  202. exit;
  203. }
  204. }
  205. $this->_makesalt();
  206. $this->_makepassword($password);
  207. if ($user = $this->fetch_user()) {
  208. if ($user['password'] === $this->password) {
  209. $_SESSION['paul']['username'] = $this->username;
  210. $_SESSION['paul']['logged_in'] = true;
  211. $_SESSION['paul']['logged_in_with_password'] = true;
  212. $_SESSION['paul']['last_seen'] = $user['last_seen'];
  213. $_SESSION['paul']['last_ip'] = $user['last_ip'];
  214. if (isset($_POST['remember_me'])) {
  215. $cookie = new cookie_login($this->username);
  216. $cookie->destroy();
  217. $cookie->assign();
  218. }
  219. $this->update_last_login($this->username);
  220. return true;
  221. }
  222. }
  223. $_SESSION['paul']['error'] = 'Wrong username or password';
  224. return false;
  225. }
  226. public function fetch_user()
  227. {
  228. $q = "SELECT * FROM _T_users WHERE username = '$this->username'";
  229. return $this->db->qfetch_first($q);
  230. }
  231. public function id($username)
  232. {
  233. if (empty($username))
  234. return false;
  235. $q = "SELECT user_id FROM _T_users WHERE username = '$username'";
  236. if ($res = $this->db->qfetch_first($q))
  237. return (int) $res['user_id'];
  238. }
  239. public function user_exists()
  240. {
  241. $q = "SELECT last_seen FROM _T_users "
  242. . "WHERE username = '$this->username'";
  243. return $this->db->qfetch_first($q) !== false;
  244. }
  245. public function update_last_login($username)
  246. {
  247. $user_id = $this->id($username);
  248. $q = "UPDATE _T_users
  249. SET last_seen = '".time()."',
  250. last_ip = '".htmlentities($_SERVER['REMOTE_ADDR'])."'
  251. WHERE user_id = '$user_id'";
  252. $this->db->query($q);
  253. }
  254. public function process_last_login($username)
  255. {
  256. $user_id = $this->id($username);
  257. $q = "SELECT last_seen, last_ip FROM _T_users
  258. WHERE user_id = '$user_id'";
  259. if ($res = $this->db->qfetch_first($q)) {
  260. $_SESSION['paul']['last_seen'] = (int) $res['last_seen'];
  261. $_SESSION['paul']['last_ip'] = $res['last_ip'];
  262. }
  263. }
  264. public function install()
  265. {
  266. $this->db->query("
  267. CREATE TABLE IF NOT EXISTS _T_users (
  268. user_id INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  269. username VARCHAR(100) NOT NULL,
  270. password VARCHAR(128) NOT NULL,
  271. salt VARCHAR(128) NOT NULL,
  272. last_seen INT(10) NOT NULL,
  273. last_ip VARCHAR(70) NOT NULL
  274. ) ENGINE = InnoDB"
  275. );
  276. }
  277. private function _makesalt()
  278. {
  279. $this->salt = hash('sha512', $this->username . $this->secret_key);
  280. }
  281. private function _makepassword($password)
  282. {
  283. $this->password = hash('sha512', $password . $this->salt);
  284. }
  285. }
  286. final class cookie_login extends paul
  287. {
  288. private $username;
  289. public function __construct($username)
  290. {
  291. parent::__construct();
  292. $this->username = $username;
  293. }
  294. public function destroy()
  295. {
  296. $cookie = $this->_parse_cookie();
  297. $q = "DELETE FROM _T_cookie_login
  298. WHERE username = '".$this->db->escape($this->username)."'
  299. AND token = '".$this->db->escape($cookie[1])."' LIMIT 1";
  300. $this->db->query($q);
  301. setcookie('persistent_login_token', '', time() - 3600);
  302. }
  303. public function destroy_all()
  304. {
  305. $q = "DELETE FROM _T_cookie_login
  306. WHERE username = '".$this->db->escape($this->username)."'";
  307. $this->db->query($q);
  308. setcookie('persistent_login_token', '', time() - 3600);
  309. }
  310. public function assign()
  311. {
  312. $token = $this->_create_token();
  313. setcookie('persistent_login_token', $token . $this->username,
  314. time() + 60*60*24*30);
  315. $q = "INSERT INTO _T_cookie_login (username ,token)
  316. VALUES (
  317. '".$this->db->escape($this->username)."',
  318. '".$this->db->escape($token)."'
  319. )";
  320. $this->db->query($q);
  321. }
  322. public function authorize()
  323. {
  324. if (!$cookie = $this->_parse_cookie())
  325. return false;
  326. $this->username = $cookie[0];
  327. $tokens = $this->_fetch_tokens();
  328. foreach ($tokens as $token) {
  329. if ($token['token'] === $cookie[1]) {
  330. $_SESSION['paul']['username'] = $this->username;
  331. $_SESSION['paul']['logged_in'] = true;
  332. $this->destroy();
  333. $this->assign();
  334. $user = new user;
  335. $user->process_last_login($this->username);
  336. $user->update_last_login($this->username);
  337. return true;
  338. }
  339. }
  340. }
  341. public function install()
  342. {
  343. $this->db->query("
  344. CREATE TABLE IF NOT EXISTS _T_cookie_login (
  345. username VARCHAR(100) NOT NULL,
  346. token VARCHAR(74) NOT NULL UNIQUE
  347. ) ENGINE = InnoDB"
  348. );
  349. }
  350. private function _fetch_tokens()
  351. {
  352. $q = "SELECT token FROM _T_cookie_login "
  353. . "WHERE username = '$this->username'";
  354. return $this->db->qfetch($q);
  355. }
  356. private function _create_token()
  357. {
  358. return hash('sha256', mt_rand() . $this->secret_key);
  359. }
  360. private function _parse_cookie()
  361. {
  362. if (!isset($_COOKIE['persistent_login_token']))
  363. return false;
  364. $cookie = $_COOKIE['persistent_login_token'];
  365. $token = substr($cookie, 0, 64);
  366. $username = substr($cookie, 64);
  367. if (empty($username))
  368. return false;
  369. return array($username, $token);
  370. }
  371. }