source: trunk/lib/Auth_SQL.inc.php @ 432

Last change on this file since 432 was 432, checked in by anonymous, 11 years ago

Removed all usage of 'user_type' from admin management; minor update to module maker.

File size: 49.7 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
6 *
7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
13 *
14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24* The Auth_SQL class provides a SQL implementation for authentication.
25*
26* @author  Quinn Comendant <quinn@strangecode.com>
27* @version 2.1
28*/
29
30// Available encryption types for class Auth_SQL.
31define('AUTH_ENCRYPT_PLAINTEXT', 1);
32define('AUTH_ENCRYPT_CRYPT', 2);
33define('AUTH_ENCRYPT_SHA1', 3);
34define('AUTH_ENCRYPT_SHA1_HARDENED', 4);
35define('AUTH_ENCRYPT_MD5', 5);
36define('AUTH_ENCRYPT_MD5_HARDENED', 6);
37
38require_once dirname(__FILE__) . '/Email.inc.php';
39
40class Auth_SQL {
41       
42    // Namespace of this auth object.
43    var $_ns;
44   
45    // Static var for test.
46    var $_authentication_tested;
47
48    // Parameters to be configured by setParam.
49    var $_params = array();
50    var $_default_params = array(
51
52        // Automatically create table and verify columns. Better set to false after site launch.
53        // This value is overwritten by the $app->getParam('db_create_tables') setting if it is available.
54        'create_table' => true,
55
56        // The database table containing users to authenticate.
57        'db_table' => 'user_tbl',
58
59        // The name of the primary key for the db_table.
60        'db_primary_key' => 'user_id',
61
62        // The name of the username key for the db_table.
63        'db_username_column' => 'username',
64
65        // If using the db_login_table feature, specify the db_login_table. The primary key must match the primary key for the db_table.
66        'db_login_table' => 'user_login_tbl',
67
68        // The type of encryption to use for passwords stored in the db_table. Use one of the AUTH_ENCRYPT_* types specified above.
69        // Hardened password hashes rely on the same key/salt being used to compare encryptions.
70        // Be aware that when using one of the hardened types the App signing_key or $more_salt below cannot change!
71        'encryption_type' => AUTH_ENCRYPT_MD5,
72
73        // The URL to the login script.
74        'login_url' => '/',
75
76        // The maximum amount of time a user is allowed to be logged in. They will be forced to login again if they expire.
77        // In seconds. 21600 seconds = 6 hours.
78        'login_timeout' => 21600,
79
80        // 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.
81        // In seconds. 3600 seconds = 1 hour.
82        'idle_timeout' => 3600,
83
84        // 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.
85        // Days and hours, like this: 'DD:HH'
86        'login_abuse_timeframe' => '04:00',
87
88        // The number of warnings a user will receive (and their password reset each time) before their account is completely blocked.
89        'login_abuse_warnings' => 3,
90
91        // The maximum number of IP addresses a user can login with over the timeout period before their account is blocked.
92        'login_abuse_max_ips' => 5,
93
94        // The IP address subnet size threshold. Uses a CIDR notation network mask (see CIDR cheat-sheet at bottom).
95        // Any integer between 0 and 32 is permitted. Setting this to '24' permits any address in a
96        // class C network (255.255.255.0) to be considered the same. Setting to '32' compares each IP absolutely.
97        // Setting to '0' ignores all IPs, thus disabling login_abuse checking.
98        'login_abuse_ip_bitmask' => 32,
99
100        // Specify usernames to exclude from the account abuse detection system. This is specified as a hardcoded array provided at
101        // class instantiation time, or can be saved in the db_table under the login_abuse_exempt field.
102        'login_abuse_exempt_usernames' => array(),
103
104        // Specify usernames to exclude from remote_ip matching. Users behind proxy servers should be appended to this array so their shifting remote IP will not log them out.
105        'match_remote_ip_exempt_usernames' => array(),
106
107        // Match the user's current remote IP against the one they logged in with.
108        'match_remote_ip' => true,
109
110        // An array of IP blocks that are bypass the remote_ip comparison check. Useful for dynamic IPs or those behind proxy servers.
111        'trusted_networks' => array(),
112
113        // Allow user accounts to be blocked? Requires the user table to have the columns 'blocked' and 'blocked_reason'
114        'blocking' => false,
115
116        // Use a db_login_table to detect excessive logins. This requires blocking to be enabled.
117        'abuse_detection' => false,
118
119        // Allow users to save login form passwords in their browser? Setting to 'true' may pose a potential security risk.
120        'login_form_allow_autocomplete' => false,
121    );
122
123    /**
124     * Constructs a new authentication object.
125     *
126     * @access public
127     * @param optional array $params  A hash containing parameters.
128     */
129    function Auth_SQL($namespace='')
130    {
131        $app =& App::getInstance();
132       
133        $this->_ns = $namespace;
134       
135        // Initialize default parameters.
136        $this->setParam($this->_default_params);
137
138        // Get create tables config from global context.
139        if (!is_null($app->getParam('db_create_tables'))) {
140            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
141        }
142
143        if (!isset($_SESSION['_auth_sql'][$this->_ns])) {
144            $this->clear();
145        }
146    }
147
148    /**
149     * Setup the database tables for this class.
150     *
151     * @access  public
152     * @author  Quinn Comendant <quinn@strangecode.com>
153     * @since   26 Aug 2005 17:09:36
154     */
155    function initDB($recreate_db=false)
156    {
157        $app =& App::getInstance();
158        $db =& DB::getInstance();
159   
160   
161        static $_db_tested = false;
162
163        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
164
165            // User table.
166            if ($recreate_db) {
167                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
168                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_INFO, __FILE__, __LINE__);
169            }
170
171            // The minimal columns for a table compatable with the Auth_SQL class.
172            $db->query("CREATE TABLE IF NOT EXISTS " . $db->escapeString($this->getParam('db_table')) . " (
173                " . $this->getParam('db_primary_key') . " smallint(11) NOT NULL auto_increment,
174                " . $this->getParam('db_username_column') . " varchar(255) NOT NULL default '',
175                userpass VARCHAR(255) NOT NULL DEFAULT '',
176                first_name VARCHAR(255) NOT NULL DEFAULT '',
177                last_name VARCHAR(255) NOT NULL DEFAULT '',
178                email VARCHAR(255) NOT NULL DEFAULT '',
179                login_abuse_exempt ENUM('TRUE') DEFAULT NULL,
180                blocked ENUM('TRUE') DEFAULT NULL,
181                blocked_reason VARCHAR(255) NOT NULL DEFAULT '',
182                abuse_warning_level TINYINT(4) NOT NULL DEFAULT '0',
183                seconds_online INT(11) NOT NULL DEFAULT '0',
184                last_login_datetime DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
185                last_access_datetime DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
186                last_login_ip VARCHAR(255) NOT NULL DEFAULT '0.0.0.0',
187                added_by_user_id SMALLINT(11) DEFAULT NULL,
188                modified_by_user_id SMALLINT(11) DEFAULT NULL,
189                added_datetime DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
190                modified_datetime DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
191                PRIMARY KEY (" . $this->getParam('db_primary_key') . "),
192                KEY " . $this->getParam('db_username_column') . " (" . $this->getParam('db_username_column') . "),
193                KEY userpass (userpass),
194                KEY email (email)
195            )");
196
197            if (!$db->columnExists($this->getParam('db_table'), array(
198                $this->getParam('db_primary_key'),
199                $this->getParam('db_username_column'),
200                'userpass',
201                'first_name',
202                'last_name',
203                'email',
204                'login_abuse_exempt',
205                'blocked',
206                'blocked_reason',
207                'abuse_warning_level',
208                'seconds_online',
209                'last_login_datetime',
210                'last_access_datetime',
211                'last_login_ip',
212                'added_by_user_id',
213                'modified_by_user_id',
214                'added_datetime',
215                'modified_datetime',
216            ), false, false)) {
217                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
218                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
219            }
220
221            // Login table is used for abuse_detection features.
222            if ($this->getParam('abuse_detection')) {
223                if ($recreate_db) {
224                    $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_login_table'));
225                    $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_login_table')), LOG_INFO, __FILE__, __LINE__);
226                }
227                $db->query("CREATE TABLE IF NOT EXISTS " . $this->getParam('db_login_table') . " (
228                    " . $this->getParam('db_primary_key') . " SMALLINT(11) NOT NULL DEFAULT '0',
229                    login_datetime DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
230                    remote_ip_binary CHAR(32) NOT NULL DEFAULT '',
231                    KEY " . $this->getParam('db_primary_key') . " (" . $this->getParam('db_primary_key') . "),
232                    KEY login_datetime (login_datetime),
233                    KEY remote_ip_binary (remote_ip_binary)
234                )");
235
236                if (!$db->columnExists($this->getParam('db_login_table'), array(
237                    $this->getParam('db_primary_key'),
238                    'login_datetime',
239                    'remote_ip_binary',
240                ), false, false)) {
241                    $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_login_table')), LOG_ALERT, __FILE__, __LINE__);
242                    trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_login_table')), E_USER_ERROR);
243                }
244            }
245        }
246        $_db_tested = true;
247    }
248
249    /**
250     * Set the params of an auth object.
251     *
252     * @param  array $params   Array of parameter keys and value to set.
253     * @return bool true on success, false on failure
254     */
255    function setParam($params)
256    {
257        if (isset($params['match_remote_ip_exempt_usernames'])) {
258            $params['match_remote_ip_exempt_usernames'] = array_map('strtolower', $params['match_remote_ip_exempt_usernames']);
259        }
260        if (isset($params['login_abuse_exempt_usernames'])) {
261            $params['login_abuse_exempt_usernames'] = array_map('strtolower', $params['login_abuse_exempt_usernames']);
262        }
263        if (isset($params) && is_array($params)) {
264            // Merge new parameters with old overriding only those passed.
265            $this->_params = array_merge($this->_params, $params);
266        }
267    }
268
269    /**
270     * Return the value of a parameter, if it exists.
271     *
272     * @access public
273     * @param string $param        Which parameter to return.
274     * @return mixed               Configured parameter value.
275     */
276    function getParam($param)
277    {
278        $app =& App::getInstance();
279   
280        if (isset($this->_params[$param])) {
281            return $this->_params[$param];
282        } else {
283            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
284            return null;
285        }
286    }
287
288    /**
289     * Clear any authentication tokens in the current session. A.K.A. logout.
290     *
291     * @access public
292     */
293    function clear()
294    {
295        $db =& DB::getInstance();
296   
297        $this->initDB();
298
299        if ($this->get('user_id', false)) {
300            // FIX ME: Should we check if the session is active?
301            $db->query("
302                UPDATE " . $this->_params['db_table'] . " SET
303                seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)),
304                last_login_datetime = '0000-00-00 00:00:00'
305                WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'
306            ");
307        }
308        $_SESSION['_auth_sql'][$this->_ns] = array(
309            'authenticated'         => false,
310            'user_id'               => null,
311            'username'              => null,
312            'login_datetime'        => null,
313            'last_access_datetime'  => null,
314            'remote_ip'             => getRemoteAddr(),
315            'login_abuse_exempt'    => null,
316            'match_remote_ip_exempt'=> null,
317            'user_data'             => null,
318        );
319    }
320
321    /**
322     * Sets a variable into a registered auth session.
323     *
324     * @access public
325     * @param mixed $key      Which value to set.
326     * @param mixed $val      Value to set variable to.
327     */
328    function set($key, $val)
329    {
330        if (!isset($_SESSION['_auth_sql'][$this->_ns]['user_data'])) {
331            $_SESSION['_auth_sql'][$this->_ns]['user_data'] = array();
332        }
333        $_SESSION['_auth_sql'][$this->_ns]['user_data'][$key] = $val;
334    }
335
336    /**
337     * Returns a specified value from a registered auth session.
338     *
339     * @access public
340     * @param mixed $key      Which value to return.
341     * @param mixed $default  Value to return if key not found in user_data.
342     * @return mixed          Value stored in session.
343     */
344    function get($key, $default='')
345    {
346        if (isset($_SESSION['_auth_sql'][$this->_ns][$key])) {
347            return $_SESSION['_auth_sql'][$this->_ns][$key];
348        } else if (isset($_SESSION['_auth_sql'][$this->_ns]['user_data'][$key])) {
349            return $_SESSION['_auth_sql'][$this->_ns]['user_data'][$key];
350        } else {
351            return $default;
352        }
353    }
354
355    /**
356     * Find out if a set of login credentials are valid.
357     *
358     * @access private
359     * @param string $username      The username to check.
360     * @param string $password      The password to compare to username.
361     * @return mixed  False if credentials not found in DB, or returns DB row matching credentials.
362     */
363    function authenticate($username, $password)
364    {
365        $app =& App::getInstance();
366        $db =& DB::getInstance();
367
368        $this->initDB();
369
370        switch ($this->_params['encryption_type']) {
371        case AUTH_ENCRYPT_CRYPT :
372            // Query DB for user matching credentials. Compare cyphertext with salted-encrypted password.
373            $qid = $db->query("
374                SELECT *, " . $this->_params['db_primary_key'] . " AS user_id
375                FROM " . $this->_params['db_table'] . "
376                WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "'
377                AND BINARY userpass = ENCRYPT('" . $db->escapeString($password) . "', LEFT(userpass, 2)))
378            ");
379            break;
380        case AUTH_ENCRYPT_PLAINTEXT :
381        case AUTH_ENCRYPT_MD5 :
382        case AUTH_ENCRYPT_SHA1 :
383        default :
384            // Query DB for user matching credentials. Directly compare cyphertext with result from encryptPassword().
385            $qid = $db->query("
386                SELECT *, " . $this->_params['db_primary_key'] . " AS user_id
387                FROM " . $this->_params['db_table'] . "
388                WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "'
389                AND BINARY userpass = '" . $db->escapeString($this->encryptPassword($password)) . "'
390            ");
391            break;
392        }
393
394        // Return user data if found.
395        if ($user_data = mysql_fetch_assoc($qid)) {
396            // Don't return password value.
397            unset($user_data['userpass']);
398            $app->logMsg(sprintf('Authentication successful for user_id %s (%s)', $user_data['user_id'], $username), LOG_INFO, __FILE__, __LINE__);
399            return $user_data;
400        } else {
401            $app->logMsg(sprintf('Authentication failed for username %s (encrypted attempted password: %s)', $username, $this->encryptPassword($password)), LOG_NOTICE, __FILE__, __LINE__);
402            return false;
403        }
404    }
405
406    /**
407     * If user authenticated, register login into session.
408     *
409     * @access private
410     * @param string $username     The username to check.
411     * @param string $password     The password to compare to username.
412     * @return boolean  Whether or not the credentials are valid.
413     */
414    function login($username, $password)
415    {
416        $app =& App::getInstance();
417        $db =& DB::getInstance();
418   
419        $this->initDB();
420
421        $this->clear();
422
423        if (!$user_data = $this->authenticate($username, $password)) {
424            // No login: failed authentication!
425            return false;
426        }
427       
428        // Register authenticated session.
429        $_SESSION['_auth_sql'][$this->_ns] = array(
430            'authenticated'         => true,
431            'user_id'               => $user_data['user_id'],
432            'username'              => $username,
433            'login_datetime'        => date('Y-m-d H:i:s'),
434            'last_access_datetime'  => date('Y-m-d H:i:s'),
435            'remote_ip'             => getRemoteAddr(),
436            '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']),
437            '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']),
438            'user_data'             => $user_data
439        );
440
441        /**
442         * Check if the account is blocked, respond in context to reason. Cancel the login if blocked.
443         */
444        if ($this->getParam('blocking')) {
445            if (!empty($user_data['blocked'])) {
446
447                $app->logMsg(sprintf('User_id %s (%s) login failed due to blocked account: %s', $this->get('user_id'), $this->get('username'), $this->get('blocked_reason')), LOG_NOTICE, __FILE__, __LINE__);
448
449                switch ($user_data['blocked_reason']) {
450                    case 'account abuse' :
451                        $app->raiseMsg(sprintf(_("This account has been blocked due to possible account abuse. Please contact us to reactivate."), null), MSG_WARNING, __FILE__, __LINE__);
452                        break;
453                    default :
454                        $app->raiseMsg(sprintf(_("This account is currently not active. %s"), $user_data['blocked_reason']), MSG_WARNING, __FILE__, __LINE__);
455                        break;
456                }
457
458                // No login: user is blocked!
459                $this->clear();
460                return false;
461            }
462        }
463
464        /**
465         * Check the db_login_table for too many logins under this account.
466         * (1) Count the number of unique IP addresses that logged in under this user within the login_abuse_timeframe
467         * (2) If this number exceeds the login_abuse_max_ips, assume multiple people are logging in under the same account.
468        **/
469        if ($this->getParam('abuse_detection') && !$this->get('login_abuse_exempt')) {
470            $qid = $db->query("
471                SELECT COUNT(DISTINCT LEFT(remote_ip_binary, " . $this->_params['login_abuse_ip_bitmask'] . "))
472                FROM " . $this->_params['db_login_table'] . "
473                WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'
474                AND DATE_ADD(login_datetime, INTERVAL '" . $this->_params['login_abuse_timeframe'] . "' DAY_HOUR) > NOW()
475            ");
476            list($distinct_ips) = mysql_fetch_row($qid);
477            if ($distinct_ips > $this->_params['login_abuse_max_ips']) {
478                if ($this->get('abuse_warning_level') < $this->_params['login_abuse_warnings']) {
479                    // Warn the user with a password reset.
480                    $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 login information with others. If further account abuse is detected this account will be blocked."));
481                    $app->raiseMsg(_("Your password has been reset as a security precaution. Please check your email for more information."), MSG_NOTICE, __FILE__, __LINE__);
482                    $app->logMsg(sprintf('Account abuse detected for user_id %s (%s) from IP %s', $this->get('user_id'), $this->get('username'), $this->get('remote_ip')), LOG_WARNING, __FILE__, __LINE__);
483                } else {
484                    // Block the account with the reason of account abuse.
485                    $this->blockAccount(null, 'account abuse');
486                    $app->raiseMsg(_("Your account has been blocked as a security precaution. Please contact us for more information."), MSG_NOTICE, __FILE__, __LINE__);
487                    $app->logMsg(sprintf('Account blocked for user_id %s (%s) from IP %s', $this->get('user_id'), $this->get('username'), $this->get('remote_ip')), LOG_ALERT, __FILE__, __LINE__);
488                }
489                // Increment user's warning level.
490                $db->query("UPDATE " . $this->_params['db_table'] . " SET abuse_warning_level = abuse_warning_level + 1 WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'");
491                // Reset the login counter for this user.
492                $db->query("DELETE FROM " . $this->_params['db_login_table'] . " WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'");
493                // No login: reset password because of account abuse!
494                $this->clear();
495                return false;
496            }
497
498            // Update the login counter table with this login access. Convert IP to binary.
499            // TODO: after MySQL 5.0.23 is released this query could benefit from INSERT DELAYED.
500            $db->query("
501                INSERT INTO " . $this->_params['db_login_table'] . " (
502                    " . $this->_params['db_primary_key'] . ",
503                    login_datetime,
504                    remote_ip_binary
505                ) VALUES (
506                    '" . $this->get('user_id') . "',
507                    '" . $this->get('login_datetime') . "',
508                    '" . sprintf('%032b', ip2long($this->get('remote_ip'))) . "'
509                )
510            ");
511        }
512
513        // Update user table with this login.
514        $db->query("
515            UPDATE " . $this->_params['db_table'] . " SET
516                last_login_datetime = '" . $this->get('login_datetime') . "',
517                last_access_datetime = '" . $this->get('login_datetime') . "',
518                last_login_ip = '" . $this->get('remote_ip') . "'
519            WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'
520        ");
521
522        // We're logged-in!
523        return true;
524    }
525
526    /**
527     * Test if user has a currently logged-in session.
528     *  - authentication flag set to true
529     *  - username not empty
530     *  - total logged-in time is not greater than login_timeout
531     *  - idle time is not greater than idle_timeout
532     *  - remote address is the same as the login remote address (aol users excluded).
533     *
534     * @access public
535     */
536    function isLoggedIn($user_id=null)
537    {
538        $app =& App::getInstance();
539        $db =& DB::getInstance();
540
541        $this->initDB();
542
543        if (isset($user_id)) {
544            // Check the login status of a specific user.
545            $qid = $db->query("
546                SELECT 1 FROM " . $this->_params['db_table'] . "
547                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
548                AND DATE_ADD(last_login_datetime, INTERVAL '" . $this->_params['login_timeout'] . "' SECOND) > NOW()
549                AND DATE_ADD(last_access_datetime, INTERVAL '" . $this->_params['idle_timeout'] . "' SECOND) > NOW()
550            ");
551            $login_status = (mysql_num_rows($qid) > 0);
552            $app->logMsg(sprintf('Returning %s login status for user_id %s', ($login_status ? 'true' : 'false'), $user_id), LOG_DEBUG, __FILE__, __LINE__);
553            return $login_status;
554        }
555
556        // User login test need only be run once per script execution. We cache the result in the session.
557        if ($this->_authentication_tested && isset($_SESSION['_auth_sql'][$this->_ns]['authenticated'])) {
558            $app->logMsg(sprintf('Returning cached authentication status: %s', ($_SESSION['_auth_sql'][$this->_ns]['authenticated'] ? 'true' : 'false')), LOG_DEBUG, __FILE__, __LINE__);
559            return $_SESSION['_auth_sql'][$this->_ns]['authenticated'];
560        }
561
562        // Tesing login should occur once. This is the first time. Set flag.
563        $this->_authentication_tested = true;
564
565        // Some users will access from networks with a changing IP number (i.e. behind a proxy server).
566        // These users must be allowed entry by adding their IP to the list of trusted_networks, or their usernames to the list of match_remote_ip_exempt_usernames.
567        if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) {
568            $user_in_trusted_network = true;
569            $app->logMsg(sprintf('User_id %s accessing from trusted network %s',
570                ($this->get('user_id') ? $this->get('user_id') . ' (' .  $this->get('username') . ')' : 'unknown'),
571                $trusted_net
572            ), LOG_DEBUG, __FILE__, __LINE__);
573        } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) {
574            $user_in_trusted_network = true;
575            $app->logMsg(sprintf('User_id %s accessing from trusted network proxy.aol.com',
576                ($this->get('user_id') ? $this->get('user_id') . ' (' .  $this->get('username') . ')' : 'unknown')
577            ), LOG_DEBUG, __FILE__, __LINE__);
578        } else {
579            $user_in_trusted_network = false;
580        }
581       
582        // Do we match the user's remote IP at all? Yes, if set in config and not disabled for specific user.
583        if ($this->getParam('match_remote_ip') && !$this->get('match_remote_ip_exempt')) {
584            $remote_ip_is_matched = (isset($_SESSION['_auth_sql'][$this->_ns]['remote_ip']) && $_SESSION['_auth_sql'][$this->_ns]['remote_ip'] == getRemoteAddr()) || $user_in_trusted_network;
585        } else {
586            $app->logMsg(sprintf('User_id %s exempt from remote_ip match (comparing %s == %s)', 
587                ($this->get('user_id') ? $this->get('user_id') . ' (' .  $this->get('username') . ')' : 'unknown'),
588                $_SESSION['_auth_sql'][$this->_ns]['remote_ip'],
589                getRemoteAddr()
590            ), LOG_DEBUG, __FILE__, __LINE__);
591            $remote_ip_is_matched = true;
592        }
593
594        // Test login with information stored in session. Skip IP matching for users from trusted networks.
595        if (isset($_SESSION['_auth_sql'][$this->_ns]['authenticated']) 
596            && true === $_SESSION['_auth_sql'][$this->_ns]['authenticated']
597            && isset($_SESSION['_auth_sql'][$this->_ns]['username'])
598            && !empty($_SESSION['_auth_sql'][$this->_ns]['username'])
599            && isset($_SESSION['_auth_sql'][$this->_ns]['login_datetime'])
600            && strtotime($_SESSION['_auth_sql'][$this->_ns]['login_datetime']) > time() - $this->_params['login_timeout']
601            && isset($_SESSION['_auth_sql'][$this->_ns]['last_access_datetime'])
602            && strtotime($_SESSION['_auth_sql'][$this->_ns]['last_access_datetime']) > time() - $this->_params['idle_timeout']
603            && $remote_ip_is_matched
604        ) {
605            // User is authenticated!
606            $_SESSION['_auth_sql'][$this->_ns]['last_access_datetime'] = date('Y-m-d H:i:s');
607
608            // Update the DB with the last_access_datetime and increment the seconds_online.
609            $db->query("
610                UPDATE " . $this->_params['db_table'] . " SET
611                seconds_online = seconds_online + (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_access_datetime)) + 1,
612                last_access_datetime = '" . $this->get('last_access_datetime') . "'
613                WHERE " . $this->_params['db_primary_key'] . " = '" . $this->get('user_id') . "'
614            ");
615            if (mysql_affected_rows($db->getDBH()) > 0) {
616                // 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.
617                return true;
618            } else {
619                $app->logMsg(sprintf('Session update failed; record not found for user_id %s (%s).', $this->get('user_id'), $this->get('username')), LOG_NOTICE, __FILE__, __LINE__);
620            }
621        } else if (isset($_SESSION['_auth_sql'][$this->_ns]['authenticated']) && true === $_SESSION['_auth_sql'][$this->_ns]['authenticated']) {
622            // User is authenticated, but login has expired.
623            if (strtotime($_SESSION['_auth_sql'][$this->_ns]['last_access_datetime']) > time() - 43200) {
624                // Only raise message if last session is less than 12 hours old.
625                $app->raiseMsg(_("Your session has expired. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__);
626            }
627
628            // Log the reason for login expiration.
629            $expire_reasons = array();
630            if (empty($_SESSION['_auth_sql'][$this->_ns]['username'])) {
631                $expire_reasons[] = 'username not found';
632            }
633            if (strtotime($_SESSION['_auth_sql'][$this->_ns]['login_datetime']) <= time() - $this->_params['login_timeout']) {
634                $expire_reasons[] = sprintf('login_timeout expired (%s older than %s seconds ago)', $_SESSION['_auth_sql'][$this->_ns]['login_datetime'], $this->_params['login_timeout']);
635            }
636            if (strtotime($_SESSION['_auth_sql'][$this->_ns]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) {
637                $expire_reasons[] = sprintf('idle_timeout expired (%s older than %s seconds ago)', $_SESSION['_auth_sql'][$this->_ns]['last_access_datetime'], $this->_params['idle_timeout']);
638            }
639            if ($_SESSION['_auth_sql'][$this->_ns]['remote_ip'] != getRemoteAddr()) {
640                if ($this->getParam('match_remote_ip') && !$this->get('match_remote_ip_exempt') && !$user_in_trusted_network) {
641                    // There are three cases when a remote IP match will be the cause of a session termination:
642                    //   1. match_remote_ip config is enabled
643                    //   2. user is not match_remote_ip_exempt (set in the user_data, or in the match_remote_ip_exempt_usernames list)
644                    //   3. the user is connecting from a trusted network (their IP is listed in the trusted_networks or from *.proxy.aol.com)
645                    $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION['_auth_sql'][$this->_ns]['remote_ip'], getRemoteAddr());
646                } else {
647                    $expire_reasons[] = sprintf('remote_ip not matched but user was exempt from this check (%s != %s)', $_SESSION['_auth_sql'][$this->_ns]['remote_ip'], getRemoteAddr());
648                }
649            }
650            $app->logMsg(sprintf('User_id %s (%s) session expired: %s', $this->get('user_id'), $this->get('username'), join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__);
651        } else {
652            $app->logMsg(sprintf('Session not authenticated: %s', getDump($_SESSION['_auth_sql'][$this->_ns])), LOG_DEBUG, __FILE__, __LINE__);
653        }
654
655        // User is not authenticated.
656        $this->clear();
657        return false;
658    }
659
660    /**
661     * Redirect user to login page if they are not logged in.
662     *
663     * @param string $message The text description of a message to raise.
664     * @param int    $type    The type of message: MSG_NOTICE,
665     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
666     * @param string $file    __FILE__.
667     * @param string $line    __LINE__.
668     * @access public
669     */
670    function requireLogin($message='', $type=MSG_NOTICE, $file=null, $line=null)
671    {
672        $app =& App::getInstance();
673   
674        if (!$this->isLoggedIn()) {
675            // Display message for requiring login. (RaiseMsg will ignore empty strings.)
676            if ('' != $message) {
677                $app->raiseMsg($message, $type, $file, $line);
678            }
679
680            // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation.
681            $app->setBoomerangURL(absoluteMe(), 'login');
682            $app->dieURL($this->_params['login_url']);
683        }
684    }
685
686    /**
687     * This sets the 'blocked' field for a user in the db_table, and also
688     * adds an optional reason
689     *
690     * @param  string   $reason      The reason for blocking the account.
691     */
692    function blockAccount($user_id=null, $reason='')
693    {
694        $app =& App::getInstance();
695        $db =& DB::getInstance();
696   
697        $this->initDB();
698
699        if ($this->getParam('blocking')) {
700            if (mb_strlen($db->escapeString($reason)) > 255) {
701                // blocked_reason field is varchar(255).
702                $app->logMsg(sprintf('Blocked reason provided is greater than 255 characters: %s', $reason), LOG_WARNING, __FILE__, __LINE__);
703            }
704
705            // Get user_id if specified.
706            $user_id = isset($user_id) ? $user_id : $this->get('user_id');
707            $db->query("
708                UPDATE " . $this->_params['db_table'] . " SET
709                blocked = 'true',
710                blocked_reason = '" . $db->escapeString($reason) . "'
711                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
712            ");
713        }
714    }
715
716    /**
717     * Tests if the "blocked" flag is set for a user.
718     *
719     * @param  int      $user_id    User id to look for.
720     * @return boolean              True if the user is blocked, false otherwise.
721     */
722    function isBlocked($user_id=null)
723    {
724        $db =& DB::getInstance();
725
726        $this->initDB();
727
728        if ($this->getParam('blocking')) {
729            // Get user_id if specified.
730            $user_id = isset($user_id) ? $user_id : $this->getVal('user_id');
731            $qid = $db->query("
732                SELECT 1
733                FROM " . $this->_params['db_table'] . "
734                WHERE blocked = 'true'
735                AND " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
736            ");
737            return mysql_num_rows($qid) === 1;
738        }
739    }
740
741    /**
742     * Unblocks a user in the db_table, and clears any blocked_reason.
743     */
744    function unblockAccount($user_id=null)
745    {
746        $db =& DB::getInstance();
747   
748        $this->initDB();
749   
750        if ($this->getParam('blocking')) {
751            // Get user_id if specified.
752            $user_id = isset($user_id) ? $user_id : $this->get('user_id');
753            $db->query("
754                UPDATE " . $this->_params['db_table'] . " SET
755                blocked = '',
756                blocked_reason = ''
757                WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
758            ");
759        }
760    }
761
762    /**
763     * Returns true if username already exists in database.
764     *
765     * @param  string  $username    Username to look for.
766     * @return bool                 True if username exists.
767     */
768    function usernameExists($username)
769    {
770        $db =& DB::getInstance();
771   
772        $this->initDB();
773
774        $qid = $db->query("
775            SELECT 1
776            FROM " . $this->_params['db_table'] . "
777            WHERE " . $this->_params['db_username_column'] . " = '" . $db->escapeString($username) . "'
778        ");
779        return (mysql_num_rows($qid) > 0);
780    }
781
782    /**
783     * Returns a username for a specified user id.
784     *
785     * @param  string  $user_id     User id to look for.
786     * @return string               Username, or false if none found.
787     */
788    function getUsername($user_id)
789    {
790        $db =& DB::getInstance();
791   
792        $this->initDB();
793
794        $qid = $db->query("
795            SELECT " . $this->_params['db_username_column'] . "
796            FROM " . $this->_params['db_table'] . "
797            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
798        ");
799        if (list($username) = mysql_fetch_row($qid)) {
800            return $username;
801        } else {
802            return false;
803        }
804    }
805
806    /**
807     * Returns a randomly generated password based on $pattern. The pattern is any
808     * sequence of 'x', 'V', 'C', 'v', 'c', or 'd' and if it is something like 'cvccv' this
809     * function will generate a pronounceable password. Recommend using more complex
810     * patterns, at minimum the US State Department standard: cvcddcvc.
811     *
812     * - x    a random upper or lower alpha character or digit
813     * - C    a random upper or lower consonant
814     * - V    a random upper or lower vowel
815     * - c    a random lowercase consonant
816     * - v    a random lowercase vowel
817     * - d    a random digit
818     *
819     * @param  string $pattern  a sequence of character types, above.
820     * @return string           a password
821     */
822    function generatePassword($pattern='CvcdCvc')
823    {
824        $app =& App::getInstance();
825        if (preg_match('/[^xCVcvd]/', $pattern)) {
826            $app->logMsg(sprintf('Invalid pattern: %s', $pattern), LOG_WARNING, __FILE__, __LINE__);
827            $pattern='CvcdCvc';
828        }
829        $str = '';
830        for ($i=0; $i<mb_strlen($pattern); $i++) {
831            $x = mb_substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZaeiouyAEIOUY0123456789!@#%&*-=+.?', (mt_rand() % 71), 1);
832            $c = mb_substr('bcdfghjklmnprstvwxz', (mt_rand() % 19), 1);
833            $C = mb_substr('bcdfghjklmnprstvwxzBCDFGHJKLMNPRSTVWXZ', (mt_rand() % 38), 1);
834            $v = mb_substr('aeiouy', (mt_rand() % 6), 1);
835            $V = mb_substr('aeiouyAEIOUY', (mt_rand() % 12), 1);
836            $d = mb_substr('0123456789', (mt_rand() % 10), 1);
837            $str .= $$pattern[$i];
838        }
839        return $str;
840    }
841
842    /**
843     *
844     */
845    function encryptPassword($password, $salt=null)
846    {
847        $app =& App::getInstance();
848       
849        // Existing password hashes rely on the same key/salt being used to compare encryptions.
850        // Don't change this (or the value applied to signing_key) unless you know existing hashes or signatures will not be affected!
851        $more_salt = 'B36D18E5-3FE4-4D58-8150-F26642852B81';
852       
853        switch ($this->_params['encryption_type']) {
854        case AUTH_ENCRYPT_PLAINTEXT :
855            return $password;
856            break;
857
858        case AUTH_ENCRYPT_CRYPT :
859            // If comparing plaintext password with a hash, provide first two chars of the hash as the salt.
860            return isset($salt) ? crypt($password, mb_substr($salt, 0, 2)) : crypt($password);
861            break;
862
863        case AUTH_ENCRYPT_SHA1 :
864            return sha1($password);
865            break;
866
867        case AUTH_ENCRYPT_SHA1_HARDENED :
868            $hash = sha1($app->getParam('signing_key') . $password . $more_salt);
869            // Increase key strength by 12 bits.
870            for ($i=0; $i < 4096; $i++) { 
871                $hash = sha1($hash); 
872            } 
873            return $hash;
874            break;
875
876        case AUTH_ENCRYPT_MD5 :
877            return md5($password);
878            break;
879
880        case AUTH_ENCRYPT_MD5_HARDENED :
881            // Include salt to improve hash
882            $hash = md5($app->getParam('signing_key') . $password . $more_salt);
883            // Increase key strength by 12 bits.
884            for ($i=0; $i < 4096; $i++) { 
885                $hash = md5($hash); 
886            } 
887            return $hash;
888            break;
889
890        default :
891            $app->logMsg(sprintf('Authentication encrypt type specified is unrecognized: %s', $this->_params['encryption_type']), LOG_NOTICE, __FILE__, __LINE__);
892            return false;
893            break;
894        }
895    }
896
897    /**
898     *
899     */
900    function setPassword($user_id=null, $password)
901    {
902        $app =& App::getInstance();
903        $db =& DB::getInstance();
904   
905        $this->initDB();
906
907        // Get user_id if specified.
908        $user_id = isset($user_id) ? $user_id : $this->get('user_id');
909
910        // Get old password.
911        $qid = $db->query("
912            SELECT userpass
913            FROM " . $this->_params['db_table'] . "
914            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
915        ");
916        if (!list($old_encrypted_password) = mysql_fetch_row($qid)) {
917            $app->logMsg(sprintf('Cannot set password for nonexistent user_id %s', $user_id), LOG_NOTICE, __FILE__, __LINE__);
918            return false;
919        }
920       
921        // Compare old with new to ensure we're actually *changing* the password.
922        $encrypted_password = $this->encryptPassword($password);
923        if ($old_encrypted_password == $encrypted_password) {
924            $app->logMsg(sprintf('Not setting password: new is the same as old.', null), LOG_INFO, __FILE__, __LINE__);
925            return false;
926        }
927
928        // Issue the password change query.
929        $db->query("
930            UPDATE " . $this->_params['db_table'] . "
931            SET userpass = '" . $db->escapeString($encrypted_password) . "'
932            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
933        ");
934       
935        if (mysql_affected_rows($db->getDBH()) != 1) {
936            $app->logMsg(sprintf('Failed to update password for user_id %s', $user_id), LOG_WARNING, __FILE__, __LINE__);
937            return false;
938        }
939       
940        return true;
941    }
942
943    /**
944     * Resets the password for the user with the specified id.
945     *
946     * @param  string $user_id   The id of the user to reset.
947     * @param  string $reason    Additional message to add to the reset email.
948     * @return string            The user's new password.
949     */
950    function resetPassword($user_id=null, $reason='')
951    {
952        $app =& App::getInstance();
953        $db =& DB::getInstance();
954   
955        $this->initDB();
956
957        // Get user_id if specified.
958        $user_id = isset($user_id) ? $user_id : $this->get('user_id');
959
960        // Reset password of a specific user.
961        $qid = $db->query("
962            SELECT * FROM " . $this->_params['db_table'] . "
963            WHERE " . $this->_params['db_primary_key'] . " = '" . $db->escapeString($user_id) . "'
964        ");
965        if (!$user_data = mysql_fetch_assoc($qid)) {
966            $app->logMsg(sprintf('Reset password failed. User_id %s not found.', $user_id), LOG_NOTICE, __FILE__, __LINE__);
967            return false;
968        }
969
970        // Get new password.
971        $password = $this->generatePassword();
972
973        // Update password query.
974        $this->setPassword($user_id, $password);
975
976        // Make sure user has an email on record before continuing.
977        if (!isset($user_data['email']) || '' == trim($user_data['email'])) {
978            $app->logMsg(sprintf('Password reset but notification failed, no email address for user_id %s (%s).', $user_data[$this->_params['db_primary_key']], $user_data[$this->_params['db_username_column']]), LOG_NOTICE, __FILE__, __LINE__);
979        } else {
980            // Send the new password in an email.
981            $email = new Email(array(
982                'to' => $user_data['email'],
983                'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')),
984                'subject' => sprintf('%s password change', $app->getParam('site_name'))
985            ));
986            $email->setTemplate('codebase/services/templates/email_reset_password.txt');
987            $email->replace(array(
988                'SITE_NAME' => $app->getParam('site_name'),
989                'SITE_URL' => $app->getParam('site_url'),
990                'SITE_EMAIL' => $app->getParam('site_email'),
991                'NAME' => ('' != $user_data['first_name'] . $user_data['last_name'] ? $user_data['first_name'] . ' ' . $user_data['last_name'] : $user_data[$this->_params['db_username_column']]),
992                'USERNAME' => $user_data[$this->_params['db_username_column']],
993                'PASSWORD' => $password,
994                'REASON' => ('' == trim($reason) ? '' : trim($reason) . ' '), // Add a space after the reason if it exists for better formatting.
995            ));
996            $email->send();
997        }
998
999        return array(
1000            'username' => $user_data[$this->_params['db_username_column']],
1001            'userpass' => $password
1002        );
1003    }
1004
1005    /**
1006     * If the current user has access to the specified $security_zone, return true.
1007     * If the optional $user_type is supplied, test that against the zone.
1008     *
1009     * NOTE: "user_type" used to be called "priv" in some older implementations.
1010     *
1011     * @param  constant $security_zone   string of comma delimited privileges for the zone
1012     * @param  string   $user_type       a privilege that might be found in a zone
1013     * @return bool     true if user is a member of security zone, false otherwise
1014     */
1015    function inClearanceZone($security_zone, $user_type='')
1016    {
1017        // return true; /// WTF?
1018        $zone_members = preg_split('/,\s*/', $security_zone);
1019        $user_type = empty($user_type) ? $this->get('user_type') : $user_type;
1020
1021        // If the current user's privilege level is NOT in that array or if the
1022        // user has no privilege, return false. Otherwise the user is clear.
1023        if (!in_array($user_type, $zone_members) || empty($user_type)) {
1024            return false;
1025        } else {
1026            return true;
1027        }
1028    }
1029
1030    /**
1031     * This function tests a list of arguments $security_zone against the priv that the current user has.
1032     * If the user doesn't have one of the supplied privs, die.
1033     *
1034     * NOTE: "user_type" used to be called "priv" in some older implementations.
1035     *
1036     * @param  constant $security_zone   string of comma delimited privileges for the zone
1037     */
1038    function requireAccessClearance($security_zone, $message='')
1039    {
1040        $app =& App::getInstance();
1041   
1042        // return true; /// WTF?
1043        $zone_members = preg_split('/,\s*/', $security_zone);
1044
1045        /* If the current user's privilege level is NOT in that array or if the
1046         * user has no privilege, DIE with a message. */
1047        if (!in_array($this->get('user_type'), $zone_members) || !$this->get('user_type')) {
1048            $message = empty($message) ? _("You have insufficient privileges to view that page.") : $message;
1049            $app->raiseMsg($message, MSG_NOTICE, __FILE__, __LINE__);
1050            $app->dieBoomerangURL();
1051        }
1052    }
1053
1054} // end class
1055
1056// CIDR cheat-sheet
1057//
1058// Netmask              Netmask (binary)                 CIDR     Notes
1059// _____________________________________________________________________________
1060// 255.255.255.255  11111111.11111111.11111111.11111111  /32  Host (single addr)
1061// 255.255.255.254  11111111.11111111.11111111.11111110  /31  Unusable
1062// 255.255.255.252  11111111.11111111.11111111.11111100  /30    2  useable
1063// 255.255.255.248  11111111.11111111.11111111.11111000  /29    6  useable
1064// 255.255.255.240  11111111.11111111.11111111.11110000  /28   14  useable
1065// 255.255.255.224  11111111.11111111.11111111.11100000  /27   30  useable
1066// 255.255.255.192  11111111.11111111.11111111.11000000  /26   62  useable
1067// 255.255.255.128  11111111.11111111.11111111.10000000  /25  126  useable
1068// 255.255.255.0    11111111.11111111.11111111.00000000  /24 "Class C" 254 useable
1069//
1070// 255.255.254.0    11111111.11111111.11111110.00000000  /23    2  Class C's
1071// 255.255.252.0    11111111.11111111.11111100.00000000  /22    4  Class C's
1072// 255.255.248.0    11111111.11111111.11111000.00000000  /21    8  Class C's
1073// 255.255.240.0    11111111.11111111.11110000.00000000  /20   16  Class C's
1074// 255.255.224.0    11111111.11111111.11100000.00000000  /19   32  Class C's
1075// 255.255.192.0    11111111.11111111.11000000.00000000  /18   64  Class C's
1076// 255.255.128.0    11111111.11111111.10000000.00000000  /17  128  Class C's
1077// 255.255.0.0      11111111.11111111.00000000.00000000  /16  "Class B"
1078//
1079// 255.254.0.0      11111111.11111110.00000000.00000000  /15    2  Class B's
1080// 255.252.0.0      11111111.11111100.00000000.00000000  /14    4  Class B's
1081// 255.248.0.0      11111111.11111000.00000000.00000000  /13    8  Class B's
1082// 255.240.0.0      11111111.11110000.00000000.00000000  /12   16  Class B's
1083// 255.224.0.0      11111111.11100000.00000000.00000000  /11   32  Class B's
1084// 255.192.0.0      11111111.11000000.00000000.00000000  /10   64  Class B's
1085// 255.128.0.0      11111111.10000000.00000000.00000000  /9   128  Class B's
1086// 255.0.0.0        11111111.00000000.00000000.00000000  /8   "Class A"
1087//
1088// 254.0.0.0        11111110.00000000.00000000.00000000  /7
1089// 252.0.0.0        11111100.00000000.00000000.00000000  /6
1090// 248.0.0.0        11111000.00000000.00000000.00000000  /5
1091// 240.0.0.0        11110000.00000000.00000000.00000000  /4
1092// 224.0.0.0        11100000.00000000.00000000.00000000  /3
1093// 192.0.0.0        11000000.00000000.00000000.00000000  /2
1094// 128.0.0.0        10000000.00000000.00000000.00000000  /1
1095// 0.0.0.0          00000000.00000000.00000000.00000000  /0   IP space
1096?>
Note: See TracBrowser for help on using the repository browser.