]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | /** | |
3 | * Utility functions of a general nature which are used by | |
4 | * most AWL library classes. | |
5 | * | |
6 | * @package awl | |
7 | * @subpackage Utilities | |
8 | * @author Andrew McMillan <andrew@catalyst.net.nz> | |
9 | * @copyright Catalyst IT Ltd | |
10 | * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 | |
11 | */ | |
12 | ||
13 | if ( !function_exists('dbg_error_log') ) { | |
14 | /** | |
15 | * Writes a debug message into the error log using printf syntax. If the first | |
16 | * parameter is "ERROR" then the message will _always_ be logged. | |
17 | * Otherwise, the first parameter is a "component" name, and will only be logged | |
18 | * if $c->dbg["component"] is set to some non-null value. | |
19 | * | |
20 | * If you want to see every log message then $c->dbg["ALL"] can be set, to | |
21 | * override the debugging status of the individual components. | |
22 | * | |
23 | * @var string $component The component to identify itself, or "ERROR", or "LOG:component" | |
24 | * @var string $format A format string for the log message | |
25 | * @var [string $parameter ...] Parameters for the format string. | |
26 | */ | |
27 | function dbg_error_log() { | |
28 | global $c; | |
29 | $argc = func_num_args(); | |
30 | $args = func_get_args(); | |
31 | $type = "DBG"; | |
32 | $component = array_shift($args); | |
33 | if ( substr( $component, 0, 3) == "LOG" ) { | |
34 | // Special escape case for stuff that always gets logged. | |
35 | $type = 'LOG'; | |
36 | $component = substr($component,4); | |
37 | } | |
38 | else if ( $component == "ERROR" ) { | |
39 | $type = "***"; | |
40 | } | |
41 | else if ( isset($c->dbg["ALL"]) ) { | |
42 | $type = "ALL"; | |
43 | } | |
44 | else if ( !isset($c->dbg[strtolower($component)]) ) return; | |
45 | ||
46 | if ( 2 <= $argc ) { | |
47 | $format = array_shift($args); | |
48 | } | |
49 | else { | |
50 | $format = "%s"; | |
51 | } | |
52 | @error_log( $c->sysabbr.": $type: $component:". vsprintf( $format, $args ) ); | |
53 | } | |
54 | } | |
55 | ||
56 | ||
57 | ||
58 | if ( !function_exists('apache_request_headers') ) { | |
59 | /** | |
60 | * Forward compatibility so we can use the non-deprecated name in PHP4 | |
61 | * @package awl | |
62 | */ | |
63 | function apache_request_headers() { | |
64 | return getallheaders(); | |
65 | } | |
66 | } | |
67 | ||
68 | ||
69 | ||
70 | if ( !function_exists('dbg_log_array') ) { | |
71 | /** | |
72 | * Function to dump an array to the error log, possibly recursively | |
73 | * | |
74 | * @var string $component Which component should this log message identify itself from | |
75 | * @var string $name What name should this array dump identify itself as | |
76 | * @var array $arr The array to be dumped. | |
77 | * @var boolean $recursive Should the dump recurse into arrays/objects in the array | |
78 | */ | |
79 | function dbg_log_array( $component, $name, $arr, $recursive = false ) { | |
80 | if ( !isset($arr) || (gettype($arr) != 'array' && gettype($arr) != 'object') ) { | |
81 | dbg_error_log( $component, "%s: array is not set, or is not an array!", $name); | |
82 | return; | |
83 | } | |
84 | foreach ($arr as $key => $value) { | |
85 | dbg_error_log( $component, "%s: >>%s<< = >>%s<<", $name, $key, | |
86 | (gettype($value) == 'array' || gettype($value) == 'object' ? gettype($value) : $value) ); | |
87 | if ( $recursive && (gettype($value) == 'array' || (gettype($value) == 'object' && "$key" != 'self' && "$key" != 'parent') ) ) { | |
88 | dbg_log_array( $component, "$name"."[$key]", $value, $recursive ); | |
89 | } | |
90 | } | |
91 | } | |
92 | } | |
93 | ||
94 | ||
95 | ||
96 | if ( !function_exists("session_salted_md5") ) { | |
97 | /** | |
98 | * Make a salted MD5 string, given a string and (possibly) a salt. | |
99 | * | |
100 | * If no salt is supplied we will generate a random one. | |
101 | * | |
102 | * @param string $instr The string to be salted and MD5'd | |
103 | * @param string $salt Some salt to sprinkle into the string to be MD5'd so we don't get the same PW always hashing to the same value. | |
104 | * @return string The salt, a * and the MD5 of the salted string, as in SALT*SALTEDHASH | |
105 | */ | |
106 | function session_salted_md5( $instr, $salt = "" ) { | |
107 | if ( $salt == "" ) $salt = substr( md5(rand(100000,999999)), 2, 8); | |
108 | dbg_error_log( "Login", "Making salted MD5: salt=$salt, instr=$instr, md5($salt$instr)=".md5($salt . $instr) ); | |
109 | return ( sprintf("*%s*%s", $salt, md5($salt . $instr) ) ); | |
110 | } | |
111 | } | |
112 | ||
113 | ||
114 | ||
115 | if ( !function_exists("session_salted_sha1") && version_compare(phpversion(), "4.9.9") > 0 ) { | |
116 | /** | |
117 | * Make a salted SHA1 string, given a string and (possibly) a salt. PHP5 only (although it | |
118 | * could be made to work on PHP4 (@see http://www.openldap.org/faq/data/cache/347.html). The | |
119 | * algorithm used here is compatible with OpenLDAP so passwords generated through this function | |
120 | * should be able to be migrated to OpenLDAP by using the part following the second '*', i.e. | |
121 | * the '{SSHA}....' part. | |
122 | * | |
123 | * If no salt is supplied we will generate a random one. | |
124 | * | |
125 | * @param string $instr The string to be salted and SHA1'd | |
126 | * @param string $salt Some salt to sprinkle into the string to be SHA1'd so we don't get the same PW always hashing to the same value. | |
127 | * @return string A *, the salt, a * and the SHA1 of the salted string, as in *SALT*SALTEDHASH | |
128 | */ | |
129 | function session_salted_sha1( $instr, $salt = "" ) { | |
130 | if ( $salt == "" ) $salt = substr( str_replace('*','',base64_encode(sha1(rand(100000,9999999),true))), 2, 9); | |
131 | dbg_error_log( "Login", "Making salted SHA1: salt=$salt, instr=$instr, encoded($instr$salt)=".base64_encode(sha1($instr . $salt, true).$salt) ); | |
132 | return ( sprintf("*%s*{SSHA}%s", $salt, base64_encode(sha1($instr.$salt, true) . $salt ) ) ); | |
133 | } | |
134 | } | |
135 | ||
136 | ||
137 | if ( !function_exists("session_validate_password") ) { | |
138 | /** | |
139 | * Checks what a user entered against the actual password on their account. | |
140 | * @param string $they_sent What the user entered. | |
141 | * @param string $we_have What we have in the database as their password. Which may (or may not) be a salted MD5. | |
142 | * @return boolean Whether or not the users attempt matches what is already on file. | |
143 | */ | |
144 | function session_validate_password( $they_sent, $we_have ) { | |
145 | if ( preg_match('/^\*\*.+$/', $we_have ) ) { | |
146 | // The "forced" style of "**plaintext" to allow easier admin setting | |
147 | return ( "**$they_sent" == $we_have ); | |
148 | } | |
149 | ||
150 | if ( preg_match('/^\*(.+)\*{[A-Z]+}.+$/', $we_have, $regs ) ) { | |
151 | if ( function_exists("session_salted_sha1") ) { | |
152 | // A nicely salted sha1sum like "*<salt>*{SSHA}<salted_sha1>" | |
153 | $salt = $regs[1]; | |
154 | $sha1_sent = session_salted_sha1( $they_sent, $salt ) ; | |
155 | return ( $sha1_sent == $we_have ); | |
156 | } | |
157 | else { | |
158 | dbg_error_log( "ERROR", "Password is salted SHA-1 but you are using PHP4!" ); | |
159 | echo <<<EOERRMSG | |
160 | <html> | |
161 | <head> | |
162 | <title>Salted SHA1 Password format not supported with PHP4</title> | |
163 | </head> | |
164 | <body> | |
165 | <h1>Salted SHA1 Password format not supported with PHP4</h1> | |
166 | <p>At some point you have used PHP5 to set the password for this user and now you are | |
167 | using PHP4. You will need to assign a new password to this user using PHP4, or ensure | |
168 | you use PHP5 everywhere (recommended).</p> | |
169 | <p>AWL has now switched to using salted SHA-1 passwords by preference in a format | |
170 | compatible with OpenLDAP.</p> | |
171 | </body> | |
172 | </html> | |
173 | EOERRMSG; | |
174 | exit; | |
175 | } | |
176 | } | |
177 | ||
178 | if ( preg_match('/^\*(.+)\*.+$/', $we_have, $regs ) ) { | |
179 | // A nicely salted md5sum like "*<salt>*<salted_md5>" | |
180 | $salt = $regs[1]; | |
181 | $md5_sent = session_salted_md5( $they_sent, $salt ) ; | |
182 | return ( $md5_sent == $we_have ); | |
183 | } | |
184 | ||
185 | // Anything else is bad | |
186 | return false; | |
187 | ||
188 | } | |
189 | } | |
190 | ||
191 | ||
192 | ||
193 | if ( !function_exists("replace_uri_params") ) { | |
194 | /** | |
195 | * Given a URL (presumably the current one) and a parameter, replace the value of parameter, | |
196 | * extending the URL as necessary if the parameter is not already there. | |
197 | * @param string $uri The URI we will be replacing parameters in. | |
198 | * @param array $replacements An array of replacement pairs array( "replace_this" => "with this" ) | |
199 | * @return string The URI with the replacements done. | |
200 | */ | |
201 | function replace_uri_params( $uri, $replacements ) { | |
202 | $replaced = $uri; | |
203 | foreach( $replacements AS $param => $new_value ) { | |
204 | $rxp = preg_replace( '/([\[\]])/', '\\\\$1', $param ); // Some parameters may be arrays. | |
205 | $regex = "/([&?])($rxp)=([^&]+)/"; | |
206 | dbg_error_log("core", "Looking for [%s] to replace with [%s] regex is %s and searching [%s]", $param, $new_value, $regex, $replaced ); | |
207 | if ( preg_match( $regex, $replaced ) ) | |
208 | $replaced = preg_replace( $regex, "\$1$param=$new_value", $replaced); | |
209 | else | |
210 | $replaced .= "&$param=$new_value"; | |
211 | } | |
212 | if ( ! preg_match( '/\?/', $replaced ) ) { | |
213 | $replaced = preg_replace("/&(.+)$/", "?\$1", $replaced); | |
214 | } | |
215 | $replaced = str_replace("&", "--AmPeRsAnD--", $replaced); | |
216 | $replaced = str_replace("&", "&", $replaced); | |
217 | $replaced = str_replace("--AmPeRsAnD--", "&", $replaced); | |
218 | dbg_error_log("core", "URI <<$uri>> morphed to <<$replaced>>"); | |
219 | return $replaced; | |
220 | } | |
221 | } | |
222 | ||
223 | ||
224 | if ( !function_exists("uuid") ) { | |
225 | /** | |
226 | * Generates a Universally Unique IDentifier, version 4. | |
227 | * | |
228 | * RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt) defines a special type of Globally | |
229 | * Unique IDentifiers (GUID), as well as several methods for producing them. One | |
230 | * such method, described in section 4.4, is based on truly random or pseudo-random | |
231 | * number generators, and is therefore implementable in a language like PHP. | |
232 | * | |
233 | * We choose to produce pseudo-random numbers with the Mersenne Twister, and to always | |
234 | * limit single generated numbers to 16 bits (ie. the decimal value 65535). That is | |
235 | * because, even on 32-bit systems, PHP's RAND_MAX will often be the maximum *signed* | |
236 | * value, with only the equivalent of 31 significant bits. Producing two 16-bit random | |
237 | * numbers to make up a 32-bit one is less efficient, but guarantees that all 32 bits | |
238 | * are random. | |
239 | * | |
240 | * The algorithm for version 4 UUIDs (ie. those based on random number generators) | |
241 | * states that all 128 bits separated into the various fields (32 bits, 16 bits, 16 bits, | |
242 | * 8 bits and 8 bits, 48 bits) should be random, except : (a) the version number should | |
243 | * be the last 4 bits in the 3rd field, and (b) bits 6 and 7 of the 4th field should | |
244 | * be 01. We try to conform to that definition as efficiently as possible, generating | |
245 | * smaller values where possible, and minimizing the number of base conversions. | |
246 | * | |
247 | * @copyright Copyright (c) CFD Labs, 2006. This function may be used freely for | |
248 | * any purpose ; it is distributed without any form of warranty whatsoever. | |
249 | * @author David Holmes <dholmes@cfdsoftware.net> | |
250 | * | |
251 | * @return string A UUID, made up of 32 hex digits and 4 hyphens. | |
252 | */ | |
253 | ||
254 | function uuid() { | |
255 | ||
256 | // The field names refer to RFC 4122 section 4.1.2 | |
257 | ||
258 | return sprintf('%04x%04x-%04x-%03x4-%04x-%04x%04x%04x', | |
259 | mt_rand(0, 65535), mt_rand(0, 65535), // 32 bits for "time_low" | |
260 | mt_rand(0, 65535), // 16 bits for "time_mid" | |
261 | mt_rand(0, 4095), // 12 bits before the 0100 of (version) 4 for "time_hi_and_version" | |
262 | bindec(substr_replace(sprintf('%016b', mt_rand(0, 65535)), '01', 6, 2)), | |
263 | // 8 bits, the last two of which (positions 6 and 7) are 01, for "clk_seq_hi_res" | |
264 | // (hence, the 2nd hex digit after the 3rd hyphen can only be 1, 5, 9 or d) | |
265 | // 8 bits for "clk_seq_low" | |
266 | mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535) // 48 bits for "node" | |
267 | ); | |
268 | } | |
269 | } | |
270 | ||
271 | if ( !function_exists("translate") ) { | |
272 | require_once("Translation.php"); | |
273 | } | |
274 | ||
275 | if ( !function_exists("clone") && version_compare(phpversion(), '5.0') < 0) { | |
276 | /** | |
277 | * PHP5 screws with the assignment operator changing so that $a = $b means that | |
278 | * $a becomes a reference to $b. There is a clone() that we can use in PHP5, so | |
279 | * we have to emulate that for PHP4. Bleargh. | |
280 | */ | |
281 | eval( 'function clone($object) { return $object; }' ); | |
282 | } | |
283 | ||
284 | if ( !function_exists("quoted_printable_encode") ) { | |
285 | /** | |
286 | * Process a string to fit the requirements of RFC2045 section 6.7. Note that | |
287 | * this works, but replaces more characters than the minimum set. For readability | |
288 | * the spaces aren't encoded as =20 though. | |
289 | */ | |
290 | function quoted_printable_encode($string) { | |
291 | return preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\r\n", str_replace("%","=",str_replace("%20"," ",rawurlencode($string)))); | |
292 | } | |
293 | } | |
294 | ||
295 | ||
296 | if ( !function_exists("clean_by_regex") ) { | |
297 | /** | |
298 | * Clean a value by applying a regex to it. If it is an array apply it to | |
299 | * each element in the array recursively. If it is an object we don't mess | |
300 | * with it. | |
301 | */ | |
302 | function clean_by_regex( $val, $regex ) { | |
303 | if ( is_null($val) ) return null; | |
304 | switch( $regex ) { | |
305 | case 'int': $regex = '#^\d+$#'; break; | |
306 | } | |
307 | if ( is_array($val) ) { | |
308 | foreach( $val AS $k => $v ) { | |
309 | $val[$k] = clean_by_regex($v,$regex); | |
310 | } | |
311 | } | |
312 | else if ( ! is_object($val) ) { | |
313 | if ( preg_match( $regex, $val, $matches) ) { | |
314 | $val = $matches[0]; | |
315 | } | |
316 | else { | |
317 | $val = ''; | |
318 | } | |
319 | } | |
320 | return $val; | |
321 | } | |
322 | } | |
323 | ||
324 | ||
325 | if ( !function_exists("param_to_global") ) { | |
326 | /** | |
327 | * Convert a parameter to a global. We first look in _POST and then in _GET, | |
328 | * and if they passed in a bunch of valid characters, we will make sure the | |
329 | * incoming is cleaned to only match that set. | |
330 | * | |
331 | * @param string $varname The name of the global variable to put the answer in | |
332 | * @param string $match_regex The part of the parameter matching this regex will be returned | |
333 | * @param string $alias1 An alias for the name that we should look for first. | |
334 | * @param " ... More aliases, in the order which they should be examined. $varname will be appended to the end. | |
335 | */ | |
336 | function param_to_global( ) { | |
337 | $args = func_get_args(); | |
338 | ||
339 | $varname = array_shift($args); | |
340 | $GLOBALS[$varname] = null; | |
341 | ||
342 | $match_regex = null; | |
343 | $argc = func_num_args(); | |
344 | if ( $argc > 1 ) { | |
345 | $match_regex = array_shift($args); | |
346 | } | |
347 | ||
348 | $args[] = $varname; | |
349 | foreach( $args AS $k => $name ) { | |
350 | if ( isset($_POST[$name]) ) { | |
351 | $result = $_POST[$name]; | |
352 | break; | |
353 | } | |
354 | else if ( isset($_GET[$name]) ) { | |
355 | $result = $_GET[$name]; | |
356 | break; | |
357 | } | |
358 | } | |
359 | if ( !isset($result) ) return null; | |
360 | ||
361 | if ( isset($match_regex) ) { | |
362 | $result = clean_by_regex( $result, $match_regex ); | |
363 | } | |
364 | ||
365 | $GLOBALS[$varname] = $result; | |
366 | return $result; | |
367 | } | |
368 | } | |
369 | ||
370 | ||
371 | if ( !function_exists("get_fields") ) { | |
372 | /** | |
373 | * @var array $_AWL_field_cache is a cache of the field names for a table | |
374 | */ | |
375 | $_AWL_field_cache = array(); | |
376 | ||
377 | ||
378 | /** | |
379 | * Get the names of the fields for a particular table | |
380 | * @param string $tablename The name of the table. | |
381 | * @return array of string The public fields in the table. | |
382 | */ | |
383 | function get_fields( $tablename ) { | |
384 | global $_AWL_field_cache; | |
385 | ||
386 | if ( !isset($_AWL_field_cache[$tablename]) ) { | |
387 | dbg_error_log( "DataUpdate", ":get_fields: Loaded fields for table '$tablename'" ); | |
388 | $sql = "SELECT f.attname, t.typname FROM pg_attribute f "; | |
389 | $sql .= "JOIN pg_class c ON ( f.attrelid = c.oid ) "; | |
390 | $sql .= "JOIN pg_type t ON ( f.atttypid = t.oid ) "; | |
391 | $sql .= "WHERE relname = ? AND attnum >= 0 order by f.attnum;"; | |
392 | $qry = new PgQuery( $sql, $tablename ); | |
393 | $qry->Exec("DataUpdate"); | |
394 | $fields = array(); | |
395 | while( $row = $qry->Fetch() ) { | |
396 | $fields["$row->attname"] = $row->typname; | |
397 | } | |
398 | $_AWL_field_cache[$tablename] = $fields; | |
399 | } | |
400 | return $_AWL_field_cache[$tablename]; | |
401 | } | |
402 | } | |
403 |