1<?php
2	/* libraries/security.php
3	 *
4	 * Copyright (C) by Hugo Leisink <hugo@leisink.net>
5	 * This file is part of the Banshee PHP framework
6	 * http://www.banshee-php.org/
7	 */
8
9	/* Pre-defined validation strings for valid_input()
10	 */
11	define("VALIDATE_CAPITALS",     "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
12	define("VALIDATE_NONCAPITALS",  "abcdefghijklmnopqrstuvwxyz");
13	define("VALIDATE_LETTERS",      VALIDATE_CAPITALS.VALIDATE_NONCAPITALS);
14	define("VALIDATE_PHRASE",       VALIDATE_LETTERS." ,.?!:;-'");
15	define("VALIDATE_NUMBERS",      "0123456789");
16	define("VALIDATE_EMAIL",        VALIDATE_LETTERS.VALIDATE_NUMBERS."_-@.");
17	define("VALIDATE_SYMBOLS",      "!@#$%^&*()_-+={}[]|\:;\"'`~<>,./?");
18	define("VALIDATE_URL",          VALIDATE_LETTERS.VALIDATE_NUMBERS."-_/.=");
19
20	define("VALIDATE_NONEMPTY",     0);
21
22	/* Secure password with PBKDF2
23	 *
24	 * INPUT:  string password, string salt
25	 * OUTPUT: string hashed password
26	 * ERROR:  -
27	 */
28	function hash_password($password, $salt) {
29		return hash_pbkdf2("sha256", $password, hash("sha256", $salt), PASSWORD_ITERATIONS, 0);
30	}
31
32	/* Validate input
33	 *
34	 * INPUT:  string input, string valid characters[, int length]
35	 * OUTPUT: boolean input oke
36	 * ERROR:  -
37	 */
38	function valid_input($data, $allowed, $length = null) {
39		if (is_array($data) == false) {
40			$data_len = strlen($data);
41
42			if ($length !== null) {
43				if ($length == VALIDATE_NONEMPTY) {
44					if ($data_len == 0) {
45						return false;
46					}
47				} else if ($data_len !== $length) {
48					return false;
49				}
50			} else if ($data_len == 0) {
51				return true;
52			}
53
54			$data = str_split($data);
55			$allowed = str_split($allowed);
56			$diff = array_diff($data, $allowed);
57
58			return count($diff) == 0;
59		} else foreach ($data as $item) {
60			if (valid_input($item, $allowed, $length) == false) {
61				return false;
62			}
63		}
64
65		return true;
66	}
67
68	/* Validate an e-mail address
69	 *
70	 * INPUT:  string e-mail address
71	 * OUTPUT: boolean e-mail address oke
72	 * ERROR:  -
73	 */
74	function valid_email($email) {
75		return email::valid_address($email);
76	}
77
78	/* Validate a date string
79	 *
80	 * INPUT:  string date
81	 * OUTPUT: boolean date oke
82	 * ERROR:  -
83	 */
84	function valid_date($date) {
85		if ($date == "0000-00-00") {
86			return false;
87		}
88
89		return preg_match("/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/", $date) === 1;
90	}
91
92	/* Validate a time string
93	 *
94	 * INPUT:  string time
95	 * OUTPUT: boolean time oke
96	 * ERROR:  -
97	 */
98	function valid_time($time) {
99		return preg_match("/^(([01]?[0-9])|(2[0-3])):[0-5][0-9](:[0-5][0-9])?$/", $time) === 1;
100	}
101
102	/* Validate a timestamp
103	 *
104	 * INPUT:  string timestamp
105	 * OUTPUT: boolean timestamp oke
106	 * ERROR:  -
107	 */
108	function valid_timestamp($timestamp) {
109		list($date, $time) = explode(" ", $timestamp, 2);
110		return valid_date($date) && valid_time($time);
111	}
112
113	/* Validate a telephone number
114	 *
115	 * INPUT:  string telephone number
116	 * OUTPUT: boolean telephone number oke
117	 * ERROR:  -
118	 */
119	function valid_phonenumber($phonenr) {
120		$phonenr = str_replace(" ", "", $phonenr);
121		return preg_match("/^(\+31|0)([0-9]{9}|6-?[0-9]{8}|[0-9]{2}-?[0-9]{7}|[0-9]{3}-?[0-9]{6})$/", $phonenr) === 1;
122	}
123
124	/* Get users with a certain role
125	 *
126	 * INPUT:  object database, string role name[, string role name, ...]
127	 * OUTPUT: array user information
128	 * ERROR:  false
129	 */
130	function users_with_role() {
131		$roles = func_get_args();
132		if (count($roles) < 2) {
133			return false;
134		}
135
136		$db = array_shift($roles);
137
138		$query = "select distinct u.* from users u, user_role m, roles r ".
139		         "where r.id=m.role_id and m.user_id=u.id and (".
140		         implode(" or ", array_fill(0, count($roles), "r.name=%s")).
141		         ")";
142
143		return $db->execute($query, $roles);
144	}
145
146	/* Return a per-page overview of the access levels
147	 *
148	 * INPUT:  object database
149	 * OUTPUT: array( string page => int access level[, ....] )
150	 * ERROR:  false
151	 */
152	function page_access_list($db, $user) {
153		$access_rights = array();
154
155		/* Public pages on disk
156		 */
157		$public = page_to_module(config_file("public_pages"));
158		foreach ($public as $page) {
159			$access_rights[$page] = 1;
160		}
161
162		/* Private pages on disk
163		 */
164		$private_pages = page_to_module(config_file("private_pages"));
165		foreach ($private_pages as $page) {
166			$access_rights[$page] = $user->is_admin ? YES : NO;
167		}
168
169		if ($user->logged_in && ($user->is_admin == false)) {
170			$query = "select * from roles where id in ".
171					 "(select role_id from user_role where user_id=%d)";
172			if (($roles = $db->execute($query, $user->id)) === false) {
173				return false;
174			}
175			foreach ($roles as $role) {
176				$role = array_slice($role, 2);
177				foreach ($role as $page => $level) {
178					$level = (int)$level;
179					if ($user->is_admin && ($level == NO)) {
180						$level = YES;
181					}
182					if (isset($access_rights[$page]) == false) {
183						$access_rights[$page] = $level;
184					} else if ($access_rights[$page] < $level) {
185						$access_rights[$page] = $level;
186					}
187				}
188			}
189		}
190
191		/* Pages in database
192		 */
193		if (($pages = $db->execute("select * from pages")) === false) {
194			return false;
195		}
196		foreach ($pages as $page) {
197			$access_rights[ltrim($page["url"], "/")] = is_false($page["private"]) || $user->is_admin ? YES : NO;
198		}
199
200		if ($user->logged_in && ($user->is_admin == false)) {
201			$conditions = $rids = array();
202			foreach ($user->role_ids as $rid) {
203				array_push($conditions, "role_id=%d");
204				array_push($rids, $rid);
205			}
206
207			$query = "select p.url,a.level from pages p, page_access a ".
208					 "where p.id=a.page_id and (".implode(" or ", $conditions).")";
209			if (($pages = $db->execute($query, $rids)) === false) {
210				return false;
211			}
212
213			foreach ($pages as $page) {
214				$url = ltrim($page["url"], "/");
215				if ($access_rights[$url] < $page["level"]) {
216					$access_rights[$url] = $page["level"];
217				}
218			}
219		}
220
221		return $access_rights;
222	}
223
224	/* Generate random string
225	 *
226	 * INPUT:  [int length]
227	 * OUTPUT: string random string
228	 * ERROR:  -
229	 */
230	function random_string($length = 32) {
231		$characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890";
232		$max_chars = strlen($characters) - 1;
233
234		$bytes = openssl_random_pseudo_bytes($length);
235
236		$result = "";
237		for ($i = 0; $i < $length; $i++) {
238			$result .= $characters[floor(ord($bytes[$i]) * $max_chars / 256)];
239		}
240
241		return $result;
242	}
243
244	/* Get user's one time key
245	 *
246	 * INPUT:  object database, int user identifier
247	 * OUTPUT: string one time key
248	 * ERROR:  false
249	 */
250	function one_time_key($db, $user_id) {
251		if (($user = $db->entry("users", $user_id)) == false) {
252			return false;
253		}
254
255		if ($user["one_time_key"] != null) {
256			return $user["one_time_key"];
257		}
258
259		$attempts = 3;
260		$query = "select id from users where one_time_key=%s";
261
262		do {
263			if ($attempts-- == 0) {
264				return false;
265			}
266
267			$key = random_string();
268
269			if (($result = $db->execute($query, $key)) === false) {
270				return false;
271			}
272		} while ($result != false);
273
274		if ($db->update("users", $user_id, array("one_time_key" => $key)) == false) {
275			return false;
276		}
277
278		return $key;
279	}
280?>
281