source: branches/1.1dev/lib/AuthSQL.inc.php @ 204

Last change on this file since 204 was 204, checked in by scdev, 18 years ago

Q - added match_remote_ip_exempt_usernames functionality to 1.1dev/lib/AuthSQL.inc.php

File size: 34.4 KB
Line 
1<?php
2/**
3 * The Auth_File:: class provides a SQL implementation for authentication.
4 *
5 * @author  Quinn Comendant <quinn@strangecode.com>
6 * @inspiration  Horde's Auth class <www.horde.org>
7 * @version 1.0
8 */
9class AuthSQL {
10
11    var $_params = array();
12    var $_auth_name = '_auth';
13    var $_authentication_tested;
14
15    /**
16     * Constructs a new authentication object.
17     *
18     * @access public
19     *
20     * @param optional array $params  A hash containing parameters.
21     */
22    function AuthSQL($params = array())
23    {
24        global $CFG;
25       
26        // The name of this auth session.
27        $this->_params['auth_name'] = isset($params['auth_name']) ? $params['auth_name'] : '';
28       
29        // The database table containing users to authenticate.
30        $this->_params['user_tbl'] = isset($params['user_tbl']) ? $params['user_tbl'] : 'user_tbl';
31       
32        // The name of the primary key for the user_tbl.
33        $this->_params['user_id_column'] = isset($params['user_id_column']) ? $params['user_id_column'] : 'user_id';
34       
35        // The name of the username key for the user_tbl.
36        $this->_params['username_column'] = isset($params['username_column']) ? $params['username_column'] : 'username';
37       
38        // If using the login_tbl feature, specify the login_tbl. The primary key must match the primary key for the user_tbl.
39        $this->_params['login_tbl'] = isset($params['login_tbl']) ? $params['login_tbl'] : 'login_tbl';
40       
41        // The type of encryption to use for passwords stored in the user_tbl. Use 'md5' or 'crypt'.
42        $this->_params['encryption_type'] = isset($params['encryption_type']) ? $params['encryption_type'] : 'md5';
43
44        // The URL the user will be directed if unsuccessfully calling requireLogin.
45        $this->_params['login_url'] = isset($params['login_url']) ? $params['login_url'] : '/';
46
47        // The maximum amount of time a user is allowed to be logged in. They will be forced to login again if they expire.
48        // This applies to admins and users. In seconds. 21600 seconds = 6 hours.
49        $this->_params['login_timeout'] = isset($params['login_timeout']) ? $params['login_timeout'] : $CFG->login_timeout;
50       
51        // The maximum amount of time a user is allowed to be idle before their session expires. They will be forced to login again if they expire.
52        // This applies to admins and users. In seconds. 3600 seconds = 1 hour.
53        $this->_params['idle_timeout'] = isset($params['idle_timeout']) ? $params['idle_timeout'] : $CFG->idle_timeout;
54
55        // The period of time to compare login abuse attempts. If a threshold of logins is reached in this amount of time the account is blocked.
56        // Days and hours, like this: 'DD:HH'
57        $this->_params['login_abuse_timeframe'] = isset($params['login_abuse_timeframe']) ? $params['login_abuse_timeframe'] : $CFG->login_abuse_timeframe;
58
59        // When an account is accessed from this many different IPs, the user's password is reset and they are issued a warning.
60        $this->_params['login_abuse_warning_ips'] = isset($params['login_abuse_warning_ips']) ? $params['login_abuse_warning_ips'] : $CFG->login_abuse_warning_ips;
61
62        // The number of warnings a user will receive (and their password reset each time) before their account is completely blocked.
63        $this->_params['login_abuse_warnings'] = isset($params['login_abuse_warnings']) ? $params['login_abuse_warnings'] : $CFG->login_abuse_warnings;
64
65        // The maximum number of IP addresses a user can login with over the timeout period before their account is blocked.
66        $this->_params['login_abuse_max_ips'] = isset($params['login_abuse_max_ips']) ? $params['login_abuse_max_ips'] : $CFG->login_abuse_max_ips;
67
68        // The IP address subnet size threshold. Uses a CIDR notation network mask. Any integar between 0 and 32 is permitted. Setting this
69        // to '24' permits any address in a class C network (255.255.255.0) to be considered the same. Setting to '32' compares each IP absolutely.
70        // Setting to '0' ignores all IPs, thus disabling this feature.
71        $this->_params['login_abuse_ip_bitmask'] = isset($params['login_abuse_ip_bitmask']) ? $params['login_abuse_ip_bitmask'] : $CFG->login_abuse_ip_bitmask;
72
73        // Specify usernames to exclude from the account abuse detection system. This is specified as a hardcoded array provided at
74        // class instantiation time, or can be saved in the user_tbl under the login_abuse_exempt field.
75        $this->_params['login_abuse_exempt_usernames'] = isset($params['login_abuse_exempt_usernames']) && is_array($params['login_abuse_exempt_usernames']) ? $params['login_abuse_exempt_usernames'] : $CFG->login_abuse_exempt_usernames;
76       
77        $this->_params['trusted_networks'] = isset($params['trusted_networks']) && is_array($params['trusted_networks']) ? $params['trusted_networks'] : $CFG->trusted_networks;
78
79        // Feature: Allow user accounts to be blocked? Requires the user table to have the columns 'blocked' and 'blocked_reason'
80        $this->_params['features']['blocking'] = isset($params['features']['blocking']) ? $params['features']['blocking'] : false;
81       
82        // Feature: Use a login_tbl to detect excessive logins. This requires blocking to be enabled.
83        $this->_params['features']['abuse_detection'] = isset($params['features']['abuse_detection']) ? $params['features']['abuse_detection'] : false;
84       
85        // Array of usernames which are exempt from remote_ip matching. Users behind proxy servers should be appended to this array so their shifting remote IP will not log them out.
86        $this->_params['match_remote_ip_exempt_usernames'] = isset($params['match_remote_ip_exempt_usernames']) && is_array($params['match_remote_ip_exempt_usernames']) ? $params['match_remote_ip_exempt_usernames'] : $CFG->match_remote_ip_exempt_usernames;
87
88        // Feature: Match the user's current remote IP against the one they logged in with.
89        $this->_params['features']['match_remote_ip'] = isset($params['features']['match_remote_ip']) ? $params['features']['match_remote_ip'] : true;
90       
91        $this->_auth_name = '_auth_' . $this->_params['auth_name'];
92    }
93
94    /**
95     * Clear any authentication tokens in the current session. A.K.A. logout.
96     *
97     * @access public
98     */
99    function clearAuth()
100    {
101        dbQuery("
102            UPDATE " . $this->_params['user_tbl'] . " SET
103            seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)),
104            last_login_datetime = '0000-00-00 00:00:00'
105            WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'
106        ");
107        $_SESSION[$this->_auth_name] = array();
108        $_SESSION[$this->_auth_name]['authenticated'] = false;
109    }
110
111    /**
112     * Sets a variable into a registered auth session.
113     *
114     * @access public
115     *
116     * @param mixed $key      Which value to set.
117     * @param mixed $val      Value to set variable to.
118     */
119    function setVal($key, $val)
120    {
121        if (!isset($_SESSION[$this->_auth_name]['user_data'])) {
122            $_SESSION[$this->_auth_name]['user_data'] = array();
123        }
124        $_SESSION[$this->_auth_name]['user_data'][$key] = $val;
125    }
126
127    /**
128     * Returns a specified value from a registered auth session.
129     *
130     * @access public
131     *
132     * @param mixed $key      Which value to return.
133     * @param mixed $default  Value to return if key not found in user_data.
134     *
135     * @return mixed          Value stored in session.
136     */
137    function getVal($key, $default='')
138    {
139        if (isset($_SESSION[$this->_auth_name][$key])) {
140            return $_SESSION[$this->_auth_name][$key];
141        } else if (isset($_SESSION[$this->_auth_name]['user_data'][$key])) {
142            return $_SESSION[$this->_auth_name]['user_data'][$key];
143        } else {
144            return $default;
145        }
146    }
147   
148    /**
149     * Set the features of an auth object.
150     *
151     * @param  array $features   Array of feature keys and value to set.
152     *
153     * @return bool true on success, false on failure
154     */
155    function setFeature($features=null)
156    {
157        if (isset($features) && is_array($features)) {
158            // Set features for this object.
159            $this->_params['features'] = array_merge($this->_params['features'], $features);
160        }
161    }
162
163    /**
164     * Return the value of a feature configuration. This usually returns a bool value.
165     *
166     * @access public
167     *
168     * @param string $feature      Which feature to return.
169     *
170     * @return mixed               Configured feature value.
171     */
172    function getFeature($feature)
173    {
174        return $this->_params['features'][$feature];
175    }
176
177    /**
178     * Find out if a set of login credentials are valid.
179     *
180     * @access private
181     *
182     * @param string $username      The username to check.
183     * @param string $password      The password to compare to username.
184     *
185     * @return mixed  False if credentials not found in DB, or returns DB row matching credentials.
186     */
187    function authenticate($username, $password)
188    {       
189        // Query DB for user matching credentials.
190        $qid = dbQuery("
191            SELECT *, " . $this->_params['user_id_column'] . " AS user_id
192            FROM " . $this->_params['user_tbl'] . "
193            WHERE BINARY username = '" . mysql_real_escape_string($username) . "'
194            AND BINARY userpass = '" . mysql_real_escape_string($this->encryptPassword($password)) . "'
195        ");
196       
197        // Return user data if found.
198        if ($user_data = mysql_fetch_assoc($qid)) {
199            return $user_data;
200        } else {
201            return false;
202        }
203    }
204
205    /**
206     * If user authenticated, register login into session.
207     *
208     * @access private
209     *
210     * @param string $username     The username to check.
211     * @param string $password     The password to compare to username.
212     *
213     * @return boolean  Whether or not the credentials are valid.
214     */
215    function login($username, $password)
216    {
217        $this->clearAuth();
218
219        if (!$user_data = $this->authenticate($username, $password)) {
220            // No login: failed authentication!
221            return false;
222        }
223
224        // Register authenticated session.
225        $_SESSION[$this->_auth_name] = array(
226            'authenticated'         => true,
227            'user_id'               => $user_data['user_id'],
228            'auth_name'             => $this->_params['auth_name'],
229            'username'              => $username,
230            'priv'                  => $user_data['priv'],
231            'login_datetime'        => date('Y-m-d H:i:s'),
232            'last_access_datetime'  => date('Y-m-d H:i:s'),
233            'remote_ip'             => getRemoteAddr(),
234            'abuse_warning_level'   => $user_data['abuse_warning_level'],
235            'login_abuse_exempt'    => isset($user_data['login_abuse_exempt']) ? !empty($user_data['login_abuse_exempt']) : in_array(strtolower($username), $this->_params['login_abuse_exempt_usernames']),
236            'match_remote_ip_exempt'=> isset($user_data['match_remote_ip_exempt']) ? !empty($user_data['match_remote_ip_exempt']) : in_array(strtolower($username), $this->_params['match_remote_ip_exempt_usernames']),
237            'user_data'             => $user_data
238        );
239       
240        /**
241         * Check if the account is blocked, respond in context to reason. Cancel the login if blocked.
242         */
243        if ($this->getFeature('blocking')) {
244            if (!empty($user_data['blocked'])) {
245               
246                logMsg(sprintf('Login failed, blocked account. User: %s (%s) Reason: %s', $user_data['user_id'], $username, $user_data['blocked_reason']), LOG_NOTICE, __FILE__, __LINE__);
247               
248                switch ($user_data['blocked_reason']) {
249                    case 'account abuse' :
250                        raiseMsg(sprintf(_("This account has been blocked due to possible account abuse. Please contact us to reactivate."), null), MSG_WARNING, __FILE__, __LINE__);
251                        break;
252                    default :
253                        raiseMsg(sprintf(_("This account is currently not active. %s"), $user_data['blocked_reason']), MSG_WARNING, __FILE__, __LINE__);
254                        break;
255                }
256               
257                // No login: user is blocked!
258                $this->clearAuth();
259                return false;
260            }
261        }
262       
263        /**
264         * Check the login_tbl for too many logins under this account.
265         * (1) Count the number of unique IP addresses that logged in under this user within the login_abuse_timeframe
266         * (2) If this number exceeds the login_abuse_max_ips, assume multiple people are logging in under the same account.
267        **/
268        if ($this->getFeature('abuse_detection') && !$this->getVal('login_abuse_exempt')) {
269            $qid = dbQuery("
270                SELECT COUNT(DISTINCT LEFT(remote_ip_binary, " . $this->_params['login_abuse_ip_bitmask'] . "))
271                FROM " . $this->_params['login_tbl'] . "
272                WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'
273                AND DATE_ADD(login_datetime, INTERVAL '" . $this->_params['login_abuse_timeframe'] . "' DAY_HOUR) > NOW()
274            ");
275            list($distinct_ips) = mysql_fetch_row($qid);
276            if ($distinct_ips > $this->_params['login_abuse_max_ips']) {
277                if ($this->getVal('abuse_warning_level') < $this->_params['login_abuse_warnings']) {
278                    // Warn the user with a password reset.
279                    $this->resetPassword(null, _("This is a security precaution. We have detected this account has been accessed from multiple computers simultaneously. It is against policy to share your login information with others. If further account abuse is detected your account will be blocked."));
280                    raiseMsg(_("Your password has been reset as a security precaution. Please check your email for more information."), MSG_NOTICE, __FILE__, __LINE__);
281                    logMsg(sprintf('Account abuse detected for user %s from IP %s', $this->getVal('username'), $this->getVal('remote_ip')), LOG_WARNING, __FILE__, __LINE__);
282                } else {
283                    // Block the account with the reason of account abuse.
284                    $this->blockAccount(null, 'account abuse');
285                    raiseMsg(_("Your account has been blocked as a security precaution. Please contact us for more information."), MSG_NOTICE, __FILE__, __LINE__);
286                    logMsg(sprintf('Account blocked for user %s from IP %s', $this->getVal('username'), $this->getVal('remote_ip')), LOG_ALERT, __FILE__, __LINE__);
287                }
288                // Increment user's warning level.
289                dbQuery("UPDATE " . $this->_params['user_tbl'] . " SET abuse_warning_level = abuse_warning_level + 1 WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'");
290                // Reset the login counter for this user.
291                dbQuery("DELETE FROM " . $this->_params['login_tbl'] . " WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'");
292                // No login: reset password because of account abuse!
293                $this->clearAuth();
294                return false;
295            }
296
297            // Update the login counter table with this login access. Convert IP to binary.
298            dbQuery("
299                INSERT INTO " . $this->_params['login_tbl'] . " (
300                    " . $this->_params['user_id_column'] . ",
301                    login_datetime,
302                    remote_ip_binary
303                ) VALUES (
304                    '" . $this->getVal('user_id') . "',
305                    '" . $this->getVal('login_datetime') . "',
306                    '" . sprintf('%032b', ip2long($this->getVal('remote_ip'))) . "'
307                )
308            ");
309        }
310       
311        // Update user table with this login.
312        dbQuery("
313            UPDATE " . $this->_params['user_tbl'] . " SET
314                last_login_datetime = '" . $this->getVal('login_datetime') . "',
315                last_access_datetime = '" . $this->getVal('login_datetime') . "',
316                last_login_ip = '" . $this->getVal('remote_ip') . "'
317            WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'
318        ");
319       
320        // We're logged-in!
321        return true;
322    }
323
324    /**
325     * Test if user has a currently logged-in session.
326     *  - authentication flag set to true
327     *  - username not empty
328     *  - total logged-in time is not greater than login_timeout
329     *  - idle time is not greater than idle_timeout
330     *  - remote address is the same as the login remote address (aol users excluded).
331     *
332     * @access public
333     */
334    function isLoggedIn($user_id=null)
335    {
336        if (isset($user_id)) {
337            // Check the login status of a specific user.
338            $qid = dbQuery("
339                SELECT 1 FROM " . $this->_params['user_tbl'] . "
340                WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
341                AND DATE_ADD(last_login_datetime, INTERVAL '" . $this->_params['login_timeout'] . "' SECOND) > NOW()
342                AND DATE_ADD(last_access_datetime, INTERVAL '" . $this->_params['idle_timeout'] . "' SECOND) > NOW()
343            ");
344            return (mysql_num_rows($qid) > 0);
345        }
346       
347        // User login test need only be run once per script execution. We cache the result in the session.
348        if ($this->_authentication_tested && isset($_SESSION[$this->_auth_name]['authenticated'])) {
349            return $_SESSION[$this->_auth_name]['authenticated'];
350        }
351       
352        // Tesing login should occur once. This is the first time. Set flag.
353        $this->_authentication_tested = true;
354       
355        // Some users will access from networks with changing IP number (i.e. behind a proxy server). These users must be allowed entry be adding their IP to the list of trusted_networks.
356        if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) {
357            $user_in_trusted_network = true;
358            logMsg(sprintf('%s%s accessing from trusted network %s', 
359                ucfirst($this->_params['auth_name']), 
360                ($this->getVal('user_id') ? ' ' . $this->getVal('user_id') . ' (' .  $this->getVal('username') . ')' : ''),
361                $trusted_net
362            ), LOG_INFO, __FILE__, __LINE__);
363        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
364            $user_in_trusted_network = true;
365            logMsg(sprintf('%s%s accessing from trusted network proxy.aol.com', 
366                ucfirst($this->_params['auth_name']), 
367                ($this->getVal('user_id') ? ' ' . $this->getVal('user_id') . ' (' .  $this->getVal('username') . ')' : '')
368            ), LOG_NOTICE, __FILE__, __LINE__);
369        } else {
370            $user_in_trusted_network = false;
371        }
372       
373        // Do we match the user's remote IP at all? Yes, if set in config and not disabled for specific user.
374        if ($this->getFeature('match_remote_ip') && !$this->getVal('match_remote_ip_exempt')) {
375            $remote_ip_is_matched = ($_SESSION[$this->_auth_name]['remote_ip'] == getRemoteAddr() || $user_in_trusted_network);
376        } else {
377            $remote_ip_is_matched = true;
378        }
379       
380        // Test login with information stored in session. Skip IP matching for users from trusted networks.
381        if (true === $_SESSION[$this->_auth_name]['authenticated']
382            && !empty($_SESSION[$this->_auth_name]['username'])
383            && strtotime($_SESSION[$this->_auth_name]['login_datetime']) > time() - $this->_params['login_timeout']
384            && strtotime($_SESSION[$this->_auth_name]['last_access_datetime']) > time() - $this->_params['idle_timeout']
385            && $remote_ip_is_matched
386        ) {
387            // User is authenticated!
388            $_SESSION[$this->_auth_name]['last_access_datetime'] = date('Y-m-d H:i:s');
389
390            // Update the DB with the last_access_datetime and increment the seconds_online.
391            dbQuery("
392                UPDATE " . $this->_params['user_tbl'] . " SET
393                seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)) + 1,
394                last_access_datetime = '" . $this->getVal('last_access_datetime') . "'
395                WHERE " . $this->_params['user_id_column'] . " = '" . $this->getVal('user_id') . "'
396            ");
397            if (mysql_affected_rows($GLOBALS['dbh']) > 0) {
398                // User record still exists in DB. Do this to ensure user was not delete from DB between accesses. Notice "+ 1" in SQL above to ensure record is modified.
399                return true;
400            } else {
401                logMsg(sprintf('User update failed. Record not found for %s %s (%s).', $this->_params['auth_name'], $this->getVal('user_id'), $this->getVal('username')), LOG_NOTICE, __FILE__, __LINE__);
402            }
403        } else if (true === $_SESSION[$this->_auth_name]['authenticated']) {
404            // User is authenticated, but login has expired.
405            raiseMsg(sprintf(_("Your %s session has closed. You need to log-in again."), strtolower($this->_params['auth_name'])), MSG_NOTICE, __FILE__, __LINE__);
406           
407            // Log the reason for login expiration.
408            $expire_reasons = array();
409            if (empty($_SESSION[$this->_auth_name]['username'])) {
410                $expire_reasons[] = 'username not found';
411            }
412            if (strtotime($_SESSION[$this->_auth_name]['login_datetime']) <= time() - $this->_params['login_timeout']) {
413                $expire_reasons[] = 'login_timeout expired';
414            }
415            if (strtotime($_SESSION[$this->_auth_name]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) {
416                $expire_reasons[] = 'idle_timeout expired';
417            }
418            if ($_SESSION[$this->_auth_name]['remote_ip'] != getRemoteAddr()) {
419                if ($this->getFeature('match_remote_ip') && !$this->getVal('match_remote_ip_exempt')) {
420                    $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION[$this->_auth_name]['remote_ip'], getRemoteAddr());
421                } else {
422                    $expire_reasons[] = sprintf('remote_ip not matched but user was exempt from this check (%s != %s)', $_SESSION[$this->_auth_name]['remote_ip'], getRemoteAddr());
423                }
424            }
425            logMsg(sprintf('%s %s (%s) session expired: %s', ucfirst($this->_params['auth_name']), $this->getVal('user_id'), $this->getVal('username'), join(', ', $expire_reasons)), LOG_DEBUG, __FILE__, __LINE__);
426        }
427
428        // User is not authenticated.
429        $this->clearAuth();
430        return false;
431    }
432
433    /**
434     * Redirect user to login page if they are not logged in.
435     *
436     * @param string $msg     The text description of a message to raise.
437     * @param int    $type    The type of message: MSG_NOTICE,
438     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
439     * @param string $file    __FILE__.
440     * @param string $line    __LINE__.
441     *
442     * @access public
443     */
444    function requireLogin($msg='', $type=MSG_NOTICE, $file=null, $line=null)
445    {
446        if (!$this->isLoggedIn()) {
447            if ('' != $msg) {
448                raiseMsg($msg, $type, $file, $line);
449            }
450            setBoomerangURL(absoluteMe());
451            dieURL($this->_params['login_url']);
452        }
453    }
454
455    /**
456     * This sets the 'blocked' field for a user in the user_tbl, and also
457     * adds an optional reason
458     *
459     * @param  string   $reason      The reason for blocking the account.
460     */
461    function blockAccount($user_id=null, $reason='')
462    {
463        if ($this->getFeature('blocking')) {
464            if (strlen(mysql_real_escape_string($reason)) > 255) {
465                // blocked_reason field is varchar(255).
466                logMsg(sprintf('Blocked reason provided is greater than 255 characters: %s', $reason), LOG_WARNING, __FILE__, __LINE__);
467            }
468           
469            // Get user_id if specified.
470            $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
471            dbQuery("
472                UPDATE " . $this->_params['user_tbl'] . " SET
473                blocked = 'true',
474                blocked_reason = '" . mysql_real_escape_string($reason) . "'
475                WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
476            ");
477        }
478    }
479
480    /**
481     * Unblocks a user in the user_tbl, and clears any blocked_reason.
482     */
483    function unblockAccount($user_id=null)
484    {
485        if ($this->getFeature('blocking')) {
486            // Get user_id if specified.
487            $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
488            dbQuery("
489                UPDATE " . $this->_params['user_tbl'] . " SET
490                blocked = '',
491                blocked_reason = ''
492                WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
493            ");
494        }
495    }
496
497    /**
498     * Returns true if username already exists in database.
499     *
500     * @param  string  $username    Username to look for.
501     *
502     * @return bool                 True if username exists.
503     */
504    function usernameExists($username)
505    {   
506        $qid = dbQuery("SELECT 1 FROM " . $this->_params['user_tbl'] . " WHERE username = '" . mysql_real_escape_string($username) . "'");
507        return (mysql_num_rows($qid) > 0);
508    }
509
510    /**
511     * Returns a username for a specified user id.
512     *
513     * @param  string  $user_id     User id to look for.
514     *
515     * @return string               Username, or false if none found.
516     */
517    function getUsername($user_id)
518    {   
519        $qid = dbQuery("SELECT " . $this->_params['username_column'] . " FROM " . $this->_params['user_tbl'] . " WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'");
520        if (list($username) = mysql_fetch_row($qid)) {
521            return $username;
522        } else {
523            return false;
524        }
525    }
526
527    /**
528     * Returns a randomly generated password based on $pattern. The pattern is any
529     * sequence of 'x', 'V', 'C', 'v', 'c', or 'd' and if it is something like 'cvccv' this
530     * function will generate a pronouncable password. Recommend using more complex
531     * patterns, at minimum the US State Department standard: cvcddcvc.
532     *
533     * - x    a random upper or lower alpha character or digit
534     * - C    a random upper or lower consanant
535     * - V    a random upper or lower vowel
536     * - c    a random lowercase consanant
537     * - v    a random lowercase vowel
538     * - d    a random digit
539     *
540     * @param  string $pattern  a sequence of character types, above.
541     *
542     * @return string           a password
543     */
544    function generatePassword($pattern='CvccvCdd')
545    {
546        mt_srand((double) microtime() * 10000000);
547        for ($i=0; $i<strlen($pattern); $i++) {
548            $x = substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZaeiouyAEIOUY0123456789', (mt_rand() % 60), 1);
549            $c = substr('bcdfghjklmnprstvwxz', (mt_rand() % 19), 1);
550            $C = substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZ', (mt_rand() % 38), 1);
551            $v = substr('aeiouy', (mt_rand() % 6), 1);
552            $V = substr('aeiouyAEIOUY', (mt_rand() % 12), 1);
553            $d = substr('0123456789', (mt_rand() % 10), 1);
554            $str .= $$pattern{$i};
555        }
556        return $str;
557    }
558   
559    /**
560     *
561     */
562    function encryptPassword($password)
563    {
564        switch ($this->_params['encryption_type']) {
565        case 'plain' :
566            return $password;
567            break;
568           
569        case 'crypt' :
570            return crypt($password, crypt($password));
571            break;
572           
573        case 'sha1' :
574            if (function_exists('sha1')) { // Only in PHP 4.3.0+
575                return sha1($password);
576                break;
577            }
578           
579        case 'md5' :
580        default :
581            return md5($password);
582            break;
583        }
584    }
585
586    /**
587     *
588     */
589    function setPassword($user_id=null, $password)
590    {       
591        // Get user_id if specified.
592        $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
593       
594        // Issue the password change query.
595        dbQuery("
596            UPDATE " . $this->_params['user_tbl'] . "
597            SET userpass = '" . mysql_real_escape_string($this->encryptPassword($password)) . "'
598            WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
599        ");
600    }
601
602    /**
603     * Resets the password for the user with the specified id.
604     *
605     * @param  string $user_id   The id of the user to reset.
606     * @param  string $reason    Additional message to add to the reset email.
607     *
608     * @return string            The user's new password.
609     */
610    function resetPassword($user_id=null, $reason='')
611    {
612        global $CFG;
613       
614        // Get user_id if specified.
615        $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
616       
617        // Reset password of a specific user.
618        $qid = dbQuery("
619            SELECT * FROM " . $this->_params['user_tbl'] . "
620            WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
621        ");
622        $user_data = mysql_fetch_assoc($qid);
623
624        // Get new password.
625        $password = $this->generatePassword();
626       
627        // Issue the password change query.
628        dbQuery("
629            UPDATE " . $this->_params['user_tbl'] . "
630            SET userpass = '" . mysql_real_escape_string($this->encryptPassword($password)) . "'
631            WHERE " . $this->_params['user_id_column'] . " = '" . mysql_real_escape_string($user_id) . "'
632        ");
633
634        // Email the user with the new account information.
635        // $reason is used in this template.
636        if (include 'email_reset_password.ihtml') {
637            mail($user_data['email'], $email_subject, $email_body, "From: $CFG->site_name <$CFG->site_email>\r\n", $CFG->envelope_sender_address);
638        }
639   
640        return array('username'=>$user_data['username'], 'userpass'=>$password);
641    }
642   
643    /**
644     * If the current user has access to the specified $security_zone, return true.
645     * If the optional $priv is supplied, test that against the zone.
646     *
647     * @param  constant $security_zone   string of comma delimited priviliges for the zone
648     * @param  string   $priv            a privilege that might be found in a zone
649     *
650     * @return bool     true if user is a member of security zone, false otherwise
651     */
652    function inClearanceZone($security_zone, $priv='')
653    {
654        $zone_members = preg_split('/,\s*/', $security_zone);
655        $priv = empty($priv) ? $this->getVal('priv') : $priv;
656       
657        // If the current user's privilege level is NOT in that array or if the
658        // user has no privilege, return false. Otherwise the user is clear.
659        if (!in_array($priv, $zone_members) || empty($priv)) {
660            return false;
661        } else {
662            return true;
663        }
664    }
665   
666    /**
667     * This function tests a list of arguments $security_zone against the priv that the current user has.
668     * If the user doesn't have one of the supplied privs, die.
669     *
670     * @param  constant $security_zone   string of comma delimited priviliges for the zone
671     */
672    function requireAccessClearance($security_zone, $msg='')
673    {
674        $zone_members = preg_split('/,\s*/', $security_zone);
675   
676        /* If the current user's privilege level is NOT in that array or if the
677         * user has no privilege, DIE with a message. */
678        if (!in_array($this->getVal('priv'), $zone_members) || !$this->getVal('priv')) {
679            $msg = empty($msg) ? _("You have insufficient privileges to view that page.") : $msg;
680            raiseMsg($msg, MSG_NOTICE, __FILE__, __LINE__);
681            dieBoomerangURL();
682        }
683    }
684
685} // end class
686
687// CIDR cheatsheet
688//
689// Netmask              Netmask (binary)                 CIDR     Notes   
690// _____________________________________________________________________________
691// 255.255.255.255  11111111.11111111.11111111.11111111  /32  Host (single addr)
692// 255.255.255.254  11111111.11111111.11111111.11111110  /31  Unuseable
693// 255.255.255.252  11111111.11111111.11111111.11111100  /30    2  useable
694// 255.255.255.248  11111111.11111111.11111111.11111000  /29    6  useable
695// 255.255.255.240  11111111.11111111.11111111.11110000  /28   14  useable
696// 255.255.255.224  11111111.11111111.11111111.11100000  /27   30  useable
697// 255.255.255.192  11111111.11111111.11111111.11000000  /26   62  useable
698// 255.255.255.128  11111111.11111111.11111111.10000000  /25  126  useable
699// 255.255.255.0    11111111.11111111.11111111.00000000  /24 "Class C" 254 useable
700//
701// 255.255.254.0    11111111.11111111.11111110.00000000  /23    2  Class C's
702// 255.255.252.0    11111111.11111111.11111100.00000000  /22    4  Class C's
703// 255.255.248.0    11111111.11111111.11111000.00000000  /21    8  Class C's
704// 255.255.240.0    11111111.11111111.11110000.00000000  /20   16  Class C's
705// 255.255.224.0    11111111.11111111.11100000.00000000  /19   32  Class C's
706// 255.255.192.0    11111111.11111111.11000000.00000000  /18   64  Class C's
707// 255.255.128.0    11111111.11111111.10000000.00000000  /17  128  Class C's
708// 255.255.0.0      11111111.11111111.00000000.00000000  /16  "Class B"
709//     
710// 255.254.0.0      11111111.11111110.00000000.00000000  /15    2  Class B's
711// 255.252.0.0      11111111.11111100.00000000.00000000  /14    4  Class B's
712// 255.248.0.0      11111111.11111000.00000000.00000000  /13    8  Class B's
713// 255.240.0.0      11111111.11110000.00000000.00000000  /12   16  Class B's
714// 255.224.0.0      11111111.11100000.00000000.00000000  /11   32  Class B's
715// 255.192.0.0      11111111.11000000.00000000.00000000  /10   64  Class B's
716// 255.128.0.0      11111111.10000000.00000000.00000000  /9   128  Class B's
717// 255.0.0.0        11111111.00000000.00000000.00000000  /8   "Class A"
718//   
719// 254.0.0.0        11111110.00000000.00000000.00000000  /7
720// 252.0.0.0        11111100.00000000.00000000.00000000  /6
721// 248.0.0.0        11111000.00000000.00000000.00000000  /5
722// 240.0.0.0        11110000.00000000.00000000.00000000  /4
723// 224.0.0.0        11100000.00000000.00000000.00000000  /3
724// 192.0.0.0        11000000.00000000.00000000.00000000  /2
725// 128.0.0.0        10000000.00000000.00000000.00000000  /1
726// 0.0.0.0          00000000.00000000.00000000.00000000  /0   IP space
727?>
Note: See TracBrowser for help on using the repository browser.