11 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
11 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
12 */ |
12 */ |
13 |
13 |
14 function db_error_handler($errno, $errstr, $errfile = false, $errline = false, $errcontext = Array() ) |
14 function db_error_handler($errno, $errstr, $errfile = false, $errline = false, $errcontext = Array() ) |
15 { |
15 { |
16 if ( !defined('ENANO_DEBUG') ) |
16 if ( !defined('ENANO_DEBUG') ) |
17 return; |
17 return; |
18 $e = error_reporting(0); |
18 $e = error_reporting(0); |
19 error_reporting($e); |
19 error_reporting($e); |
20 if ( $e < $errno ) |
20 if ( $e < $errno ) |
21 return; |
21 return; |
22 $errtype = 'Notice'; |
22 $errtype = 'Notice'; |
23 switch ( $errno ) |
23 switch ( $errno ) |
24 { |
24 { |
25 case E_ERROR: case E_USER_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: $errtype = 'Error'; break; |
25 case E_ERROR: case E_USER_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: $errtype = 'Error'; break; |
26 case E_WARNING: case E_USER_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: $errtype = 'Warning'; break; |
26 case E_WARNING: case E_USER_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: $errtype = 'Warning'; break; |
27 } |
27 } |
28 $debug = debug_backtrace(); |
28 $debug = debug_backtrace(); |
29 if ( !isset($debug[0]['file']) ) |
29 if ( !isset($debug[0]['file']) ) |
30 return false; |
30 return false; |
31 $debug = $debug[0]['file'] . ', line ' . $debug[0]['line']; |
31 $debug = $debug[0]['file'] . ', line ' . $debug[0]['line']; |
32 echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>"; |
32 echo "<b>$errtype:</b> $errstr<br />Error source:<pre>$debug</pre>"; |
33 } |
33 } |
34 |
34 |
35 global $db_sql_parse_time; |
35 global $db_sql_parse_time; |
36 $db_sql_parse_time = 0; |
36 $db_sql_parse_time = 0; |
37 |
37 |
38 class mysql { |
38 class mysql { |
39 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
39 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
40 var $row = array(); |
40 var $row = array(); |
41 var $rowset = array(); |
41 var $rowset = array(); |
42 var $errhandler; |
42 var $errhandler; |
43 var $dbms_name = 'MySQL'; |
43 var $dbms_name = 'MySQL'; |
44 |
44 |
45 /** |
45 /** |
46 * Get a flat textual list of queries that have been made. |
46 * Get a flat textual list of queries that have been made. |
47 */ |
47 */ |
48 |
48 |
49 function sql_backtrace() |
49 function sql_backtrace() |
50 { |
50 { |
51 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
51 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
52 } |
52 } |
53 |
53 |
54 /** |
54 /** |
55 * Connect to the database, but only if a connection isn't already up. |
55 * Connect to the database, but only if a connection isn't already up. |
56 */ |
56 */ |
57 |
57 |
58 function ensure_connection() |
58 function ensure_connection() |
59 { |
59 { |
60 if(!$this->_conn) |
60 if(!$this->_conn) |
61 { |
61 { |
62 $this->connect(); |
62 $this->connect(); |
63 } |
63 } |
64 } |
64 } |
65 |
65 |
66 /** |
66 /** |
67 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
67 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
68 * @param string Description or location of error; defaults to none |
68 * @param string Description or location of error; defaults to none |
69 */ |
69 */ |
70 |
70 |
71 function _die($t = '') |
71 function _die($t = '') |
72 { |
72 { |
73 if ( defined('ENANO_HEADERS_SENT') ) |
73 if ( defined('ENANO_HEADERS_SENT') ) |
74 ob_clean(); |
74 ob_clean(); |
75 |
75 |
76 $internal_text = $this->get_error($t); |
76 $internal_text = $this->get_error($t); |
77 |
77 |
78 if ( defined('ENANO_CONFIG_FETCHED') ) |
78 if ( defined('ENANO_CONFIG_FETCHED') ) |
79 // config is in, we can show a slightly nicer looking error page |
79 // config is in, we can show a slightly nicer looking error page |
80 die_semicritical('Database error', $internal_text); |
80 die_semicritical('Database error', $internal_text); |
81 else |
81 else |
82 // no config, display using no-DB template engine |
82 // no config, display using no-DB template engine |
83 grinding_halt('Database error', $internal_text); |
83 grinding_halt('Database error', $internal_text); |
84 |
84 |
85 exit; |
85 exit; |
86 } |
86 } |
87 |
87 |
88 /** |
88 /** |
89 * Get the internal text used for a database error message. |
89 * Get the internal text used for a database error message. |
90 * @param string Description or location of error; defaults to none |
90 * @param string Description or location of error; defaults to none |
91 */ |
91 */ |
92 |
92 |
93 function get_error($t = '') |
93 function get_error($t = '') |
94 { |
94 { |
95 @header('HTTP/1.1 500 Internal Server Error'); |
95 @header('HTTP/1.1 500 Internal Server Error'); |
96 |
96 |
97 $bt = $this->latest_query; |
97 $bt = $this->latest_query; |
98 $e = htmlspecialchars($this->sql_error()); |
98 $e = htmlspecialchars($this->sql_error()); |
99 if ( empty($e) ) |
99 if ( empty($e) ) |
100 $e = '<none>'; |
100 $e = '<none>'; |
101 |
101 |
102 global $email; |
102 global $email; |
103 |
103 |
104 // As long as the admin's e-mail is accessible, display it. |
104 // As long as the admin's e-mail is accessible, display it. |
105 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
105 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
106 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
106 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
107 : ''; |
107 : ''; |
108 |
108 |
109 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
109 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
110 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
110 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
111 <p>Description or location of error: $t<br /> |
111 <p>Description or location of error: $t<br /> |
112 Error returned by $this->dbms_name extension: $e</p> |
112 Error returned by $this->dbms_name extension: $e</p> |
113 <p>Most recent SQL query:</p> |
113 <p>Most recent SQL query:</p> |
114 <pre>$bt</pre>"; |
114 <pre>$bt</pre>"; |
115 return $internal_text; |
115 return $internal_text; |
116 } |
116 } |
117 |
117 |
118 /** |
118 /** |
119 * Exit Enano and output a JSON format datbase error. |
119 * Exit Enano and output a JSON format datbase error. |
120 * @param string Description or location of error; defaults to none |
120 * @param string Description or location of error; defaults to none |
121 */ |
121 */ |
122 |
122 |
123 function die_json($loc = false) |
123 function die_json($loc = false) |
124 { |
124 { |
125 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
125 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
126 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
126 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
127 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
127 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
128 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
128 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
129 $loc = str_replace("\n", "\\n", $loc); |
129 $loc = str_replace("\n", "\\n", $loc); |
130 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
130 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
131 die($t); |
131 die($t); |
132 } |
132 } |
133 |
133 |
134 /** |
134 /** |
135 * Connect to the database. |
135 * Connect to the database. |
136 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
136 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
137 * @param string Database server hostname |
137 * @param string Database server hostname |
138 * @param string Database server username |
138 * @param string Database server username |
139 * @param string Database server password |
139 * @param string Database server password |
140 * @param string Name of the database |
140 * @param string Name of the database |
141 * @param int Optional port number to connect over |
141 * @param int Optional port number to connect over |
142 */ |
142 */ |
143 |
143 |
144 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
144 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
145 { |
145 { |
146 if ( !defined('ENANO_SQL_CONSTANTS') ) |
146 if ( !defined('ENANO_SQL_CONSTANTS') ) |
147 { |
147 { |
148 define('ENANO_SQL_CONSTANTS', ''); |
148 define('ENANO_SQL_CONSTANTS', ''); |
149 define('ENANO_DBLAYER', 'MYSQL'); |
149 define('ENANO_DBLAYER', 'MYSQL'); |
150 define('ENANO_SQLFUNC_LOWERCASE', 'lcase'); |
150 define('ENANO_SQLFUNC_LOWERCASE', 'lcase'); |
151 define('ENANO_SQL_MULTISTRING_PRFIX', ''); |
151 define('ENANO_SQL_MULTISTRING_PRFIX', ''); |
152 define('ENANO_SQL_BOOLEAN_TRUE', 'true'); |
152 define('ENANO_SQL_BOOLEAN_TRUE', 'true'); |
153 define('ENANO_SQL_BOOLEAN_FALSE', 'false'); |
153 define('ENANO_SQL_BOOLEAN_FALSE', 'false'); |
154 } |
154 } |
155 |
155 |
156 if ( !$manual_credentials ) |
156 if ( !$manual_credentials ) |
157 { |
157 { |
158 if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') ) |
158 if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') ) |
159 { |
159 { |
160 @include(ENANO_ROOT.'/config.new.php'); |
160 @include(ENANO_ROOT.'/config.new.php'); |
161 } |
161 } |
162 else |
162 else |
163 { |
163 { |
164 @include(ENANO_ROOT.'/config.php'); |
164 @include(ENANO_ROOT.'/config.php'); |
165 } |
165 } |
166 |
166 |
167 if ( isset($crypto_key) ) |
167 if ( isset($crypto_key) ) |
168 unset($crypto_key); // Get this sucker out of memory fast |
168 unset($crypto_key); // Get this sucker out of memory fast |
169 if ( empty($dbport) ) |
169 if ( empty($dbport) ) |
170 $dbport = 3306; |
170 $dbport = 3306; |
171 |
171 |
172 if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') ) |
172 if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') ) |
173 { |
173 { |
174 // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects |
174 // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects |
175 if ( !defined('scriptPath') ) |
175 if ( !defined('scriptPath') ) |
176 { |
176 { |
177 if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) ) |
177 if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) ) |
178 { |
178 { |
179 $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']); |
179 $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']); |
180 } |
180 } |
181 if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) ) |
181 if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) ) |
182 { |
182 { |
183 // user requested http://foo/enano as opposed to http://foo/enano/index.php |
183 // user requested http://foo/enano as opposed to http://foo/enano/index.php |
184 $_SERVER['REQUEST_URI'] .= '/index.php'; |
184 $_SERVER['REQUEST_URI'] .= '/index.php'; |
185 } |
185 } |
186 $sp = dirname($_SERVER['REQUEST_URI']); |
186 $sp = dirname($_SERVER['REQUEST_URI']); |
187 if($sp == '/' || $sp == '\\') $sp = ''; |
187 if($sp == '/' || $sp == '\\') $sp = ''; |
188 define('scriptPath', $sp); |
188 define('scriptPath', $sp); |
189 define('contentPath', "$sp/index.php?title="); |
189 define('contentPath', "$sp/index.php?title="); |
190 } |
190 } |
191 $loc = scriptPath . '/install/index.php'; |
191 $loc = scriptPath . '/install/index.php'; |
192 define('IN_ENANO_INSTALL', 1); |
192 define('IN_ENANO_INSTALL', 1); |
193 $GLOBALS['lang'] = new Language('eng'); |
193 $GLOBALS['lang'] = new Language('eng'); |
194 global $lang; |
194 global $lang; |
195 $lang->load_file('./language/english/core.json'); |
195 $lang->load_file('./language/english/core.json'); |
196 $lang->load_file('./language/english/install.json'); |
196 $lang->load_file('./language/english/install.json'); |
197 // header("Location: $loc"); |
197 // header("Location: $loc"); |
198 redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 0); |
198 redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 0); |
199 exit; |
199 exit; |
200 } |
200 } |
201 } |
201 } |
202 |
202 |
203 if ( !$dbport ) |
203 if ( !$dbport ) |
204 $dbport = 3306; |
204 $dbport = 3306; |
205 |
205 |
206 if ( $dbhost && !empty($dbport) && $dbport != 3306 ) |
206 if ( $dbhost && !empty($dbport) && $dbport != 3306 ) |
207 $dbhost = '127.0.0.1'; |
207 $dbhost = '127.0.0.1'; |
208 |
208 |
209 $host_line = ( preg_match('/^:/', $dbhost) ) ? $dbhost : "{$dbhost}:{$dbport}"; |
209 $host_line = ( preg_match('/^:/', $dbhost) ) ? $dbhost : "{$dbhost}:{$dbport}"; |
210 |
210 |
211 $this->_conn = @mysql_connect($host_line, $dbuser, $dbpasswd); |
211 $this->_conn = @mysql_connect($host_line, $dbuser, $dbpasswd); |
212 unset($dbuser); |
212 unset($dbuser); |
213 unset($dbpasswd); // Security |
213 unset($dbpasswd); // Security |
214 |
214 |
215 if ( !$this->_conn && !$manual_credentials ) |
215 if ( !$this->_conn && !$manual_credentials ) |
216 { |
216 { |
217 grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to MySQL.<br />'.mysql_error().'</p>'); |
217 grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to MySQL.<br />'.mysql_error().'</p>'); |
218 } |
218 } |
219 else if ( !$this->_conn && $manual_credentials ) |
219 else if ( !$this->_conn && $manual_credentials ) |
220 { |
220 { |
221 return false; |
221 return false; |
222 } |
222 } |
223 |
223 |
224 // Reset some variables |
224 // Reset some variables |
225 $this->query_backtrace = array(); |
225 $this->query_backtrace = array(); |
226 $this->query_times = array(); |
226 $this->query_times = array(); |
227 $this->query_sources = array(); |
227 $this->query_sources = array(); |
228 $this->num_queries = 0; |
228 $this->num_queries = 0; |
229 |
229 |
230 $this->debug = ( defined('ENANO_DEBUG') ); |
230 $this->debug = ( defined('ENANO_DEBUG') ); |
231 |
231 |
232 $q = @mysql_select_db($dbname); |
232 $q = @mysql_select_db($dbname); |
233 |
233 |
234 if ( !$q ) |
234 if ( !$q ) |
235 { |
235 { |
236 if ( $manual_credentials ) |
236 if ( $manual_credentials ) |
237 return false; |
237 return false; |
238 $this->_die('The database could not be selected.'); |
238 $this->_die('The database could not be selected.'); |
239 } |
239 } |
240 |
240 |
241 // We're in! |
241 // We're in! |
242 return true; |
242 return true; |
243 } |
243 } |
244 |
244 |
245 /** |
245 /** |
246 * Make a SQL query. |
246 * Make a SQL query. |
247 * @param string Query |
247 * @param string Query |
248 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
248 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
249 * @return resource or false on failure |
249 * @return resource or false on failure |
250 */ |
250 */ |
251 |
251 |
252 function sql_query($q, $log_query = true) |
252 function sql_query($q, $log_query = true) |
253 { |
253 { |
254 if ( $this->debug && function_exists('debug_backtrace') ) |
254 if ( $this->debug && function_exists('debug_backtrace') ) |
255 { |
255 { |
256 $backtrace = @debug_backtrace(); |
256 $backtrace = @debug_backtrace(); |
257 if ( is_array($backtrace) ) |
257 if ( is_array($backtrace) ) |
258 { |
258 { |
259 $bt = $backtrace[0]; |
259 $bt = $backtrace[0]; |
260 if ( isset($backtrace[1]['class']) ) |
260 if ( isset($backtrace[1]['class']) ) |
261 { |
261 { |
262 if ( $backtrace[1]['class'] == 'sessionManager' ) |
262 if ( $backtrace[1]['class'] == 'sessionManager' ) |
263 { |
263 { |
264 $bt = $backtrace[1]; |
264 $bt = $backtrace[1]; |
265 } |
265 } |
266 } |
266 } |
267 $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line']; |
267 $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line']; |
268 } |
268 } |
269 unset($backtrace); |
269 unset($backtrace); |
270 } |
270 } |
271 |
271 |
272 $this->num_queries++; |
272 $this->num_queries++; |
273 if ( $log_query || defined('ENANO_DEBUG') ) |
273 if ( $log_query || defined('ENANO_DEBUG') ) |
274 { |
274 { |
275 $this->query_backtrace[] = $q; |
275 $this->query_backtrace[] = $q; |
276 $this->latest_query = $q; |
276 $this->latest_query = $q; |
277 } |
277 } |
278 // First make sure we have a connection |
278 // First make sure we have a connection |
279 if ( !$this->_conn ) |
279 if ( !$this->_conn ) |
280 { |
280 { |
281 $this->_die('A database connection has not yet been established.'); |
281 $this->_die('A database connection has not yet been established.'); |
282 } |
282 } |
283 // Start the timer |
283 // Start the timer |
284 if ( $log_query || defined('ENANO_DEBUG') ) |
284 if ( $log_query || defined('ENANO_DEBUG') ) |
285 $time_start = microtime_float(); |
285 $time_start = microtime_float(); |
286 // Does this query look malicious? |
286 // Does this query look malicious? |
287 if ( $log_query || defined('ENANO_DEBUG') ) |
287 if ( $log_query || defined('ENANO_DEBUG') ) |
288 { |
288 { |
289 if ( !$this->check_query($q) ) |
289 if ( !$this->check_query($q) ) |
290 { |
290 { |
291 $this->report_query($q); |
291 $this->report_query($q); |
292 $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : ''; |
292 $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : ''; |
293 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p>' . $debug); |
293 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p>' . $debug); |
294 } |
294 } |
295 } |
295 } |
296 |
296 |
297 $r = mysql_query($q, $this->_conn); |
297 $r = mysql_query($q, $this->_conn); |
298 |
298 |
299 if ( $log_query ) |
299 if ( $log_query ) |
300 $this->query_times[$q] = microtime_float() - $time_start; |
300 $this->query_times[$q] = microtime_float() - $time_start; |
301 |
301 |
302 $this->latest_result = $r; |
302 $this->latest_result = $r; |
303 |
303 |
304 return $r; |
304 return $r; |
305 } |
305 } |
306 |
306 |
307 /** |
307 /** |
308 * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results. |
308 * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results. |
309 * @param string Query |
309 * @param string Query |
310 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
310 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
311 * @return resource or false on failure |
311 * @return resource or false on failure |
312 */ |
312 */ |
313 |
313 |
314 function sql_unbuffered_query($q, $log_query = true) |
314 function sql_unbuffered_query($q, $log_query = true) |
315 { |
315 { |
316 $this->num_queries++; |
316 $this->num_queries++; |
317 if ( $log_query || defined('ENANO_DEBUG') ) |
317 if ( $log_query || defined('ENANO_DEBUG') ) |
318 $this->query_backtrace[] = '(UNBUFFERED) ' . $q; |
318 $this->query_backtrace[] = '(UNBUFFERED) ' . $q; |
319 $this->latest_query = $q; |
319 $this->latest_query = $q; |
320 // First make sure we have a connection |
320 // First make sure we have a connection |
321 if ( !$this->_conn ) |
321 if ( !$this->_conn ) |
322 { |
322 { |
323 $this->_die('A database connection has not yet been established.'); |
323 $this->_die('A database connection has not yet been established.'); |
324 } |
324 } |
325 // Does this query look malicious? |
325 // Does this query look malicious? |
326 if ( !$this->check_query($q) ) |
326 if ( !$this->check_query($q) ) |
327 { |
327 { |
328 $this->report_query($q); |
328 $this->report_query($q); |
329 $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : ''; |
329 $debug = ( defined('ENANO_DEBUG') ) ? '<p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>' : ''; |
330 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p>' . $debug); |
330 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p>' . $debug); |
331 } |
331 } |
332 |
332 |
333 $time_start = microtime_float(); |
333 $time_start = microtime_float(); |
334 $r = @mysql_unbuffered_query($q, $this->_conn); |
334 $r = @mysql_unbuffered_query($q, $this->_conn); |
335 $this->query_times[$q] = microtime_float() - $time_start; |
335 $this->query_times[$q] = microtime_float() - $time_start; |
336 $this->latest_result = $r; |
336 $this->latest_result = $r; |
337 return $r; |
337 return $r; |
338 } |
338 } |
339 |
339 |
340 /** |
340 /** |
341 * Performs heuristic analysis on a SQL query to check for known attack patterns. |
341 * Performs heuristic analysis on a SQL query to check for known attack patterns. |
342 * @param string $q the query to check |
342 * @param string $q the query to check |
343 * @return bool true if query passed check, otherwise false |
343 * @return bool true if query passed check, otherwise false |
344 */ |
344 */ |
345 |
345 |
346 function check_query($q, $debug = false) |
346 function check_query($q, $debug = false) |
347 { |
347 { |
348 global $db_sql_parse_time; |
348 global $db_sql_parse_time; |
349 $ts = microtime_float(); |
349 $ts = microtime_float(); |
350 |
350 |
351 // remove properly escaped quotes |
351 // remove properly escaped quotes |
352 $q = str_replace('\\\\', '', $q); |
352 $q = str_replace('\\\\', '', $q); |
353 $q = str_replace(array("\\\"", "\\'"), '', $q); |
353 $q = str_replace(array("\\\"", "\\'"), '', $q); |
354 |
354 |
355 // make sure quotes match |
355 // make sure quotes match |
356 foreach ( array("'", '"') as $quote ) |
356 foreach ( array("'", '"') as $quote ) |
357 { |
357 { |
358 $n_quotes = get_char_count($q, $quote); |
358 $n_quotes = get_char_count($q, $quote); |
359 if ( $n_quotes % 2 == 1 ) |
359 if ( $n_quotes % 2 == 1 ) |
360 { |
360 { |
361 // mismatched quotes |
361 // mismatched quotes |
362 if ( $debug ) echo "Found mismatched quotes in query; parsed:\n$q\n"; |
362 if ( $debug ) echo "Found mismatched quotes in query; parsed:\n$q\n"; |
363 return false; |
363 return false; |
364 } |
364 } |
365 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
365 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
366 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
366 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
367 } |
367 } |
368 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
368 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
369 |
369 |
370 // quotes are now matched out. does this string have a comment marker in it? |
370 // quotes are now matched out. does this string have a comment marker in it? |
371 if ( strstr($q, '--') ) |
371 if ( strstr($q, '--') ) |
372 { |
372 { |
373 return false; |
373 return false; |
374 } |
374 } |
375 |
375 |
376 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
376 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
377 { |
377 { |
378 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
378 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
379 return false; |
379 return false; |
380 } |
380 } |
381 |
381 |
382 $ts = microtime_float() - $ts; |
382 $ts = microtime_float() - $ts; |
383 $db_sql_parse_time += $ts; |
383 $db_sql_parse_time += $ts; |
384 return true; |
384 return true; |
385 } |
385 } |
386 |
386 |
387 /** |
387 /** |
388 * Set the internal result pointer to X |
388 * Set the internal result pointer to X |
389 * @param int $pos The number of the row |
389 * @param int $pos The number of the row |
390 * @param resource $result The MySQL result resource - if not given, the latest cached query is assumed |
390 * @param resource $result The MySQL result resource - if not given, the latest cached query is assumed |
391 * @return true on success, false on failure |
391 * @return true on success, false on failure |
392 */ |
392 */ |
393 |
393 |
394 function sql_data_seek($pos, $result = false) |
394 function sql_data_seek($pos, $result = false) |
395 { |
395 { |
396 if ( !$result ) |
396 if ( !$result ) |
397 $result = $this->latest_result; |
397 $result = $this->latest_result; |
398 if ( !$result ) |
398 if ( !$result ) |
399 return false; |
399 return false; |
400 |
400 |
401 return mysql_data_seek($result, $pos) ? true : false; |
401 return mysql_data_seek($result, $pos) ? true : false; |
402 } |
402 } |
403 |
403 |
404 /** |
404 /** |
405 * Reports a bad query to the admin |
405 * Reports a bad query to the admin |
406 * @param string $query the naughty query |
406 * @param string $query the naughty query |
407 * @access private |
407 * @access private |
408 */ |
408 */ |
409 |
409 |
410 function report_query($query) |
410 function report_query($query) |
411 { |
411 { |
412 global $session; |
412 global $session; |
413 if ( is_object($session) && defined('ENANO_MAINSTREAM') ) |
413 if ( is_object($session) && defined('ENANO_MAINSTREAM') ) |
414 { |
414 { |
415 $username = $session->username; |
415 $username = $session->username; |
416 $user_id = $session->user_id; |
416 $user_id = $session->user_id; |
417 } |
417 } |
418 else |
418 else |
419 { |
419 { |
420 $username = 'Unavailable'; |
420 $username = 'Unavailable'; |
421 $user_id = 1; |
421 $user_id = 1; |
422 } |
422 } |
423 |
423 |
424 $query = $this->escape($query); |
424 $query = $this->escape($query); |
425 $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type, action, time_id, date_string, page_text, author, author_uid, edit_summary) |
425 $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type, action, time_id, date_string, page_text, author, author_uid, edit_summary) |
426 VALUES(\'security\', \'sql_inject\', '.time().', \'\', \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');'); |
426 VALUES(\'security\', \'sql_inject\', '.time().', \'\', \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');'); |
427 } |
427 } |
428 |
428 |
429 /** |
429 /** |
430 * Returns the ID of the row last inserted. |
430 * Returns the ID of the row last inserted. |
431 * @return int |
431 * @return int |
432 */ |
432 */ |
433 |
433 |
434 function insert_id() |
434 function insert_id() |
435 { |
435 { |
436 return @mysql_insert_id(); |
436 return @mysql_insert_id(); |
437 } |
437 } |
438 |
438 |
439 /** |
439 /** |
440 * Fetch one row from the given query as an associative array. |
440 * Fetch one row from the given query as an associative array. |
441 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
441 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
442 * @return array |
442 * @return array |
443 */ |
443 */ |
444 |
444 |
445 function fetchrow($r = false) |
445 function fetchrow($r = false) |
446 { |
446 { |
447 if ( !$this->_conn ) |
447 if ( !$this->_conn ) |
448 return false; |
448 return false; |
449 |
449 |
450 if ( !$r ) |
450 if ( !$r ) |
451 $r = $this->latest_result; |
451 $r = $this->latest_result; |
452 |
452 |
453 if ( !$r ) |
453 if ( !$r ) |
454 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
454 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
455 |
455 |
456 $row = mysql_fetch_assoc($r); |
456 $row = mysql_fetch_assoc($r); |
457 |
457 |
458 return integerize_array($row); |
458 return integerize_array($row); |
459 } |
459 } |
460 |
460 |
461 /** |
461 /** |
462 * Fetch one row from the given query as a numeric array. |
462 * Fetch one row from the given query as a numeric array. |
463 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
463 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
464 * @return array |
464 * @return array |
465 */ |
465 */ |
466 |
466 |
467 function fetchrow_num($r = false) |
467 function fetchrow_num($r = false) |
468 { |
468 { |
469 if ( !$r ) |
469 if ( !$r ) |
470 $r = $this->latest_result; |
470 $r = $this->latest_result; |
471 if ( !$r ) |
471 if ( !$r ) |
472 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
472 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
473 |
473 |
474 $row = mysql_fetch_row($r); |
474 $row = mysql_fetch_row($r); |
475 return integerize_array($row); |
475 return integerize_array($row); |
476 } |
476 } |
477 |
477 |
478 /** |
478 /** |
479 * Get the number of results for a given query. |
479 * Get the number of results for a given query. |
480 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
480 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
481 * @return array |
481 * @return array |
482 */ |
482 */ |
483 |
483 |
484 function numrows($r = false) |
484 function numrows($r = false) |
485 { |
485 { |
486 if ( !$r ) |
486 if ( !$r ) |
487 $r = $this->latest_result; |
487 $r = $this->latest_result; |
488 if ( !$r ) |
488 if ( !$r ) |
489 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
489 $this->_die('$db->fetchrow(): an invalid MySQL resource was passed.'); |
490 |
490 |
491 return mysql_num_rows($r); |
491 return mysql_num_rows($r); |
492 } |
492 } |
493 |
493 |
494 /** |
494 /** |
495 * Escape a string so that it may safely be included in a SQL query. |
495 * Escape a string so that it may safely be included in a SQL query. |
496 * @param string String to escape |
496 * @param string String to escape |
497 * @return string Escaped string |
497 * @return string Escaped string |
498 */ |
498 */ |
499 |
499 |
500 function escape($str) |
500 function escape($str) |
501 { |
501 { |
502 $str = mysql_real_escape_string($str); |
502 $str = mysql_real_escape_string($str); |
503 return $str; |
503 return $str; |
504 } |
504 } |
505 |
505 |
506 /** |
506 /** |
507 * Free the given result from memory. Use this when completely finished with a result resource. |
507 * Free the given result from memory. Use this when completely finished with a result resource. |
508 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
508 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
509 * @return null |
509 * @return null |
510 */ |
510 */ |
511 |
511 |
512 function free_result($result = false) |
512 function free_result($result = false) |
513 { |
513 { |
514 if ( !$result ) |
514 if ( !$result ) |
515 $result = $this->latest_result; |
515 $result = $this->latest_result; |
516 if ( !$result ) |
516 if ( !$result ) |
517 return null; |
517 return null; |
518 |
518 |
519 @mysql_free_result($result); |
519 @mysql_free_result($result); |
520 return null; |
520 return null; |
521 } |
521 } |
522 |
522 |
523 /** |
523 /** |
524 * Returns the number of rows affected. |
524 * Returns the number of rows affected. |
525 * @return int |
525 * @return int |
526 */ |
526 */ |
527 |
527 |
528 function sql_affectedrows() |
528 function sql_affectedrows() |
529 { |
529 { |
530 return mysql_affected_rows($this->_conn); |
530 return mysql_affected_rows($this->_conn); |
531 } |
531 } |
532 |
532 |
533 /** |
533 /** |
534 * Close the database connection |
534 * Close the database connection |
535 */ |
535 */ |
536 |
536 |
537 function close() |
537 function close() |
538 { |
538 { |
539 @mysql_close($this->_conn); |
539 @mysql_close($this->_conn); |
540 unset($this->_conn); |
540 unset($this->_conn); |
541 } |
541 } |
542 |
542 |
543 /** |
543 /** |
544 * Get a list of columns in the given table |
544 * Get a list of columns in the given table |
545 * @param string Table |
545 * @param string Table |
546 * @return array |
546 * @return array |
547 */ |
547 */ |
548 |
548 |
549 function columns_in($table) |
549 function columns_in($table) |
550 { |
550 { |
551 if ( !is_string($table) ) |
551 if ( !is_string($table) ) |
552 return false; |
552 return false; |
553 $q = $this->sql_query("SHOW COLUMNS IN $table;"); |
553 $q = $this->sql_query("SHOW COLUMNS IN $table;"); |
554 if ( !$q ) |
554 if ( !$q ) |
555 $this->_die(); |
555 $this->_die(); |
556 |
556 |
557 $columns = array(); |
557 $columns = array(); |
558 while ( $row = $this->fetchrow_num() ) |
558 while ( $row = $this->fetchrow_num() ) |
559 { |
559 { |
560 $columns[] = $row[0]; |
560 $columns[] = $row[0]; |
561 } |
561 } |
562 return $columns; |
562 return $columns; |
563 } |
563 } |
564 |
564 |
565 /** |
565 /** |
566 * Get the text of the most recent error. |
566 * Get the text of the most recent error. |
567 * @return string |
567 * @return string |
568 */ |
568 */ |
569 |
569 |
570 function sql_error() |
570 function sql_error() |
571 { |
571 { |
572 return mysql_error(); |
572 return mysql_error(); |
573 } |
573 } |
574 |
574 |
575 /** |
575 /** |
576 * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with. |
576 * Generates and outputs a report of all the SQL queries made during execution. Should only be called after everything's over with. |
577 */ |
577 */ |
578 |
578 |
579 function sql_report() |
579 function sql_report() |
580 { |
580 { |
581 global $db, $session, $paths, $template, $plugins; // Common objects |
581 global $db, $session, $paths, $template, $plugins; // Common objects |
582 if ( !$session->get_permissions('mod_misc') ) |
582 if ( !$session->get_permissions('mod_misc') ) |
583 { |
583 { |
584 die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>'); |
584 die_friendly('Access denied', '<p>You are not authorized to generate a SQL backtrace.</p>'); |
585 } |
585 } |
586 // Create copies of variables that may be changed after header is called |
586 // Create copies of variables that may be changed after header is called |
587 $backtrace = $this->query_backtrace; |
587 $backtrace = $this->query_backtrace; |
588 $times = $this->query_times; |
588 $times = $this->query_times; |
589 $template->header(); |
589 $template->header(); |
590 echo '<h3>SQL query log and timetable</h3>'; |
590 echo '<h3>SQL query log and timetable</h3>'; |
591 echo '<div class="tblholder"> |
591 echo '<div class="tblholder"> |
592 <table border="0" cellspacing="1" cellpadding="4">'; |
592 <table border="0" cellspacing="1" cellpadding="4">'; |
593 $i = 0; |
593 $i = 0; |
594 foreach ( $backtrace as $query ) |
594 foreach ( $backtrace as $query ) |
595 { |
595 { |
596 $i++; |
596 $i++; |
597 $unbuffered = false; |
597 $unbuffered = false; |
598 if ( substr($query, 0, 13) == '(UNBUFFERED) ' ) |
598 if ( substr($query, 0, 13) == '(UNBUFFERED) ' ) |
599 { |
599 { |
600 $query = substr($query, 13); |
600 $query = substr($query, 13); |
601 $unbuffered = true; |
601 $unbuffered = true; |
602 } |
602 } |
603 if ( $i == 1 ) |
603 if ( $i == 1 ) |
604 { |
604 { |
605 echo '<tr> |
605 echo '<tr> |
606 <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th> |
606 <th colspan="2">SQL backtrace for a normal page load of ' . htmlspecialchars($paths->cpage['urlname']) . '</th> |
607 </tr>'; |
607 </tr>'; |
608 } |
608 } |
609 else |
609 else |
610 { |
610 { |
611 echo '<tr> |
611 echo '<tr> |
612 <th class="subhead" colspan="2"> </th> |
612 <th class="subhead" colspan="2"> </th> |
613 </tr>'; |
613 </tr>'; |
614 } |
614 } |
615 echo '<tr> |
615 echo '<tr> |
616 <td class="row2">Query:</td> |
616 <td class="row2">Query:</td> |
617 <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td> |
617 <td class="row1"><pre>' . htmlspecialchars($query) . '</pre></td> |
618 </tr> |
618 </tr> |
619 <tr> |
619 <tr> |
620 <td class="row2">Time:</td> |
620 <td class="row2">Time:</td> |
621 <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td> |
621 <td class="row1">' . number_format($this->query_times[$query], 6) . ' seconds</td> |
622 </tr> |
622 </tr> |
623 <tr> |
623 <tr> |
624 <td class="row2">Unbuffered:</td> |
624 <td class="row2">Unbuffered:</td> |
625 <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td> |
625 <td class="row1">' . ( $unbuffered ? 'Yes' : 'No' ) . '</td> |
626 </tr>'; |
626 </tr>'; |
627 if ( isset($this->query_sources[$query]) ) |
627 if ( isset($this->query_sources[$query]) ) |
628 { |
628 { |
629 echo '<tr> |
629 echo '<tr> |
630 <td class="row2">Called from:</td> |
630 <td class="row2">Called from:</td> |
631 <td class="row1">' . $this->query_sources[$query] . '</td> |
631 <td class="row1">' . $this->query_sources[$query] . '</td> |
632 </tr>'; |
632 </tr>'; |
633 } |
633 } |
634 } |
634 } |
635 if ( function_exists('array_sum') ) |
635 if ( function_exists('array_sum') ) |
636 { |
636 { |
637 $query_time_total = array_sum($this->query_times); |
637 $query_time_total = array_sum($this->query_times); |
638 echo '<tr> |
638 echo '<tr> |
639 <th class="subhead" colspan="2"> |
639 <th class="subhead" colspan="2"> |
640 Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds |
640 Total time taken for SQL queries: ' . round( $query_time_total, 6 ) . ' seconds |
641 </th> |
641 </th> |
642 </tr>'; |
642 </tr>'; |
643 } |
643 } |
644 echo ' </table> |
644 echo ' </table> |
645 </div>'; |
645 </div>'; |
646 $template->footer(); |
646 $template->footer(); |
647 } |
647 } |
648 } |
648 } |
649 |
649 |
650 class postgresql |
650 class postgresql |
651 { |
651 { |
652 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
652 var $num_queries, $query_backtrace, $query_times, $query_sources, $latest_result, $latest_query, $_conn, $sql_stack_fields, $sql_stack_values, $debug; |
653 var $row = array(); |
653 var $row = array(); |
654 var $rowset = array(); |
654 var $rowset = array(); |
655 var $errhandler; |
655 var $errhandler; |
656 var $dbms_name = 'PostgreSQL'; |
656 var $dbms_name = 'PostgreSQL'; |
657 |
657 |
658 /** |
658 /** |
659 * Get a flat textual list of queries that have been made. |
659 * Get a flat textual list of queries that have been made. |
660 */ |
660 */ |
661 |
661 |
662 function sql_backtrace() |
662 function sql_backtrace() |
663 { |
663 { |
664 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
664 return implode("\n-------------------------------------------------------------------\n", $this->query_backtrace); |
665 } |
665 } |
666 |
666 |
667 /** |
667 /** |
668 * Connect to the database, but only if a connection isn't already up. |
668 * Connect to the database, but only if a connection isn't already up. |
669 */ |
669 */ |
670 |
670 |
671 function ensure_connection() |
671 function ensure_connection() |
672 { |
672 { |
673 if(!$this->_conn) |
673 if(!$this->_conn) |
674 { |
674 { |
675 $this->connect(); |
675 $this->connect(); |
676 } |
676 } |
677 } |
677 } |
678 |
678 |
679 /** |
679 /** |
680 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
680 * Exit Enano, dumping out a friendly error message indicating a database error on the way out. |
681 * @param string Description or location of error; defaults to none |
681 * @param string Description or location of error; defaults to none |
682 */ |
682 */ |
683 |
683 |
684 function _die($t = '') |
684 function _die($t = '') |
685 { |
685 { |
686 if ( defined('ENANO_HEADERS_SENT') ) |
686 if ( defined('ENANO_HEADERS_SENT') ) |
687 ob_clean(); |
687 ob_clean(); |
688 |
688 |
689 $internal_text = $this->get_error($t); |
689 $internal_text = $this->get_error($t); |
690 |
690 |
691 if ( defined('ENANO_CONFIG_FETCHED') ) |
691 if ( defined('ENANO_CONFIG_FETCHED') ) |
692 // config is in, we can show a slightly nicer looking error page |
692 // config is in, we can show a slightly nicer looking error page |
693 die_semicritical('Database error', $internal_text); |
693 die_semicritical('Database error', $internal_text); |
694 else |
694 else |
695 // no config, display using no-DB template engine |
695 // no config, display using no-DB template engine |
696 grinding_halt('Database error', $internal_text); |
696 grinding_halt('Database error', $internal_text); |
697 |
697 |
698 exit; |
698 exit; |
699 } |
699 } |
700 |
700 |
701 /** |
701 /** |
702 * Get the internal text used for a database error message. |
702 * Get the internal text used for a database error message. |
703 * @param string Description or location of error; defaults to none |
703 * @param string Description or location of error; defaults to none |
704 */ |
704 */ |
705 |
705 |
706 function get_error($t = '') |
706 function get_error($t = '') |
707 { |
707 { |
708 @header('HTTP/1.1 500 Internal Server Error'); |
708 @header('HTTP/1.1 500 Internal Server Error'); |
709 |
709 |
710 $bt = $this->latest_query; |
710 $bt = $this->latest_query; |
711 $e = htmlspecialchars($this->sql_error()); |
711 $e = htmlspecialchars($this->sql_error()); |
712 if ( empty($e) ) |
712 if ( empty($e) ) |
713 $e = '<none>'; |
713 $e = '<none>'; |
714 |
714 |
715 global $email; |
715 global $email; |
716 |
716 |
717 // As long as the admin's e-mail is accessible, display it. |
717 // As long as the admin's e-mail is accessible, display it. |
718 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
718 $email_info = ( defined('ENANO_CONFIG_FETCHED') && is_object($email) ) |
719 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
719 ? ', at <' . $email->jscode() . $email->encryptEmail(getConfig('contact_email')) . '>' |
720 : ''; |
720 : ''; |
721 |
721 |
722 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
722 $internal_text = "<h3>The site was unable to finish serving your request.</h3> |
723 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
723 <p>We apologize for the inconveience, but an error occurred in the Enano database layer. Please report the full text of this page to the administrator of this site{$email_info}.</p> |
724 <p>Description or location of error: $t<br /> |
724 <p>Description or location of error: $t<br /> |
725 Error returned by $this->dbms_name extension: $e</p> |
725 Error returned by $this->dbms_name extension: $e</p> |
726 <p>Most recent SQL query:</p> |
726 <p>Most recent SQL query:</p> |
727 <pre>$bt</pre>"; |
727 <pre>$bt</pre>"; |
728 return $internal_text; |
728 return $internal_text; |
729 } |
729 } |
730 |
730 |
731 /** |
731 /** |
732 * Exit Enano and output a JSON format datbase error. |
732 * Exit Enano and output a JSON format datbase error. |
733 * @param string Description or location of error; defaults to none |
733 * @param string Description or location of error; defaults to none |
734 */ |
734 */ |
735 |
735 |
736 function die_json($loc = false) |
736 function die_json($loc = false) |
737 { |
737 { |
738 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
738 $e = str_replace("\n", "\\n", addslashes(htmlspecialchars($this->sql_error()))); |
739 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
739 $q = str_replace("\n", "\\n", addslashes($this->latest_query)); |
740 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
740 $loc = ( $loc ) ? addslashes("\n\nDescription or location of error: $loc") : ""; |
741 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
741 $loc .= "\n\nPlease report the full text of this error to the administrator of the site. If you believe that this is a bug with the software, please contact support@enanocms.org."; |
742 $loc = str_replace("\n", "\\n", $loc); |
742 $loc = str_replace("\n", "\\n", $loc); |
743 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
743 $t = "{\"mode\":\"error\",\"error\":\"An error occurred during database query.\\nQuery was:\\n $q\\n\\nError returned by {$this->dbms_name}: $e$loc\"}"; |
744 die($t); |
744 die($t); |
745 } |
745 } |
746 |
746 |
747 /** |
747 /** |
748 * Connect to the database. |
748 * Connect to the database. |
749 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
749 * @param bool If true, enables all other parameters. Defaults to false, which emans that you can call this function with no arguments and it will fetch information from the config file. |
750 * @param string Database server hostname |
750 * @param string Database server hostname |
751 * @param string Database server username |
751 * @param string Database server username |
752 * @param string Database server password |
752 * @param string Database server password |
753 * @param string Name of the database |
753 * @param string Name of the database |
754 * @param int Optional port number to connect over |
754 * @param int Optional port number to connect over |
755 */ |
755 */ |
756 |
756 |
757 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
757 function connect($manual_credentials = false, $dbhost = false, $dbuser = false, $dbpasswd = false, $dbname = false, $dbport = false) |
758 { |
758 { |
759 if ( !defined('ENANO_SQL_CONSTANTS') ) |
759 if ( !defined('ENANO_SQL_CONSTANTS') ) |
760 { |
760 { |
761 define('ENANO_SQL_CONSTANTS', ''); |
761 define('ENANO_SQL_CONSTANTS', ''); |
762 define('ENANO_DBLAYER', 'PGSQL'); |
762 define('ENANO_DBLAYER', 'PGSQL'); |
763 define('ENANO_SQLFUNC_LOWERCASE', 'lower'); |
763 define('ENANO_SQLFUNC_LOWERCASE', 'lower'); |
764 define('ENANO_SQL_MULTISTRING_PRFIX', 'E'); |
764 define('ENANO_SQL_MULTISTRING_PRFIX', 'E'); |
765 define('ENANO_SQL_BOOLEAN_TRUE', '1'); |
765 define('ENANO_SQL_BOOLEAN_TRUE', '1'); |
766 define('ENANO_SQL_BOOLEAN_FALSE', '0'); |
766 define('ENANO_SQL_BOOLEAN_FALSE', '0'); |
767 } |
767 } |
768 |
768 |
769 if ( !$manual_credentials ) |
769 if ( !$manual_credentials ) |
770 { |
770 { |
771 if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') ) |
771 if ( defined('IN_ENANO_INSTALL') && !defined('IN_ENANO_UPGRADE') ) |
772 { |
772 { |
773 @include(ENANO_ROOT.'/config.new.php'); |
773 @include(ENANO_ROOT.'/config.new.php'); |
774 } |
774 } |
775 else |
775 else |
776 { |
776 { |
777 @include(ENANO_ROOT.'/config.php'); |
777 @include(ENANO_ROOT.'/config.php'); |
778 } |
778 } |
779 |
779 |
780 if ( isset($crypto_key) ) |
780 if ( isset($crypto_key) ) |
781 unset($crypto_key); // Get this sucker out of memory fast |
781 unset($crypto_key); // Get this sucker out of memory fast |
782 if ( empty($dbport) ) |
782 if ( empty($dbport) ) |
783 $dbport = 5432; |
783 $dbport = 5432; |
784 |
784 |
785 if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') ) |
785 if ( !defined('ENANO_INSTALLED') && !defined('MIDGET_INSTALLED') && !defined('IN_ENANO_INSTALL') ) |
786 { |
786 { |
787 // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects |
787 // scriptPath isn't set yet - we need to autodetect it to avoid infinite redirects |
788 if ( !defined('scriptPath') ) |
788 if ( !defined('scriptPath') ) |
789 { |
789 { |
790 if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) ) |
790 if ( isset($_SERVER['PATH_INFO']) && !preg_match('/index\.php$/', $_SERVER['PATH_INFO']) ) |
791 { |
791 { |
792 $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']); |
792 $_SERVER['REQUEST_URI'] = preg_replace(';' . preg_quote($_SERVER['PATH_INFO']) . '$;', '', $_SERVER['REQUEST_URI']); |
793 } |
793 } |
794 if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) ) |
794 if ( !preg_match('/\.php$/', $_SERVER['REQUEST_URI']) ) |
795 { |
795 { |
796 // user requested http://foo/enano as opposed to http://foo/enano/index.php |
796 // user requested http://foo/enano as opposed to http://foo/enano/index.php |
797 $_SERVER['REQUEST_URI'] .= '/index.php'; |
797 $_SERVER['REQUEST_URI'] .= '/index.php'; |
798 } |
798 } |
799 $sp = dirname($_SERVER['REQUEST_URI']); |
799 $sp = dirname($_SERVER['REQUEST_URI']); |
800 if($sp == '/' || $sp == '\\') $sp = ''; |
800 if($sp == '/' || $sp == '\\') $sp = ''; |
801 define('scriptPath', $sp); |
801 define('scriptPath', $sp); |
802 define('contentPath', "$sp/index.php?title="); |
802 define('contentPath', "$sp/index.php?title="); |
803 } |
803 } |
804 $loc = scriptPath . '/install.php'; |
804 $loc = scriptPath . '/install.php'; |
805 // header("Location: $loc"); |
805 // header("Location: $loc"); |
806 redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3); |
806 redirect($loc, 'Enano not installed', 'We can\'t seem to find an Enano installation (valid config file). You will be transferred to the installation wizard momentarily...', 3); |
807 exit; |
807 exit; |
808 } |
808 } |
809 } |
809 } |
810 |
810 |
811 if ( empty($dbport) ) |
811 if ( empty($dbport) ) |
812 $dbport = 5432; |
812 $dbport = 5432; |
813 |
813 |
814 $this->_conn = @pg_connect("host=$dbhost port=$dbport dbname=$dbname user=$dbuser password=$dbpasswd"); |
814 $this->_conn = @pg_connect("host=$dbhost port=$dbport dbname=$dbname user=$dbuser password=$dbpasswd"); |
815 unset($dbuser); |
815 unset($dbuser); |
816 unset($dbpasswd); // Security |
816 unset($dbpasswd); // Security |
817 |
817 |
818 if ( !$this->_conn && !$manual_credentials ) |
818 if ( !$this->_conn && !$manual_credentials ) |
819 { |
819 { |
820 grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>'); |
820 grinding_halt('Enano is having a problem', '<p>Error: couldn\'t connect to PostgreSQL.<br />'.pg_last_error().'</p>'); |
821 } |
821 } |
822 else if ( !$this->_conn && $manual_credentials ) |
822 else if ( !$this->_conn && $manual_credentials ) |
823 { |
823 { |
824 return false; |
824 return false; |
825 } |
825 } |
826 |
826 |
827 // Reset some variables |
827 // Reset some variables |
828 $this->query_backtrace = array(); |
828 $this->query_backtrace = array(); |
829 $this->query_times = array(); |
829 $this->query_times = array(); |
830 $this->query_sources = array(); |
830 $this->query_sources = array(); |
831 $this->num_queries = 0; |
831 $this->num_queries = 0; |
832 |
832 |
833 $this->debug = ( defined('ENANO_DEBUG') ); |
833 $this->debug = ( defined('ENANO_DEBUG') ); |
834 |
834 |
835 // We're in! |
835 // We're in! |
836 return true; |
836 return true; |
837 } |
837 } |
838 |
838 |
839 /** |
839 /** |
840 * Make a SQL query. |
840 * Make a SQL query. |
841 * @param string Query |
841 * @param string Query |
842 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
842 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
843 * @return resource or false on failure |
843 * @return resource or false on failure |
844 */ |
844 */ |
845 |
845 |
846 function sql_query($q) |
846 function sql_query($q) |
847 { |
847 { |
848 if ( $this->debug && function_exists('debug_backtrace') ) |
848 if ( $this->debug && function_exists('debug_backtrace') ) |
849 { |
849 { |
850 $backtrace = @debug_backtrace(); |
850 $backtrace = @debug_backtrace(); |
851 if ( is_array($backtrace) ) |
851 if ( is_array($backtrace) ) |
852 { |
852 { |
853 $bt = $backtrace[0]; |
853 $bt = $backtrace[0]; |
854 if ( isset($backtrace[1]['class']) ) |
854 if ( isset($backtrace[1]['class']) ) |
855 { |
855 { |
856 if ( $backtrace[1]['class'] == 'sessionManager' ) |
856 if ( $backtrace[1]['class'] == 'sessionManager' ) |
857 { |
857 { |
858 $bt = $backtrace[1]; |
858 $bt = $backtrace[1]; |
859 } |
859 } |
860 } |
860 } |
861 $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line']; |
861 $this->query_sources[$q] = substr($bt['file'], strlen(ENANO_ROOT) + 1) . ', line ' . $bt['line']; |
862 } |
862 } |
863 unset($backtrace); |
863 unset($backtrace); |
864 } |
864 } |
865 |
865 |
866 $this->num_queries++; |
866 $this->num_queries++; |
867 $this->query_backtrace[] = $q; |
867 $this->query_backtrace[] = $q; |
868 $this->latest_query = $q; |
868 $this->latest_query = $q; |
869 // First make sure we have a connection |
869 // First make sure we have a connection |
870 if ( !$this->_conn ) |
870 if ( !$this->_conn ) |
871 { |
871 { |
872 $this->_die('A database connection has not yet been established.'); |
872 $this->_die('A database connection has not yet been established.'); |
873 } |
873 } |
874 // Does this query look malicious? |
874 // Does this query look malicious? |
875 if ( !$this->check_query($q) ) |
875 if ( !$this->check_query($q) ) |
876 { |
876 { |
877 $this->report_query($q); |
877 $this->report_query($q); |
878 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>'); |
878 grinding_halt('SQL Injection attempt', '<p>Enano has caught and prevented an SQL injection attempt. Your IP address has been recorded and the administrator has been notified.</p><p>Query was:</p><pre>'.htmlspecialchars($q).'</pre>'); |
879 } |
879 } |
880 |
880 |
881 $time_start = microtime_float(); |
881 $time_start = microtime_float(); |
882 $r = @pg_query($this->_conn, $q); |
882 $r = @pg_query($this->_conn, $q); |
883 $this->query_times[$q] = microtime_float() - $time_start; |
883 $this->query_times[$q] = microtime_float() - $time_start; |
884 $this->latest_result = $r; |
884 $this->latest_result = $r; |
885 return $r; |
885 return $r; |
886 } |
886 } |
887 |
887 |
888 /** |
888 /** |
889 * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results. |
889 * Make a SQL query, but do not have PHP buffer all the results. Useful for queries that are expected to return a huge number of results. |
890 * @param string Query |
890 * @param string Query |
891 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
891 * @param bool If false, skips all checks and logging stages. If you're doing a ton of queries, set this to true; in all other cases, leave at the default of false. |
892 * @return resource or false on failure |
892 * @return resource or false on failure |
893 */ |
893 */ |
894 |
894 |
895 function sql_unbuffered_query($q) |
895 function sql_unbuffered_query($q) |
896 { |
896 { |
897 return $this->sql_query($q); |
897 return $this->sql_query($q); |
898 } |
898 } |
899 |
899 |
900 /** |
900 /** |
901 * Checks a SQL query for possible signs of injection attempts |
901 * Checks a SQL query for possible signs of injection attempts |
902 * @param string $q the query to check |
902 * @param string $q the query to check |
903 * @return bool true if query passed check, otherwise false |
903 * @return bool true if query passed check, otherwise false |
904 */ |
904 */ |
905 |
905 |
906 function check_query($q, $debug = false) |
906 function check_query($q, $debug = false) |
907 { |
907 { |
908 global $db_sql_parse_time; |
908 global $db_sql_parse_time; |
909 $ts = microtime_float(); |
909 $ts = microtime_float(); |
910 |
910 |
911 // remove properly escaped quotes |
911 // remove properly escaped quotes |
912 $q = str_replace(array("\\\"", "\\'"), '', $q); |
912 $q = str_replace(array("\\\"", "\\'"), '', $q); |
913 |
913 |
914 // make sure quotes match |
914 // make sure quotes match |
915 foreach ( array("'", '"') as $quote ) |
915 foreach ( array("'", '"') as $quote ) |
916 { |
916 { |
917 if ( get_char_count($q, $quote) % 2 == 1 ) |
917 if ( get_char_count($q, $quote) % 2 == 1 ) |
918 { |
918 { |
919 // mismatched quotes |
919 // mismatched quotes |
920 return false; |
920 return false; |
921 } |
921 } |
922 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
922 // this quote is now confirmed to be matching; we can safely move all quoted strings out and replace with a token |
923 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
923 $q = preg_replace("/$quote(.*?)$quote/s", 'SAFE_QUOTE', $q); |
924 } |
924 } |
925 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
925 $q = preg_replace("/(SAFE_QUOTE)+/", 'SAFE_QUOTE', $q); |
926 |
926 |
927 // quotes are now matched out. does this string have a comment marker in it? |
927 // quotes are now matched out. does this string have a comment marker in it? |
928 if ( strstr($q, '--') ) |
928 if ( strstr($q, '--') ) |
929 { |
929 { |
930 return false; |
930 return false; |
931 } |
931 } |
932 |
932 |
933 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
933 if ( preg_match('/[\s]+(SAFE_QUOTE|[\S]+)=\\1($|[\s]+)/', $q, $match) ) |
934 { |
934 { |
935 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
935 if ( $debug ) echo 'Found always-true test in query, injection attempt caught, match:<br />' . '<pre>' . print_r($match, true) . '</pre>'; |
936 return false; |
936 return false; |
937 } |
937 } |
938 |
938 |
939 $ts = microtime_float() - $ts; |
939 $ts = microtime_float() - $ts; |
940 $db_sql_parse_time += $ts; |
940 $db_sql_parse_time += $ts; |
941 return true; |
941 return true; |
942 } |
942 } |
943 |
943 |
944 /** |
944 /** |
945 * Set the internal result pointer to X |
945 * Set the internal result pointer to X |
946 * @param int $pos The number of the row |
946 * @param int $pos The number of the row |
947 * @param resource $result The PostgreSQL result resource - if not given, the latest cached query is assumed |
947 * @param resource $result The PostgreSQL result resource - if not given, the latest cached query is assumed |
948 * @return true on success, false on failure |
948 * @return true on success, false on failure |
949 */ |
949 */ |
950 |
950 |
951 function sql_data_seek($pos, $result = false) |
951 function sql_data_seek($pos, $result = false) |
952 { |
952 { |
953 if ( !$result ) |
953 if ( !$result ) |
954 $result = $this->latest_result; |
954 $result = $this->latest_result; |
955 if ( !$result ) |
955 if ( !$result ) |
956 return false; |
956 return false; |
957 |
957 |
958 return pg_result_seek($result, $pos) ? true : false; |
958 return pg_result_seek($result, $pos) ? true : false; |
959 } |
959 } |
960 |
960 |
961 /** |
961 /** |
962 * Reports a bad query to the admin |
962 * Reports a bad query to the admin |
963 * @param string $query the naughty query |
963 * @param string $query the naughty query |
964 * @access private |
964 * @access private |
965 */ |
965 */ |
966 |
966 |
967 function report_query($query) |
967 function report_query($query) |
968 { |
968 { |
969 global $session; |
969 global $session; |
970 if ( is_object($session) && defined('ENANO_MAINSTREAM') ) |
970 if ( is_object($session) && defined('ENANO_MAINSTREAM') ) |
971 { |
971 { |
972 $username = $session->username; |
972 $username = $session->username; |
973 $user_id = $session->user_id; |
973 $user_id = $session->user_id; |
974 } |
974 } |
975 else |
975 else |
976 { |
976 { |
977 $username = 'Unavailable'; |
977 $username = 'Unavailable'; |
978 $user_id = 1; |
978 $user_id = 1; |
979 } |
979 } |
980 |
980 |
981 $query = $this->escape($query); |
981 $query = $this->escape($query); |
982 $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type, action, time_id, date_string, page_text, author, author_uid, edit_summary) |
982 $q = $this->sql_query('INSERT INTO '.table_prefix.'logs(log_type, action, time_id, date_string, page_text, author, author_uid, edit_summary) |
983 VALUES(\'security\', \'sql_inject\', '.time().', \'\', \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');'); |
983 VALUES(\'security\', \'sql_inject\', '.time().', \'\', \''.$query.'\', \''.$username.'\', ' . $user_id . ', \''.$_SERVER['REMOTE_ADDR'].'\');'); |
984 } |
984 } |
985 |
985 |
986 /** |
986 /** |
987 * Returns the ID of the row last inserted. |
987 * Returns the ID of the row last inserted. |
988 * @return int |
988 * @return int |
989 */ |
989 */ |
990 |
990 |
991 function insert_id() |
991 function insert_id() |
992 { |
992 { |
993 // list of primary keys in Enano tables |
993 // list of primary keys in Enano tables |
994 // this is a bit hackish, but not much choice. |
994 // this is a bit hackish, but not much choice. |
995 static $primary_keys = false; |
995 static $primary_keys = false; |
996 if ( !is_array($primary_keys) ) |
996 if ( !is_array($primary_keys) ) |
997 { |
997 { |
998 $primary_keys = array( |
998 $primary_keys = array( |
999 table_prefix . 'comments' => 'comment_id', |
999 table_prefix . 'comments' => 'comment_id', |
1000 table_prefix . 'logs' => 'log_id', |
1000 table_prefix . 'logs' => 'log_id', |
1001 table_prefix . 'users' => 'user_id', |
1001 table_prefix . 'users' => 'user_id', |
1002 table_prefix . 'banlist' => 'ban_id', |
1002 table_prefix . 'banlist' => 'ban_id', |
1003 table_prefix . 'files' => 'file_id', |
1003 table_prefix . 'files' => 'file_id', |
1004 table_prefix . 'buddies' => 'buddy_id', |
1004 table_prefix . 'buddies' => 'buddy_id', |
1005 table_prefix . 'privmsgs' => 'message_id', |
1005 table_prefix . 'privmsgs' => 'message_id', |
1006 table_prefix . 'sidebar' => 'item_id', |
1006 table_prefix . 'sidebar' => 'item_id', |
1007 table_prefix . 'hits' => 'hit_id', |
1007 table_prefix . 'hits' => 'hit_id', |
1008 table_prefix . 'groups' => 'group_id', |
1008 table_prefix . 'groups' => 'group_id', |
1009 table_prefix . 'group_members' => 'member_id', |
1009 table_prefix . 'group_members' => 'member_id', |
1010 table_prefix . 'acl' => 'rule_id', |
1010 table_prefix . 'acl' => 'rule_id', |
1011 table_prefix . 'page_groups' => 'pg_id', |
1011 table_prefix . 'page_groups' => 'pg_id', |
1012 table_prefix . 'page_group_members' => 'pg_member_id', |
1012 table_prefix . 'page_group_members' => 'pg_member_id', |
1013 table_prefix . 'tags' => 'tag_id', |
1013 table_prefix . 'tags' => 'tag_id', |
1014 table_prefix . 'lockout' => 'id', |
1014 table_prefix . 'lockout' => 'id', |
1015 table_prefix . 'language' => 'lang_id', |
1015 table_prefix . 'language' => 'lang_id', |
1016 table_prefix . 'language_strings' => 'string_id', |
1016 table_prefix . 'language_strings' => 'string_id', |
1017 table_prefix . 'ranks' => 'rank_id', |
1017 table_prefix . 'ranks' => 'rank_id', |
1018 table_prefix . 'captcha' => 'code_id', |
1018 table_prefix . 'captcha' => 'code_id', |
1019 table_prefix . 'diffiehellman' => 'key_id', |
1019 table_prefix . 'diffiehellman' => 'key_id', |
1020 table_prefix . 'plugins' => 'plugin_id' |
1020 table_prefix . 'plugins' => 'plugin_id' |
1021 ); |
1021 ); |
1022 // allow plugins to patch this if needed |
1022 // allow plugins to patch this if needed |
1023 global $plugins; |
1023 global $plugins; |
1024 $code = $plugins->setHook('pgsql_set_serial_list'); |
1024 $code = $plugins->setHook('pgsql_set_serial_list'); |
1025 foreach ( $code as $cmd ) |
1025 foreach ( $code as $cmd ) |
1026 { |
1026 { |
1027 eval($cmd); |
1027 eval($cmd); |
1028 } |
1028 } |
1029 } |
1029 } |
1030 $last_was_insert = preg_match('/^INSERT INTO ([a-z0-9_]+)/i', $this->latest_query, $match); |
1030 $last_was_insert = preg_match('/^INSERT INTO ([a-z0-9_]+)/i', $this->latest_query, $match); |
1031 if ( $last_was_insert ) |
1031 if ( $last_was_insert ) |
1032 { |
1032 { |
1033 // trick based on PunBB's PostgreSQL driver |
1033 // trick based on PunBB's PostgreSQL driver |
1034 $table =& $match[1]; |
1034 $table =& $match[1]; |
1035 if ( isset($primary_keys[$table]) ) |
1035 if ( isset($primary_keys[$table]) ) |
1036 { |
1036 { |
1037 $primary_key = "{$table}_{$primary_keys[$table]}_seq"; |
1037 $primary_key = "{$table}_{$primary_keys[$table]}_seq"; |
1038 $q = pg_query("SELECT CURRVAL('$primary_key');"); |
1038 $q = pg_query("SELECT CURRVAL('$primary_key');"); |
1039 return ( $q ) ? intval(@pg_fetch_result($q, 0)) : false; |
1039 return ( $q ) ? intval(@pg_fetch_result($q, 0)) : false; |
1040 } |
1040 } |
1041 } |
1041 } |
1042 return false; |
1042 return false; |
1043 } |
1043 } |
1044 |
1044 |
1045 /** |
1045 /** |
1046 * Fetch one row from the given query as an associative array. |
1046 * Fetch one row from the given query as an associative array. |
1047 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1047 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1048 * @return array |
1048 * @return array |
1049 */ |
1049 */ |
1050 |
1050 |
1051 function fetchrow($r = false) |
1051 function fetchrow($r = false) |
1052 { |
1052 { |
1053 if ( !$this->_conn ) |
1053 if ( !$this->_conn ) |
1054 return false; |
1054 return false; |
1055 if ( !$r ) |
1055 if ( !$r ) |
1056 $r = $this->latest_result; |
1056 $r = $this->latest_result; |
1057 if ( !$r ) |
1057 if ( !$r ) |
1058 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1058 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1059 |
1059 |
1060 $row = pg_fetch_assoc($r); |
1060 $row = pg_fetch_assoc($r); |
1061 return integerize_array($row); |
1061 return integerize_array($row); |
1062 } |
1062 } |
1063 |
1063 |
1064 /** |
1064 /** |
1065 * Fetch one row from the given query as a numeric array. |
1065 * Fetch one row from the given query as a numeric array. |
1066 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1066 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1067 * @return array |
1067 * @return array |
1068 */ |
1068 */ |
1069 |
1069 |
1070 function fetchrow_num($r = false) |
1070 function fetchrow_num($r = false) |
1071 { |
1071 { |
1072 if ( !$r ) |
1072 if ( !$r ) |
1073 $r = $this->latest_result; |
1073 $r = $this->latest_result; |
1074 if ( !$r ) |
1074 if ( !$r ) |
1075 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1075 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1076 |
1076 |
1077 $row = pg_fetch_row($r); |
1077 $row = pg_fetch_row($r); |
1078 return integerize_array($row); |
1078 return integerize_array($row); |
1079 } |
1079 } |
1080 |
1080 |
1081 /** |
1081 /** |
1082 * Get the number of results for a given query. |
1082 * Get the number of results for a given query. |
1083 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1083 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1084 * @return array |
1084 * @return array |
1085 */ |
1085 */ |
1086 |
1086 |
1087 function numrows($r = false) |
1087 function numrows($r = false) |
1088 { |
1088 { |
1089 if ( !$r ) |
1089 if ( !$r ) |
1090 $r = $this->latest_result; |
1090 $r = $this->latest_result; |
1091 if ( !$r ) |
1091 if ( !$r ) |
1092 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1092 $this->_die('$db->fetchrow(): an invalid ' . $this->dbms_name . ' resource was passed.'); |
1093 |
1093 |
1094 $n = pg_num_rows($r); |
1094 $n = pg_num_rows($r); |
1095 return $n; |
1095 return $n; |
1096 } |
1096 } |
1097 |
1097 |
1098 /** |
1098 /** |
1099 * Escape a string so that it may safely be included in a SQL query. |
1099 * Escape a string so that it may safely be included in a SQL query. |
1100 * @param string String to escape |
1100 * @param string String to escape |
1101 * @return string Escaped string |
1101 * @return string Escaped string |
1102 */ |
1102 */ |
1103 |
1103 |
1104 function escape($str) |
1104 function escape($str) |
1105 { |
1105 { |
1106 $str = pg_escape_string($this->_conn, $str); |
1106 $str = pg_escape_string($this->_conn, $str); |
1107 return $str; |
1107 return $str; |
1108 } |
1108 } |
1109 |
1109 |
1110 /** |
1110 /** |
1111 * Free the given result from memory. Use this when completely finished with a result resource. |
1111 * Free the given result from memory. Use this when completely finished with a result resource. |
1112 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1112 * @param resource The resource returned from sql_query; if this isn't provided, the last result resource is used. |
1113 * @return null |
1113 * @return null |
1114 */ |
1114 */ |
1115 |
1115 |
1116 function free_result($result = false) |
1116 function free_result($result = false) |
1117 { |
1117 { |
1118 if ( !$result ) |
1118 if ( !$result ) |
1119 $result = $this->latest_result; |
1119 $result = $this->latest_result; |
1120 |
1120 |
1121 if ( !$result ) |
1121 if ( !$result ) |
1122 return null; |
1122 return null; |
1123 |
1123 |
1124 @pg_free_result($result); |
1124 @pg_free_result($result); |
1125 return null; |
1125 return null; |
1126 } |
1126 } |
1127 |
1127 |
1128 /** |
1128 /** |
1129 * Close the database connection |
1129 * Close the database connection |
1130 */ |
1130 */ |
1131 |
1131 |
1132 function close() |
1132 function close() |
1133 { |
1133 { |
1134 @pg_close($this->_conn); |
1134 @pg_close($this->_conn); |
1135 unset($this->_conn); |
1135 unset($this->_conn); |
1136 } |
1136 } |
1137 |
1137 |
1138 /** |
1138 /** |
1139 * Get a list of columns in the given table |
1139 * Get a list of columns in the given table |
1140 * @param string Table |
1140 * @param string Table |
1141 * @return array |
1141 * @return array |
1142 */ |
1142 */ |
1143 |
1143 |
1144 function columns_in($table) |
1144 function columns_in($table) |
1145 { |
1145 { |
1146 if ( !is_string($table) ) |
1146 if ( !is_string($table) ) |
1147 return false; |
1147 return false; |
1148 $q = $this->sql_query("SELECT * FROM $table LIMIT 1 OFFSET 0;"); |
1148 $q = $this->sql_query("SELECT * FROM $table LIMIT 1 OFFSET 0;"); |
1149 if ( !$q ) |
1149 if ( !$q ) |
1150 $this->_die(); |
1150 $this->_die(); |
1151 if ( $this->numrows() < 1 ) |
1151 if ( $this->numrows() < 1 ) |
1152 { |
1152 { |
1153 // FIXME: Have another way to do this if the table is empty |
1153 // FIXME: Have another way to do this if the table is empty |
1154 return false; |
1154 return false; |
1155 } |
1155 } |
1156 |
1156 |
1157 $row = $this->fetchrow(); |
1157 $row = $this->fetchrow(); |
1158 $this->free_result(); |
1158 $this->free_result(); |
1159 return array_keys($row); |
1159 return array_keys($row); |
1160 } |
1160 } |
1161 |
1161 |
1162 function sql_error() |
1162 function sql_error() |
1163 { |
1163 { |
1164 if ( $this->_conn ) |
1164 if ( $this->_conn ) |
1165 { |
1165 { |
1166 return pg_last_error(); |
1166 return pg_last_error(); |