source: trunk/common/db/db.php @ 1184

Revision 1174, 7.5 KB checked in by adrian.budau, 3 months ago (diff)

Bunch of security fixes.

Login and register pages go through https
Updated recaptcha library

Implmented an anti-spam token system with price per actions and
token regeneration.

There is a maximum amount of tokens per IP.
Actions like register and login cost tokens.
When there are not enough tokens a captcha is requested.
The tokens regenerate at a constant rate.

Example -> This is how the captcha is requested at this moment for register/login:

You can login/logout as manytimes as you want. If you do 3 bad login attempts a captcha will appear and will be requested until you login correctly.
You always need a captcha for registering and after that after only one bad login attempt a captcha will be requested.
You can logout and login a different account without the need of a captcha(there is no way to use this as a brute-force entrance).

Token system description below:

You can communicate with the tokens system with the functions
get_tokens to get current tokens
check_captcha_for_tokens to check for captcha submits and their correctness thus adding an amount of tokens, this function also returns the error of the captcha(it can be forced to search for all errors)
pay_tokens which pays a certain amount of tokens or receives(if used with a negative value), it returns true or false weather it can pay or not(has enough)
save_tokens(which pushes the tokens to the mysql db)

Review URL: http://reviewboard.infoarena.ro/r/188/

  • Property svn:eol-style set to native
Line 
1<?php
2// This module contains various database-related functions and routines.
3
4// To avoid some name clashes with SMF, we have an alternate database API
5// when working inside SMF.
6if (defined("IA_FROM_SMF")) {
7    require_once(IA_ROOT_DIR."common/db/db_smf_mysql.php");
8}
9else {
10    require_once(IA_ROOT_DIR."common/db/db_mysql.php");
11}
12
13// Executes query, fetches the all result rows
14function db_fetch_all($query) {
15    $result = db_query($query, true);
16    if ($result) {
17        $buffer = array();
18        while ($row = db_next_row($result)) {
19            $buffer[] = $row;
20        }
21        db_free($result);
22        return $buffer;
23    } else {
24        return null;
25    }
26}
27
28// tells whether given string is a valid datetime value
29// see parse_datetime()
30function is_db_date($string) {
31    $timestamp = db_date_parse($string);
32    return (false !== $timestamp);
33}
34
35// parse value of a datetime parameter in SQL format.
36// i.e.: 2006-11-27 23:59:59
37//
38// returns unix timestamp or FALSE upon error
39// NOTE: We cannot use strptime() since it doesn't work on windows
40function db_date_parse($string) {
41    // maybe it's a date&time
42    $ret = preg_match('/^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})$/',
43                      $string, $matches);
44    if ($ret) {
45        return mktime($matches[4], $matches[5], $matches[6],
46                      $matches[2], $matches[3], $matches[1]);
47    }
48
49    // probably just a date
50    $ret = preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})$/',
51                      $string, $matches);
52    if ($ret) {
53        return mktime(12, 0, 0, $matches[2], $matches[3], $matches[1]);
54    }
55
56    // unknown date format
57    return false;
58}
59
60// formats unix timestamp as a datetime parameter value, suitable for SQL.
61// i.e.: 2006-11-27 23:59:59
62//
63// NOTE: prefer db_date_format() to NOW().
64// NOW returns the current time in the database server's timezone.
65//
66// All times in the database are UTC!!!
67function db_date_format($timestamp = null) {
68    if ($timestamp === null) {
69        $res = strftime('%Y-%m-%d %T');
70    } else {
71        $res = strftime('%Y-%m-%d %T', $timestamp);
72    }
73
74    return $res;
75}
76
77// Executes SQL query and returns value of the first column in the first
78// result row.
79// When query yields no results, it returns $default_value
80//
81// WARNING: This function asserts there is at most 1 result row and 1 column.
82function db_query_value($query, $default_value = null) {
83    global $dbLink;
84
85    $rows = db_fetch_all($query);
86
87    if (count($rows) == 0) {
88        return $default_value;
89    }
90
91    // failsafe
92    log_assert(1 == count($rows), 'db_query_value() expects 1 row at most');
93    $row = array_values($rows[0]);
94    log_assert(1 == count($row), 'db_query_value() expects 1 column at most');
95
96    return $row[0];
97}
98
99// Executes SQL INSERT statement (wrapper for db_query)
100// Returns last SQL insert id
101//
102// Arguments:
103// $table   SQL table name
104// $dict    dictionary of fields to insert
105//
106// Example:
107// $user = array(
108//      'full_name' => 'Gigi Kent',
109//      'username' => 'gigikent'
110// );
111// db_insert('user', $user);
112//
113// will execute:
114// INSERT INTO `user` (`full_name`, `username`)
115// VALUES ('Gigi Kent', 'gigikent')
116//
117// Returns last insert-ed primary key value
118function db_insert($table, $dict) {
119    global $dbLink;
120
121    foreach ($dict as $k => $v) {
122        if (is_null($v)) {
123            unset($dict[$k]);
124        }
125    }
126
127    $query = "INSERT INTO `{$table}` (`";
128    $query .= join('`, `', array_keys($dict));
129    $query .= "`) VALUES (";
130    $query .= join(", ", array_map('db_quote', array_values($dict)));
131    $query .= ")";
132
133    db_query($query);
134
135    return db_insert_id();
136}
137
138// Executes SQL UPDATE statement (wrapper for db_query)
139// Returns number of affected rows
140//
141// PHP null values are expanded to SQL NULL
142//
143// Arguments:
144// $table   SQL table name
145// $dict    dictionary of fields to update
146// $where   pre-escaped WHERE clause to be inserted inline
147//
148// Example:
149// $user = array(
150//      'full_name' => 'Gigi Kent',
151//      'password' => 'xxx'
152// );
153// db_update('user', $user, "username='wickedman'");
154//
155// will execute:
156// UPDATE `user`
157// SET `full_name` = 'Gigi Kent', `password` = 'xxx'
158// WHERE username='wickedman'
159function db_update($table, $dict, $where = null) {
160    global $dbLink;
161
162    // fail safe
163    log_assert(1 <= count($dict), 'db_update() called with empty $dict');
164
165    // build query
166    $query = "UPDATE `{$table}`\nSET ";
167    $first = true;
168    foreach ($dict as $k => $v) {
169        //  - comma
170        if (!$first) {
171            $query .= ', ';
172        }
173        $first = false;
174
175        //  - field-value pair
176        if (is_null($v)) {
177            $v = 'NULL';
178        }
179        else {
180            $v = db_quote($v);
181        }
182        $query .= "`{$k}` = {$v}";
183    }
184    //  - WHERE clause
185    if (!is_null($where)) {
186        $query .= " WHERE ".$where;
187    }
188
189    db_query($query);
190
191    return db_affected_rows();
192}
193
194// FIXME: obliterate
195
196/**
197 * News
198 * This is for the special "news" controller.
199 */
200function news_get_range($start, $range, $prefix = null) {
201    $query = sprintf("SELECT
202                        *
203                      FROM ia_textblock
204                      WHERE `name` LIKE 'stiri/%s%%'
205                      ORDER BY ia_textblock.`creation_timestamp` DESC
206                      LIMIT %s, %s",
207                     db_escape($prefix), db_quote((int)$start), db_quote((int)$range));
208    return db_fetch_all($query);
209}
210
211function news_count($prefix = null) {
212    $query = sprintf("SELECT COUNT(*) AS `cnt`
213                      FROM ia_textblock
214                      WHERE `name` LIKE 'stiri/%s%%'",
215                      db_escape($prefix));
216    $tmp = db_fetch($query);
217    return $tmp['cnt'];
218}
219
220// Quotes a variable so it can be safely placed inside an SQL query.
221// This will surround strings with quotes and leave integers alone.
222//
223// NOTE: this function is always safe to concat inline.
224function db_quote($arg) {
225    if (is_null($arg)) {
226        return 'NULL';
227    } else if (is_string($arg)) {
228        return "'" . db_escape($arg) . "'";
229    } else if (is_numeric($arg)) {
230        // FIXME: is_numeric guarantees mysql safety?
231        // FIXME: does it also guarantee that mysql can parse it?
232        return (string)$arg;
233        //return "'" . db_escape((string)$arg) . "'";
234    } else if (is_bool($arg)) {
235        if ($arg) {
236            return 'TRUE';
237        } else {
238            return 'FALSE';
239        }
240    } else if (is_array($arg) || is_object($arg) || is_resource($arg) || is_callable($arg)) {
241        log_error("Can't db_quote complex objects");
242        return (string)$arg;
243    } else {
244        log_error("Unknown object type?");
245    }
246}
247
248// Escape an array of strings.
249function db_escape_array($array) {
250    $ret = implode(',', array_map('db_quote', $array));
251
252    return $ret;
253}
254
255// Executes query, fetches only FIRST result row
256function db_fetch($query) {
257    $result = db_query($query, true);
258    if ($result) {
259        $row = db_next_row($result);
260        if ($row === false) {
261            db_free($result);
262            return null;
263        }
264        db_free($result);
265        return $row;
266    } else {
267        return null;
268    }
269}
270
271// FIXME: This shouldn't be here. Move it in common/db/task.php or
272// common/db/round.php
273function db_get_task_filter_clause($filter, $table_alias) {
274    if ($filter == IA_TLF_SOLVED) {
275        return "{$table_alias}.score = 100";
276    } else if ($filter == IA_TLF_TRIED) {
277        return "{$table_alias}.score < 100";
278    } else if ($filter == IA_TLF_UNSOLVED) {
279        return "{$table_alias}.score is null";
280    } else {
281        return '1';
282    }
283}
284
285?>
Note: See TracBrowser for help on using the repository browser.