[ Index ]
 

Code source de Mantis 1.1.0rc3

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/core/ -> authentication_api.php (source)

   1  <?php
   2  # Mantis - a php based bugtracking system
   3  
   4  # Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
   5  # Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
   6  
   7  # Mantis is free software: you can redistribute it and/or modify
   8  # it under the terms of the GNU General Public License as published by
   9  # the Free Software Foundation, either version 2 of the License, or
  10  # (at your option) any later version.
  11  #
  12  # Mantis is distributed in the hope that it will be useful,
  13  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15  # GNU General Public License for more details.
  16  #
  17  # You should have received a copy of the GNU General Public License
  18  # along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
  19  
  20      # --------------------------------------------------------
  21      # $Id: authentication_api.php,v 1.60.2.3 2007-10-19 06:54:58 vboctor Exp $
  22      # --------------------------------------------------------
  23  
  24      require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'gpc_api.php' );
  25  
  26      ### Authentication API ###
  27  
  28      $g_script_login_cookie = null;
  29      $g_cache_anonymous_user_cookie_string = null;
  30  
  31      #===================================
  32      # Boolean queries and ensures
  33      #===================================
  34  
  35      # --------------------
  36      # Check that there is a user logged-in and authenticated
  37      #  If the user's account is disabled they will be logged out
  38      #  If there is no user logged in, redirect to the login page
  39      #  If parameter is given it is used as a URL to redirect to following
  40      #   successful login.  If none is given, the URL of the current page is used
  41  	function auth_ensure_user_authenticated( $p_return_page = '' ) {
  42          # if logged in
  43          if ( auth_is_user_authenticated() ) {
  44              # check for access enabled
  45              #  This also makes sure the cookie is valid
  46              if ( OFF == current_user_get_field( 'enabled' ) ) {
  47                  print_header_redirect( 'logout_page.php' );
  48              }
  49          } else { # not logged in
  50              if ( is_blank( $p_return_page ) ) {
  51                  if (!isset($_SERVER['REQUEST_URI'])) {
  52                      $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
  53                  }
  54                  $p_return_page = $_SERVER['REQUEST_URI'];
  55              }
  56              $p_return_page = string_url( $p_return_page );
  57              print_header_redirect( 'login_page.php?return=' . $p_return_page );
  58          }
  59      }
  60  
  61      # --------------------
  62      # Return true if there is a currently logged in and authenticated user,
  63      #  false otherwise
  64  	function auth_is_user_authenticated() {
  65          return ( auth_is_cookie_valid( auth_get_current_user_cookie() ) );
  66      }
  67  
  68  
  69      #===================================
  70      # Login / Logout
  71      #===================================
  72  
  73      # --------------------
  74      # Attempt to login the user with the given password
  75      #  If the user fails validation, false is returned
  76      #  If the user passes validation, the cookies are set and
  77      #   true is returned.  If $p_perm_login is true, the long-term
  78      #   cookie is created.
  79  	function auth_attempt_login( $p_username, $p_password, $p_perm_login=false ) {
  80          $t_user_id = user_get_id_by_name( $p_username );
  81  
  82          $t_login_method = config_get( 'login_method' );
  83  
  84          if ( false === $t_user_id ) {
  85              if ( BASIC_AUTH == $t_login_method ) {
  86                  # attempt to create the user if using BASIC_AUTH
  87                  $t_cookie_string = user_create( $p_username, $p_password );
  88  
  89                  if ( false === $t_cookie_string ) {
  90                      # it didn't work
  91                      return false;
  92                  }
  93  
  94                  # ok, we created the user, get the row again
  95                  $t_user_id = user_get_id_by_name( $p_username );
  96  
  97                  if ( false === $t_user_id ) {
  98                      # uh oh, something must be really wrong
  99  
 100                      # @@@ trigger an error here?
 101  
 102                      return false;
 103                  }
 104              } else {
 105                  return false;
 106              }
 107          }
 108  
 109          # check for disabled account
 110          if ( !user_is_enabled( $t_user_id ) ) {
 111              return false;
 112          }
 113  
 114          # max. failed login attempts achieved...
 115          if( !user_is_login_request_allowed( $t_user_id ) ) {
 116              return false;
 117          }
 118  
 119          $t_anon_account = config_get( 'anonymous_account' );
 120          $t_anon_allowed = config_get( 'allow_anonymous_login' );
 121  
 122          # check for anonymous login
 123          if ( !( ( ON == $t_anon_allowed ) && ( $t_anon_account == $p_username)  ) ) {
 124              # anonymous login didn't work, so check the password
 125  
 126              if ( !auth_does_password_match( $t_user_id, $p_password ) ) {
 127                  user_increment_failed_login_count( $t_user_id );
 128                  return false;
 129              }
 130          }
 131  
 132          # ok, we're good to login now
 133  
 134          # increment login count
 135          user_increment_login_count( $t_user_id );
 136  
 137          user_reset_failed_login_count_to_zero( $t_user_id );
 138          user_reset_lost_password_in_progress_count_to_zero( $t_user_id );
 139  
 140          # set the cookies
 141          auth_set_cookies( $t_user_id, $p_perm_login );
 142          auth_set_tokens( $t_user_id );
 143  
 144          return true;
 145      }
 146  
 147      # --------------------
 148      # Allows scripts to login using a login name or ( login name + password )
 149  	function auth_attempt_script_login( $p_username, $p_password = null ) {
 150          global $g_script_login_cookie, $g_cache_current_user_id;
 151  
 152          $t_user_id = user_get_id_by_name( $p_username );
 153  
 154          $t_user = user_get_row( $t_user_id );
 155  
 156          # check for disabled account
 157          if ( OFF == $t_user['enabled'] ) {
 158              return false;
 159          }
 160  
 161          # validate password if supplied
 162          if ( null !== $p_password ) {
 163              if ( !auth_does_password_match( $t_user_id, $p_password ) ) {
 164                  return false;
 165              }
 166          }
 167  
 168          # ok, we're good to login now
 169  
 170          # With cases like RSS feeds and MantisConnect there is a login per operation, hence, there is no
 171          # real significance of incrementing login count.
 172          # increment login count
 173          # user_increment_login_count( $t_user_id );
 174  
 175          # set the cookies
 176          $g_script_login_cookie = $t_user['cookie_string'];
 177  
 178          # cache user id for future reference
 179          $g_cache_current_user_id = $t_user_id;
 180  
 181          return true;
 182      }
 183  
 184      # --------------------
 185      # Logout the current user and remove any remaining cookies from their browser
 186      # Returns true on success, false otherwise
 187  	function auth_logout() {
 188          global $g_cache_current_user_id;
 189          
 190          # clear cached userid
 191          $g_cache_current_user_id = null;
 192          
 193          # clear cookies, if they were set  
 194          if (auth_clear_cookies()) {
 195              helper_clear_pref_cookies();
 196          }
 197          return true;
 198      }
 199  
 200      #===================================
 201      # Password functions
 202      #===================================
 203  
 204      # --------------------
 205      # Return true if the password for the user id given matches the given
 206      #  password (taking into account the global login method)
 207  	function auth_does_password_match( $p_user_id, $p_test_password ) {
 208          $t_configured_login_method = config_get( 'login_method' );
 209  
 210          if ( LDAP == $t_configured_login_method ) {
 211              return ldap_authenticate( $p_user_id, $p_test_password );
 212          }
 213  
 214          $t_password            = user_get_field( $p_user_id, 'password' );
 215          $t_login_methods    = Array(MD5, CRYPT, PLAIN);
 216          foreach ( $t_login_methods as $t_login_method ) {
 217  
 218              # pass the stored password in as the salt
 219              if ( auth_process_plain_password( $p_test_password, $t_password, $t_login_method ) == $t_password ) {
 220                  # Do not support migration to PLAIN, since this would be a crazy thing to do.
 221                  # Also if we do, then a user will be able to login by providing the MD5 value
 222                  # that is copied from the database.  See #8467 for more details.
 223                  if ( $t_configured_login_method != PLAIN && $t_login_method == PLAIN ) {
 224                      continue;
 225                  }
 226  
 227                  # Check for migration to another login method and test whether the password was encrypted
 228                  # with our previously insecure implemention of the CRYPT method
 229                  if ( ( $t_login_method != $t_configured_login_method ) ||
 230                      ( ( CRYPT == $t_configured_login_method ) && substr( $t_password, 0, 2 ) == substr( $p_test_password, 0, 2 ) ) ) {
 231                      user_set_password( $p_user_id, $p_test_password, true );
 232                  }
 233  
 234                  return true;
 235              }
 236          }
 237  
 238          return false;
 239      }
 240  
 241      # --------------------
 242      # Encrypt and return the plain password given, as appropriate for the current
 243      #  global login method.
 244      #
 245      # When generating a new password, no salt should be passed in.
 246      # When encrypting a password to compare to a stored password, the stored
 247      #  password should be passed in as salt.  If the auth method is CRYPT then
 248      #  crypt() will extract the appropriate portion of the stored password as its salt
 249  	function auth_process_plain_password( $p_password, $p_salt=null, $p_method=null ) {
 250          $t_login_method = config_get( 'login_method' );
 251          if ( $p_method !== null ) {
 252              $t_login_method = $p_method;
 253          }
 254  
 255          switch ( $t_login_method ) {
 256              case CRYPT:
 257                  # a null salt is the same as no salt, which causes a salt to be generated
 258                  # otherwise, use the salt given
 259                  $t_processed_password = crypt( $p_password, $p_salt );
 260                  break;
 261              case MD5:
 262                  $t_processed_password = md5( $p_password );
 263                  break;
 264              case BASIC_AUTH:
 265              case PLAIN:
 266              default:
 267                  $t_processed_password = $p_password;
 268                  break;
 269          }
 270  
 271          # cut this off to 32 cahracters which the largest possible string in the database
 272          return substr( $t_processed_password, 0, 32 );
 273      }
 274  
 275      # --------------------
 276      # Generate a random 12 character password
 277      # p_email is unused
 278  	function auth_generate_random_password( $p_email ) {
 279          $t_val = mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() );
 280          $t_val = md5( $t_val );
 281  
 282          return substr( $t_val, 0, 12 );
 283      }
 284  
 285      # --------------------
 286      # Generate a confirm_hash 12 character to valide the password reset request
 287  	function auth_generate_confirm_hash( $p_user_id ) {
 288          $t_confirm_hash_generator = config_get( 'password_confirm_hash_magic_string' );
 289          $t_password = user_get_field( $p_user_id, 'password' );
 290          $t_last_visit = user_get_field( $p_user_id, 'last_visit' );
 291  
 292          $t_confirm_hash = md5( $t_confirm_hash_generator . $t_password . $t_last_visit );
 293  
 294          return $t_confirm_hash;
 295      }
 296  
 297      #===================================
 298      # Cookie functions
 299      #===================================
 300  
 301      # --------------------
 302      # Set login cookies for the user
 303      #  If $p_perm_login is true, a long-term cookie is created
 304  	function auth_set_cookies( $p_user_id, $p_perm_login=false ) {
 305          $t_cookie_string = user_get_field( $p_user_id, 'cookie_string' );
 306  
 307          $t_cookie_name = config_get( 'string_cookie' );
 308  
 309          if ( $p_perm_login ) {
 310              # set permanent cookie (1 year)
 311              gpc_set_cookie( $t_cookie_name, $t_cookie_string, true );
 312          } else {
 313              # set temp cookie, cookie dies after browser closes
 314              gpc_set_cookie( $t_cookie_name, $t_cookie_string, false );
 315          }
 316      }
 317  
 318      # --------------------
 319      # Clear login cookies, return true if they were cleared
 320  	function auth_clear_cookies() {
 321          global $g_script_login_cookie;
 322  
 323          $t_cookies_cleared = false;
 324          
 325          # clear cookie, if not logged in from script
 326          if ($g_script_login_cookie == null) {
 327              $t_cookie_name =  config_get( 'string_cookie' );
 328              $t_cookie_path = config_get( 'cookie_path' );
 329  
 330              gpc_clear_cookie( $t_cookie_name, $t_cookie_path );
 331              $t_cookies_cleared = true;
 332          } else {
 333              $g_script_login_cookie = null;
 334          }
 335          return $t_cookies_cleared;
 336      }
 337  
 338      # --------------------
 339      # Generate a string to use as the identifier for the login cookie
 340      # It is not guaranteed to be unique and should be checked
 341      # The string returned should be 64 characters in length
 342  	function auth_generate_cookie_string() {
 343          $t_val = mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() );
 344          $t_val = md5( $t_val ) . md5( time() );
 345  
 346          return substr( $t_val, 0, 64 );
 347      }
 348  
 349      # --------------------
 350      # Generate a UNIQUE string to use as the identifier for the login cookie
 351      # The string returned should be 64 characters in length
 352  	function auth_generate_unique_cookie_string() {
 353          do {
 354              $t_cookie_string = auth_generate_cookie_string();
 355          } while ( !auth_is_cookie_string_unique( $t_cookie_string ) );
 356  
 357          return $t_cookie_string;
 358      }
 359  
 360      # --------------------
 361      # Return true if the cookie login identifier is unique, false otherwise
 362  	function auth_is_cookie_string_unique( $p_cookie_string ) {
 363          $t_user_table = config_get( 'mantis_user_table' );
 364  
 365          $c_cookie_string = db_prepare_string( $p_cookie_string );
 366  
 367          $query = "SELECT COUNT(*)
 368                    FROM $t_user_table
 369                    WHERE cookie_string='$c_cookie_string'";
 370          $result = db_query( $query );
 371          $t_count = db_result( $result );
 372  
 373          if ( $t_count > 0 ) {
 374              return false;
 375          } else {
 376              return true;
 377          }
 378      }
 379  
 380      # --------------------
 381      # Return the current user login cookie string,
 382      # note that the cookie cached by a script login superceeds the cookie provided by
 383      #  the browser. This shouldn't normally matter, except that the password verification uses
 384      #  this routine to bypass the normal authentication, and can get confused when a normal user
 385      #  logs in, then runs the verify script. the act of fetching config variables may get the wrong
 386      #  userid.
 387      # if no user is logged in and anonymous login is enabled, returns cookie for anonymous user
 388      # otherwise returns '' (an empty string)
 389  	function auth_get_current_user_cookie() {
 390          global $g_script_login_cookie, $g_cache_anonymous_user_cookie_string;
 391   
 392          # if logging in via a script, return that cookie
 393          if ( $g_script_login_cookie !== null ) {
 394              return $g_script_login_cookie;
 395          }
 396              
 397          # fetch user cookie 
 398          $t_cookie_name = config_get( 'string_cookie' );
 399          $t_cookie = gpc_get_cookie( $t_cookie_name, '' );
 400  
 401          # if cookie not found, and anonymous login enabled, use cookie of anonymous account.
 402          if ( is_blank( $t_cookie ) ) {
 403              if ( ON == config_get( 'allow_anonymous_login' ) ) {
 404                  if ( $g_cache_anonymous_user_cookie_string === null ) {
 405                      if ( function_exists( 'db_is_connected' ) && db_is_connected() ) { 
 406                          # get anonymous information if database is available
 407                          $query = sprintf('SELECT id, cookie_string FROM %s WHERE username = \'%s\'',
 408                                  config_get( 'mantis_user_table' ), config_get( 'anonymous_account' ) );
 409                          $result = db_query( $query );
 410                          
 411                          if ( 1 == db_num_rows( $result ) ) {
 412                              $row = db_fetch_array( $result );
 413                              $t_cookie = $row['cookie_string'];
 414  
 415                              $g_cache_anonymous_user_cookie_string = $t_cookie;
 416                              $g_cache_current_user_id = $row['id'];
 417                          }
 418                      }
 419                  } else {
 420                      $t_cookie = $g_cache_anonymous_user_cookie_string;
 421                  }
 422              }
 423          }
 424  
 425          return $t_cookie;
 426      }
 427  
 428      #===================================
 429      # Re-Authentication Tokens
 430      #===================================
 431  
 432      /**
 433       * Set authentication tokens for secure session.
 434       * @param integer User ID
 435       */
 436  	function auth_set_tokens( $p_user_id ) {
 437          $t_auth_token = token_get( TOKEN_AUTHENTICATED, $p_user_id );
 438          if ( null == $t_auth_token ) {
 439              token_set( TOKEN_AUTHENTICATED, true, TOKEN_EXPIRY_AUTHENTICATED, $p_user_id );
 440          } else {
 441              token_touch( $t_auth_token['id'], TOKEN_EXPIRY_AUTHENTICATED );
 442          }
 443      }
 444  
 445      /**
 446       * Check for authentication tokens, and display re-authentication page if needed.
 447       * Currently, if using BASIC or HTTP authentication methods, or if logged in anonymously, 
 448       * this function will always "authenticate" the user (do nothing).
 449       */
 450  	function auth_reauthenticate() {
 451          if ( BASIC_AUTH == config_get( 'login_method' ) ||
 452                  HTTP_AUTH == config_get( 'login_method' ) ) {
 453              return true;
 454          }
 455  
 456          $t_auth_token = token_get( TOKEN_AUTHENTICATED );
 457          if ( null != $t_auth_token ) {
 458              token_touch( $t_auth_token['id'], TOKEN_EXPIRY_AUTHENTICATED );
 459              return true;
 460          } else {
 461              $t_anon_account = config_get( 'anonymous_account' );
 462              $t_anon_allowed = config_get( 'allow_anonymous_login' );
 463  
 464              $t_user_id = auth_get_current_user_id();
 465              $t_username = user_get_field( $t_user_id, 'username' );
 466  
 467              # check for anonymous login
 468              if ( ON == $t_anon_allowed && $t_anon_account == $t_username ) {
 469                  return true;
 470              }
 471      
 472              return auth_reauthenticate_page( $t_user_id, $t_username );
 473          }
 474      }
 475  
 476      /**
 477       * Generate the intermediate authentication page.
 478       * @param integer User ID
 479       * @param string Username
 480       */
 481  	function auth_reauthenticate_page( $p_user_id, $p_username ) {
 482          $t_error = false;
 483  
 484          if ( true == gpc_get_bool( '_authenticate' ) ) {
 485              $f_password     = gpc_get_string( 'password', '' );
 486                          
 487              if ( auth_attempt_login( $p_username, $f_password ) ) {
 488                  auth_set_tokens( $p_user_id );
 489                  return true;
 490              } else {
 491                  $t_error = true;
 492              }
 493          }
 494  
 495          html_page_top1();
 496          html_page_top2();
 497  
 498  ?>
 499  <div align="center">
 500  <p>
 501  <?php 
 502          echo lang_get( 'reauthenticate_message' ); 
 503          if ( $t_error != false ) {
 504              echo '<br/><font color="red">',lang_get( 'login_error' ),'</font>';
 505          }
 506  ?>
 507  </p>
 508  <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
 509  
 510  <?php
 511          print_hidden_inputs( gpc_strip_slashes( $_POST ) );
 512          print_hidden_inputs( gpc_strip_slashes( $_GET ) );
 513  ?>
 514  
 515  <input type="hidden" name="_authenticate" value="1" />
 516  
 517  <table class="width50 center">
 518  <tr>
 519      <td class="form-title"><?php echo lang_get( 'reauthenticate_title' ); ?></td>
 520  </tr>
 521  
 522  <tr class="row-1">
 523      <td class="category"><?php echo lang_get( 'username' ); ?></td>
 524      <td><input type="text" disabled="disabled" size="32" maxlength="32" value="<?php echo $p_username; ?>" /></td>
 525  </tr>
 526  
 527  <tr class="row-2">
 528      <td class="category"><?php echo lang_get( 'password' ); ?></td>
 529      <td><input type="password" name="password" size="16" maxlength="32" /></td>
 530  </tr>
 531  
 532  <tr>
 533      <td class="center" colspan="2"><input type="submit" class="button" value="<?php echo lang_get( 'login_button' ); ?>" /></td>
 534  </tr>
 535  </table>
 536  
 537  </form>
 538  </div>
 539  
 540          <?php
 541          html_page_bottom1();
 542  
 543          exit;
 544      }
 545  
 546      #===================================
 547      # Data Access
 548      #===================================
 549  
 550      #########################################
 551      # is cookie valid?
 552  
 553  	function auth_is_cookie_valid( $p_cookie_string ) {
 554          global $g_cache_current_user_id;
 555          
 556          # fail if DB isn't accessible
 557          if ( !db_is_connected() ) {
 558              return false;
 559          }
 560  
 561          # fail if cookie is blank
 562          if ( '' === $p_cookie_string ) {
 563              return false;
 564          }
 565  
 566          # succeeed if user has already been authenticated
 567          if ( null !== $g_cache_current_user_id ) {
 568              return true;
 569          }
 570          
 571          # look up cookie in the database to see if it is valid
 572          $t_user_table = config_get( 'mantis_user_table' );
 573  
 574          $c_cookie_string = db_prepare_string( $p_cookie_string );
 575  
 576          $query = "SELECT id
 577                    FROM $t_user_table
 578                    WHERE cookie_string='$c_cookie_string'";
 579          $result = db_query( $query );
 580  
 581          # return true if a matching cookie was found
 582           return ( 1 == db_num_rows( $result ) );
 583      }
 584  
 585      #########################################
 586      # SECURITY NOTE: cache globals are initialized here to prevent them
 587      #   being spoofed if register_globals is turned on
 588      #
 589      $g_cache_current_user_id = null;
 590  
 591  	function auth_get_current_user_id() {
 592          global $g_cache_current_user_id;
 593  
 594          if ( null !== $g_cache_current_user_id ) {
 595              return $g_cache_current_user_id;
 596          }
 597  
 598          $t_user_table = config_get( 'mantis_user_table' );
 599  
 600          $t_cookie_string = auth_get_current_user_cookie();
 601  
 602          # @@@ error with an error saying they aren't logged in?
 603          #     Or redirect to the login page maybe?
 604  
 605          $c_cookie_string = db_prepare_string( $t_cookie_string );
 606  
 607          $query = "SELECT id
 608                    FROM $t_user_table
 609                    WHERE cookie_string='$c_cookie_string'";
 610          $result = db_query( $query );
 611  
 612          # The cookie was invalid. Clear the cookie (to allow people to log in again)
 613          # and give them an Access Denied message.
 614          if ( db_num_rows( $result ) < 1 ) {
 615              auth_clear_cookies();
 616              access_denied(); # never returns
 617              return false;
 618          }
 619  
 620          $t_user_id = (int)db_result( $result );
 621          $g_cache_current_user_id = $t_user_id;
 622  
 623          return $t_user_id;
 624      }
 625  
 626      #===================================
 627      # HTTP Auth
 628      #===================================
 629  
 630  	function auth_http_prompt() {
 631          header( "HTTP/1.0 401 Authorization Required" );
 632          header( "WWW-Authenticate: Basic realm=\"" . lang_get( 'http_auth_realm' ) . "\"" );
 633          header( 'status: 401 Unauthorized' );
 634  
 635          echo '<center>';
 636          echo '<p>'.error_string(ERROR_ACCESS_DENIED).'</p>';
 637          print_bracket_link( 'main_page.php', lang_get( 'proceed' ) );
 638          echo '</center>';
 639  
 640          exit;
 641      }
 642  
 643  	function auth_http_set_logout_pending( $p_pending ) {
 644          $t_cookie_name = config_get( 'logout_cookie' );
 645  
 646          if ( $p_pending ) {
 647              gpc_set_cookie( $t_cookie_name, "1", false );
 648          } else {
 649              $t_cookie_path = config_get( 'cookie_path' );
 650              gpc_clear_cookie( $t_cookie_name, $t_cookie_path );
 651          }
 652      }
 653  
 654  	function auth_http_is_logout_pending() {
 655          $t_cookie_name = config_get( 'logout_cookie' );
 656          $t_cookie = gpc_get_cookie( $t_cookie_name, '' );
 657  
 658          return( $t_cookie > '' );
 659      }
 660  ?>


Généré le : Thu Nov 29 09:42:17 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics