Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 35 and 311] [Versions 36 and 311] [Versions 37 and 311] [Versions 38 and 311]

       1  <?php
       2  
       3  /**
       4   * Licensed to Jasig under one or more contributor license
       5   * agreements. See the NOTICE file distributed with this work for
       6   * additional information regarding copyright ownership.
       7   *
       8   * Jasig licenses this file to you under the Apache License,
       9   * Version 2.0 (the "License"); you may not use this file except in
      10   * compliance with the License. You may obtain a copy of the License at:
      11   *
      12   * http://www.apache.org/licenses/LICENSE-2.0
      13   *
      14   * Unless required by applicable law or agreed to in writing, software
      15   * distributed under the License is distributed on an "AS IS" BASIS,
      16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      17   * See the License for the specific language governing permissions and
      18   * limitations under the License.
      19   *
      20   * PHP Version 5
      21   *
      22   * @file     CAS/Client.php
      23   * @category Authentication
      24   * @package  PhpCAS
      25   * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
      26   * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
      27   * @author   Brett Bieber <brett.bieber@gmail.com>
      28   * @author   Joachim Fritschi <jfritschi@freenet.de>
      29   * @author   Adam Franco <afranco@middlebury.edu>
      30   * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
      31   * @link     https://wiki.jasig.org/display/CASC/phpCAS
      32   */
      33  
      34  /**
      35   * The CAS_Client class is a client interface that provides CAS authentication
      36   * to PHP applications.
      37   *
      38   * @class    CAS_Client
      39   * @category Authentication
      40   * @package  PhpCAS
      41   * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
      42   * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
      43   * @author   Brett Bieber <brett.bieber@gmail.com>
      44   * @author   Joachim Fritschi <jfritschi@freenet.de>
      45   * @author   Adam Franco <afranco@middlebury.edu>
      46   * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
      47   * @link     https://wiki.jasig.org/display/CASC/phpCAS
      48   *
      49   */
      50  
      51  class CAS_Client
      52  {
      53  
      54      // ########################################################################
      55      //  HTML OUTPUT
      56      // ########################################################################
      57      /**
      58      * @addtogroup internalOutput
      59      * @{
      60      */
      61  
      62      /**
      63       * This method filters a string by replacing special tokens by appropriate values
      64       * and prints it. The corresponding tokens are taken into account:
      65       * - __CAS_VERSION__
      66       * - __PHPCAS_VERSION__
      67       * - __SERVER_BASE_URL__
      68       *
      69       * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
      70       *
      71       * @param string $str the string to filter and output
      72       *
      73       * @return void
      74       */
      75      private function _htmlFilterOutput($str)
      76      {
      77          $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
      78          $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
      79          $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str);
      80          echo $str;
      81      }
      82  
      83      /**
      84       * A string used to print the header of HTML pages. Written by
      85       * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader().
      86       *
      87       * @hideinitializer
      88       * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
      89       */
      90      private $_output_header = '';
      91  
      92      /**
      93       * This method prints the header of the HTML output (after filtering). If
      94       * CAS_Client::setHTMLHeader() was not used, a default header is output.
      95       *
      96       * @param string $title the title of the page
      97       *
      98       * @return void
      99       * @see _htmlFilterOutput()
     100       */
     101      public function printHTMLHeader($title)
     102      {
     103          $this->_htmlFilterOutput(
     104              str_replace(
     105                  '__TITLE__', $title,
     106                  (empty($this->_output_header)
     107                  ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
     108                  : $this->_output_header)
     109              )
     110          );
     111      }
     112  
     113      /**
     114       * A string used to print the footer of HTML pages. Written by
     115       * CAS_Client::setHTMLFooter(), read by printHTMLFooter().
     116       *
     117       * @hideinitializer
     118       * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
     119       */
     120      private $_output_footer = '';
     121  
     122      /**
     123       * This method prints the footer of the HTML output (after filtering). If
     124       * CAS_Client::setHTMLFooter() was not used, a default footer is output.
     125       *
     126       * @return void
     127       * @see _htmlFilterOutput()
     128       */
     129      public function printHTMLFooter()
     130      {
     131          $lang = $this->getLangObj();
     132          $this->_htmlFilterOutput(
     133              empty($this->_output_footer)?
     134              (phpCAS::getVerbose())?
     135                  '<hr><address>phpCAS __PHPCAS_VERSION__ '
     136                  .$lang->getUsingServer()
     137                  .' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>'
     138                  :'</body></html>'
     139              :$this->_output_footer
     140          );
     141      }
     142  
     143      /**
     144       * This method set the HTML header used for all outputs.
     145       *
     146       * @param string $header the HTML header.
     147       *
     148       * @return void
     149       */
     150      public function setHTMLHeader($header)
     151      {
     152      	 // Argument Validation
     153      	 if (gettype($header) != 'string')
     154          	 throw new CAS_TypeMismatchException($header, '$header', 'string');
     155  
     156          $this->_output_header = $header;
     157      }
     158  
     159      /**
     160       * This method set the HTML footer used for all outputs.
     161       *
     162       * @param string $footer the HTML footer.
     163       *
     164       * @return void
     165       */
     166      public function setHTMLFooter($footer)
     167      {
     168      	 // Argument Validation
     169      	 if (gettype($footer) != 'string')
     170          	 throw new CAS_TypeMismatchException($footer, '$footer', 'string');
     171  
     172          $this->_output_footer = $footer;
     173      }
     174  
     175  
     176      /** @} */
     177  
     178  
     179      // ########################################################################
     180      //  INTERNATIONALIZATION
     181      // ########################################################################
     182      /**
     183      * @addtogroup internalLang
     184      * @{
     185      */
     186      /**
     187       * A string corresponding to the language used by phpCAS. Written by
     188       * CAS_Client::setLang(), read by CAS_Client::getLang().
     189  
     190       * @note debugging information is always in english (debug purposes only).
     191       */
     192      private $_lang = PHPCAS_LANG_DEFAULT;
     193  
     194      /**
     195       * This method is used to set the language used by phpCAS.
     196       *
     197       * @param string $lang representing the language.
     198       *
     199       * @return void
     200       */
     201      public function setLang($lang)
     202      {
     203      	 // Argument Validation
     204      	 if (gettype($lang) != 'string')
     205          	 throw new CAS_TypeMismatchException($lang, '$lang', 'string');
     206  
     207          phpCAS::traceBegin();
     208          $obj = new $lang();
     209          if (!($obj instanceof CAS_Languages_LanguageInterface)) {
     210              throw new CAS_InvalidArgumentException(
     211                  '$className must implement the CAS_Languages_LanguageInterface'
     212              );
     213          }
     214          $this->_lang = $lang;
     215          phpCAS::traceEnd();
     216      }
     217      /**
     218       * Create the language
     219       *
     220       * @return CAS_Languages_LanguageInterface object implementing the class
     221       */
     222      public function getLangObj()
     223      {
     224          $classname = $this->_lang;
     225          return new $classname();
     226      }
     227  
     228      /** @} */
     229      // ########################################################################
     230      //  CAS SERVER CONFIG
     231      // ########################################################################
     232      /**
     233      * @addtogroup internalConfig
     234      * @{
     235      */
     236  
     237      /**
     238       * a record to store information about the CAS server.
     239       * - $_server['version']: the version of the CAS server
     240       * - $_server['hostname']: the hostname of the CAS server
     241       * - $_server['port']: the port the CAS server is running on
     242       * - $_server['uri']: the base URI the CAS server is responding on
     243       * - $_server['base_url']: the base URL of the CAS server
     244       * - $_server['login_url']: the login URL of the CAS server
     245       * - $_server['service_validate_url']: the service validating URL of the
     246       *   CAS server
     247       * - $_server['proxy_url']: the proxy URL of the CAS server
     248       * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server
     249       * - $_server['logout_url']: the logout URL of the CAS server
     250       *
     251       * $_server['version'], $_server['hostname'], $_server['port'] and
     252       * $_server['uri'] are written by CAS_Client::CAS_Client(), read by
     253       * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(),
     254       * CAS_Client::_getServerPort() and CAS_Client::_getServerURI().
     255       *
     256       * The other fields are written and read by CAS_Client::_getServerBaseURL(),
     257       * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
     258       * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
     259       *
     260       * @hideinitializer
     261       */
     262      private $_server = array(
     263          'version' => '',
     264          'hostname' => 'none',
     265          'port' => -1,
     266          'uri' => 'none');
     267  
     268      /**
     269       * This method is used to retrieve the version of the CAS server.
     270       *
     271       * @return string the version of the CAS server.
     272       */
     273      public function getServerVersion()
     274      {
     275          return $this->_server['version'];
     276      }
     277  
     278      /**
     279       * This method is used to retrieve the hostname of the CAS server.
     280       *
     281       * @return string the hostname of the CAS server.
     282       */
     283      private function _getServerHostname()
     284      {
     285          return $this->_server['hostname'];
     286      }
     287  
     288      /**
     289       * This method is used to retrieve the port of the CAS server.
     290       *
     291       * @return int the port of the CAS server.
     292       */
     293      private function _getServerPort()
     294      {
     295          return $this->_server['port'];
     296      }
     297  
     298      /**
     299       * This method is used to retrieve the URI of the CAS server.
     300       *
     301       * @return string a URI.
     302       */
     303      private function _getServerURI()
     304      {
     305          return $this->_server['uri'];
     306      }
     307  
     308      /**
     309       * This method is used to retrieve the base URL of the CAS server.
     310       *
     311       * @return string a URL.
     312       */
     313      private function _getServerBaseURL()
     314      {
     315          // the URL is build only when needed
     316          if ( empty($this->_server['base_url']) ) {
     317              $this->_server['base_url'] = 'https://' . $this->_getServerHostname();
     318              if ($this->_getServerPort()!=443) {
     319                  $this->_server['base_url'] .= ':'
     320                  .$this->_getServerPort();
     321              }
     322              $this->_server['base_url'] .= $this->_getServerURI();
     323          }
     324          return $this->_server['base_url'];
     325      }
     326  
     327      /**
     328       * This method is used to retrieve the login URL of the CAS server.
     329       *
     330       * @param bool $gateway true to check authentication, false to force it
     331       * @param bool $renew   true to force the authentication with the CAS server
     332       *
     333       * @return string a URL.
     334       * @note It is recommended that CAS implementations ignore the "gateway"
     335       * parameter if "renew" is set
     336       */
     337      public function getServerLoginURL($gateway=false,$renew=false)
     338      {
     339          phpCAS::traceBegin();
     340          // the URL is build only when needed
     341          if ( empty($this->_server['login_url']) ) {
     342              $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL()));
     343          }
     344          $url = $this->_server['login_url'];
     345          if ($renew) {
     346              // It is recommended that when the "renew" parameter is set, its
     347              // value be "true"
     348              $url = $this->_buildQueryUrl($url, 'renew=true');
     349          } elseif ($gateway) {
     350              // It is recommended that when the "gateway" parameter is set, its
     351              // value be "true"
     352              $url = $this->_buildQueryUrl($url, 'gateway=true');
     353          }
     354          phpCAS::traceEnd($url);
     355          return $url;
     356      }
     357  
     358      /**
     359       * This method sets the login URL of the CAS server.
     360       *
     361       * @param string $url the login URL
     362       *
     363       * @return string login url
     364       */
     365      public function setServerLoginURL($url)
     366      {
     367      	 // Argument Validation
     368      	 if (gettype($url) != 'string')
     369          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
     370  
     371          return $this->_server['login_url'] = $url;
     372      }
     373  
     374  
     375      /**
     376       * This method sets the serviceValidate URL of the CAS server.
     377       *
     378       * @param string $url the serviceValidate URL
     379       *
     380       * @return string serviceValidate URL
     381       */
     382      public function setServerServiceValidateURL($url)
     383      {
     384      	 // Argument Validation
     385      	 if (gettype($url) != 'string')
     386          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
     387  
     388          return $this->_server['service_validate_url'] = $url;
     389      }
     390  
     391  
     392      /**
     393       * This method sets the proxyValidate URL of the CAS server.
     394       *
     395       * @param string $url the proxyValidate URL
     396       *
     397       * @return string proxyValidate URL
     398       */
     399      public function setServerProxyValidateURL($url)
     400      {
     401      	 // Argument Validation
     402      	 if (gettype($url) != 'string')
     403          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
     404  
     405          return $this->_server['proxy_validate_url'] = $url;
     406      }
     407  
     408  
     409      /**
     410       * This method sets the samlValidate URL of the CAS server.
     411       *
     412       * @param string $url the samlValidate URL
     413       *
     414       * @return string samlValidate URL
     415       */
     416      public function setServerSamlValidateURL($url)
     417      {
     418      	 // Argument Validation
     419      	 if (gettype($url) != 'string')
     420          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
     421  
     422          return $this->_server['saml_validate_url'] = $url;
     423      }
     424  
     425  
     426      /**
     427       * This method is used to retrieve the service validating URL of the CAS server.
     428       *
     429       * @return string serviceValidate URL.
     430       */
     431      public function getServerServiceValidateURL()
     432      {
     433          phpCAS::traceBegin();
     434          // the URL is build only when needed
     435          if ( empty($this->_server['service_validate_url']) ) {
     436              switch ($this->getServerVersion()) {
     437              case CAS_VERSION_1_0:
     438                  $this->_server['service_validate_url'] = $this->_getServerBaseURL()
     439                  .'validate';
     440                  break;
     441              case CAS_VERSION_2_0:
     442                  $this->_server['service_validate_url'] = $this->_getServerBaseURL()
     443                  .'serviceValidate';
     444                  break;
     445              case CAS_VERSION_3_0:
     446                  $this->_server['service_validate_url'] = $this->_getServerBaseURL()
     447                  .'p3/serviceValidate';
     448                  break;
     449              }
     450          }
     451          $url = $this->_buildQueryUrl(
     452              $this->_server['service_validate_url'],
     453              'service='.urlencode($this->getURL())
     454          );
     455          phpCAS::traceEnd($url);
     456          return $url;
     457      }
     458      /**
     459       * This method is used to retrieve the SAML validating URL of the CAS server.
     460       *
     461       * @return string samlValidate URL.
     462       */
     463      public function getServerSamlValidateURL()
     464      {
     465          phpCAS::traceBegin();
     466          // the URL is build only when needed
     467          if ( empty($this->_server['saml_validate_url']) ) {
     468              switch ($this->getServerVersion()) {
     469              case SAML_VERSION_1_1:
     470                  $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate';
     471                  break;
     472              }
     473          }
     474  
     475          $url = $this->_buildQueryUrl(
     476              $this->_server['saml_validate_url'],
     477              'TARGET='.urlencode($this->getURL())
     478          );
     479          phpCAS::traceEnd($url);
     480          return $url;
     481      }
     482  
     483      /**
     484       * This method is used to retrieve the proxy validating URL of the CAS server.
     485       *
     486       * @return string proxyValidate URL.
     487       */
     488      public function getServerProxyValidateURL()
     489      {
     490          phpCAS::traceBegin();
     491          // the URL is build only when needed
     492          if ( empty($this->_server['proxy_validate_url']) ) {
     493              switch ($this->getServerVersion()) {
     494              case CAS_VERSION_1_0:
     495                  $this->_server['proxy_validate_url'] = '';
     496                  break;
     497              case CAS_VERSION_2_0:
     498                  $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
     499                  break;
     500              case CAS_VERSION_3_0:
     501                  $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate';
     502                  break;
     503              }
     504          }
     505          $url = $this->_buildQueryUrl(
     506              $this->_server['proxy_validate_url'],
     507              'service='.urlencode($this->getURL())
     508          );
     509          phpCAS::traceEnd($url);
     510          return $url;
     511      }
     512  
     513  
     514      /**
     515       * This method is used to retrieve the proxy URL of the CAS server.
     516       *
     517       * @return  string proxy URL.
     518       */
     519      public function getServerProxyURL()
     520      {
     521          // the URL is build only when needed
     522          if ( empty($this->_server['proxy_url']) ) {
     523              switch ($this->getServerVersion()) {
     524              case CAS_VERSION_1_0:
     525                  $this->_server['proxy_url'] = '';
     526                  break;
     527              case CAS_VERSION_2_0:
     528              case CAS_VERSION_3_0:
     529                  $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
     530                  break;
     531              }
     532          }
     533          return $this->_server['proxy_url'];
     534      }
     535  
     536      /**
     537       * This method is used to retrieve the logout URL of the CAS server.
     538       *
     539       * @return string logout URL.
     540       */
     541      public function getServerLogoutURL()
     542      {
     543          // the URL is build only when needed
     544          if ( empty($this->_server['logout_url']) ) {
     545              $this->_server['logout_url'] = $this->_getServerBaseURL().'logout';
     546          }
     547          return $this->_server['logout_url'];
     548      }
     549  
     550      /**
     551       * This method sets the logout URL of the CAS server.
     552       *
     553       * @param string $url the logout URL
     554       *
     555       * @return string logout url
     556       */
     557      public function setServerLogoutURL($url)
     558      {
     559      	 // Argument Validation
     560      	 if (gettype($url) != 'string')
     561          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
     562  
     563          return $this->_server['logout_url'] = $url;
     564      }
     565  
     566      /**
     567       * An array to store extra curl options.
     568       */
     569      private $_curl_options = array();
     570  
     571      /**
     572       * This method is used to set additional user curl options.
     573       *
     574       * @param string $key   name of the curl option
     575       * @param string $value value of the curl option
     576       *
     577       * @return void
     578       */
     579      public function setExtraCurlOption($key, $value)
     580      {
     581          $this->_curl_options[$key] = $value;
     582      }
     583  
     584      /** @} */
     585  
     586      // ########################################################################
     587      //  Change the internal behaviour of phpcas
     588      // ########################################################################
     589  
     590      /**
     591       * @addtogroup internalBehave
     592       * @{
     593       */
     594  
     595      /**
     596       * The class to instantiate for making web requests in readUrl().
     597       * The class specified must implement the CAS_Request_RequestInterface.
     598       * By default CAS_Request_CurlRequest is used, but this may be overridden to
     599       * supply alternate request mechanisms for testing.
     600       */
     601      private $_requestImplementation = 'CAS_Request_CurlRequest';
     602  
     603      /**
     604       * Override the default implementation used to make web requests in readUrl().
     605       * This class must implement the CAS_Request_RequestInterface.
     606       *
     607       * @param string $className name of the RequestImplementation class
     608       *
     609       * @return void
     610       */
     611      public function setRequestImplementation ($className)
     612      {
     613          $obj = new $className;
     614          if (!($obj instanceof CAS_Request_RequestInterface)) {
     615              throw new CAS_InvalidArgumentException(
     616                  '$className must implement the CAS_Request_RequestInterface'
     617              );
     618          }
     619          $this->_requestImplementation = $className;
     620      }
     621  
     622      /**
     623       * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session
     624       * tickets from the URL after a successful authentication.
     625       */
     626      private $_clearTicketsFromUrl = true;
     627  
     628      /**
     629       * Configure the client to not send redirect headers and call exit() on
     630       * authentication success. The normal redirect is used to remove the service
     631       * ticket from the client's URL, but for running unit tests we need to
     632       * continue without exiting.
     633       *
     634       * Needed for testing authentication
     635       *
     636       * @return void
     637       */
     638      public function setNoClearTicketsFromUrl ()
     639      {
     640          $this->_clearTicketsFromUrl = false;
     641      }
     642  
     643      /**
     644       * @var callback $_attributeParserCallbackFunction;
     645       */
     646      private $_casAttributeParserCallbackFunction = null;
     647  
     648      /**
     649       * @var array $_attributeParserCallbackArgs;
     650       */
     651      private $_casAttributeParserCallbackArgs = array();
     652  
     653      /**
     654       * Set a callback function to be run when parsing CAS attributes
     655       *
     656       * The callback function will be passed a XMLNode as its first parameter,
     657       * followed by any $additionalArgs you pass.
     658       *
     659       * @param string $function       callback function to call
     660       * @param array  $additionalArgs optional array of arguments
     661       *
     662       * @return void
     663       */
     664      public function setCasAttributeParserCallback($function, array $additionalArgs = array())
     665      {
     666          $this->_casAttributeParserCallbackFunction = $function;
     667          $this->_casAttributeParserCallbackArgs = $additionalArgs;
     668      }
     669  
     670      /** @var callable $_postAuthenticateCallbackFunction;
     671       */
     672      private $_postAuthenticateCallbackFunction = null;
     673  
     674      /**
     675       * @var array $_postAuthenticateCallbackArgs;
     676       */
     677      private $_postAuthenticateCallbackArgs = array();
     678  
     679      /**
     680       * Set a callback function to be run when a user authenticates.
     681       *
     682       * The callback function will be passed a $logoutTicket as its first parameter,
     683       * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
     684       * opaque string that can be used to map a session-id to the logout request
     685       * in order to support single-signout in applications that manage their own
     686       * sessions (rather than letting phpCAS start the session).
     687       *
     688       * phpCAS::forceAuthentication() will always exit and forward client unless
     689       * they are already authenticated. To perform an action at the moment the user
     690       * logs in (such as registering an account, performing logging, etc), register
     691       * a callback function here.
     692       *
     693       * @param callable $function       callback function to call
     694       * @param array  $additionalArgs optional array of arguments
     695       *
     696       * @return void
     697       */
     698      public function setPostAuthenticateCallback ($function, array $additionalArgs = array())
     699      {
     700          $this->_postAuthenticateCallbackFunction = $function;
     701          $this->_postAuthenticateCallbackArgs = $additionalArgs;
     702      }
     703  
     704      /**
     705       * @var callable $_signoutCallbackFunction;
     706       */
     707      private $_signoutCallbackFunction = null;
     708  
     709      /**
     710       * @var array $_signoutCallbackArgs;
     711       */
     712      private $_signoutCallbackArgs = array();
     713  
     714      /**
     715       * Set a callback function to be run when a single-signout request is received.
     716       *
     717       * The callback function will be passed a $logoutTicket as its first parameter,
     718       * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
     719       * opaque string that can be used to map a session-id to the logout request in
     720       * order to support single-signout in applications that manage their own sessions
     721       * (rather than letting phpCAS start and destroy the session).
     722       *
     723       * @param callable $function       callback function to call
     724       * @param array  $additionalArgs optional array of arguments
     725       *
     726       * @return void
     727       */
     728      public function setSingleSignoutCallback ($function, array $additionalArgs = array())
     729      {
     730          $this->_signoutCallbackFunction = $function;
     731          $this->_signoutCallbackArgs = $additionalArgs;
     732      }
     733  
     734      // ########################################################################
     735      //  Methods for supplying code-flow feedback to integrators.
     736      // ########################################################################
     737  
     738      /**
     739       * Ensure that this is actually a proxy object or fail with an exception
     740       *
     741       * @throws CAS_OutOfSequenceBeforeProxyException
     742       *
     743       * @return void
     744       */
     745      public function ensureIsProxy()
     746      {
     747          if (!$this->isProxy()) {
     748              throw new CAS_OutOfSequenceBeforeProxyException();
     749          }
     750      }
     751  
     752      /**
     753       * Mark the caller of authentication. This will help client integraters determine
     754       * problems with their code flow if they call a function such as getUser() before
     755       * authentication has occurred.
     756       *
     757       * @param bool $auth True if authentication was successful, false otherwise.
     758       *
     759       * @return null
     760       */
     761      public function markAuthenticationCall ($auth)
     762      {
     763          // store where the authentication has been checked and the result
     764          $dbg = debug_backtrace();
     765          $this->_authentication_caller = array (
     766              'file' => $dbg[1]['file'],
     767              'line' => $dbg[1]['line'],
     768              'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
     769              'result' => (boolean)$auth
     770          );
     771      }
     772      private $_authentication_caller;
     773  
     774      /**
     775       * Answer true if authentication has been checked.
     776       *
     777       * @return bool
     778       */
     779      public function wasAuthenticationCalled ()
     780      {
     781          return !empty($this->_authentication_caller);
     782      }
     783  
     784      /**
     785       * Ensure that authentication was checked. Terminate with exception if no
     786       * authentication was performed
     787       *
     788       * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
     789       *
     790       * @return void
     791       */
     792      private function _ensureAuthenticationCalled()
     793      {
     794          if (!$this->wasAuthenticationCalled()) {
     795              throw new CAS_OutOfSequenceBeforeAuthenticationCallException();
     796          }
     797      }
     798  
     799      /**
     800       * Answer the result of the authentication call.
     801       *
     802       * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
     803       * and markAuthenticationCall() didn't happen.
     804       *
     805       * @return bool
     806       */
     807      public function wasAuthenticationCallSuccessful ()
     808      {
     809          $this->_ensureAuthenticationCalled();
     810          return $this->_authentication_caller['result'];
     811      }
     812  
     813  
     814      /**
     815       * Ensure that authentication was checked. Terminate with exception if no
     816       * authentication was performed
     817       *
     818       * @throws CAS_OutOfSequenceException
     819       *
     820       * @return void
     821       */
     822      public function ensureAuthenticationCallSuccessful()
     823      {
     824          $this->_ensureAuthenticationCalled();
     825          if (!$this->_authentication_caller['result']) {
     826              throw new CAS_OutOfSequenceException(
     827                  'authentication was checked (by '
     828                  . $this->getAuthenticationCallerMethod()
     829                  . '() at ' . $this->getAuthenticationCallerFile()
     830                  . ':' . $this->getAuthenticationCallerLine()
     831                  . ') but the method returned false'
     832              );
     833          }
     834      }
     835  
     836      /**
     837       * Answer information about the authentication caller.
     838       *
     839       * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
     840       * and markAuthenticationCall() didn't happen.
     841       *
     842       * @return string the file that called authentication
     843       */
     844      public function getAuthenticationCallerFile ()
     845      {
     846          $this->_ensureAuthenticationCalled();
     847          return $this->_authentication_caller['file'];
     848      }
     849  
     850      /**
     851       * Answer information about the authentication caller.
     852       *
     853       * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
     854       * and markAuthenticationCall() didn't happen.
     855       *
     856       * @return int the line that called authentication
     857       */
     858      public function getAuthenticationCallerLine ()
     859      {
     860          $this->_ensureAuthenticationCalled();
     861          return $this->_authentication_caller['line'];
     862      }
     863  
     864      /**
     865       * Answer information about the authentication caller.
     866       *
     867       * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
     868       * and markAuthenticationCall() didn't happen.
     869       *
     870       * @return string the method that called authentication
     871       */
     872      public function getAuthenticationCallerMethod ()
     873      {
     874          $this->_ensureAuthenticationCalled();
     875          return $this->_authentication_caller['method'];
     876      }
     877  
     878      /** @} */
     879  
     880      // ########################################################################
     881      //  CONSTRUCTOR
     882      // ########################################################################
     883      /**
     884      * @addtogroup internalConfig
     885      * @{
     886      */
     887  
     888      /**
     889       * CAS_Client constructor.
     890       *
     891       * @param string $server_version  the version of the CAS server
     892       * @param bool   $proxy           true if the CAS client is a CAS proxy
     893       * @param string $server_hostname the hostname of the CAS server
     894       * @param int    $server_port     the port the CAS server is running on
     895       * @param string $server_uri      the URI the CAS server is responding on
     896       * @param bool   $changeSessionID Allow phpCAS to change the session_id
     897       *                                (Single Sign Out/handleLogoutRequests
     898       *                                is based on that change)
     899       *
     900       * @return self a newly created CAS_Client object
     901       */
     902      public function __construct(
     903          $server_version,
     904          $proxy,
     905          $server_hostname,
     906          $server_port,
     907          $server_uri,
     908          $changeSessionID = true
     909      ) {
     910  	 	 // Argument validation
     911          if (gettype($server_version) != 'string')
     912          	 throw new CAS_TypeMismatchException($server_version, '$server_version', 'string');
     913          if (gettype($proxy) != 'boolean')
     914          	 throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean');
     915          if (gettype($server_hostname) != 'string')
     916          	 throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string');
     917          if (gettype($server_port) != 'integer')
     918          	 throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer');
     919          if (gettype($server_uri) != 'string')
     920          	 throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string');
     921          if (gettype($changeSessionID) != 'boolean')
     922          	 throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean');
     923  
     924          phpCAS::traceBegin();
     925          // true : allow to change the session_id(), false session_id won't be
     926          // change and logout won't be handle because of that
     927          $this->_setChangeSessionID($changeSessionID);
     928  
     929          // skip Session Handling for logout requests and if don't want it'
     930          if (session_id()=="" && !$this->_isLogoutRequest()) {
     931              session_start();
     932              phpCAS :: trace("Starting a new session " . session_id());
     933          }
     934          // Only for debug purposes
     935          if ($this->isSessionAuthenticated()){
     936              phpCAS :: trace("Session is authenticated as: " . $_SESSION['phpCAS']['user']);
     937          } else {
     938              phpCAS :: trace("Session is not authenticated");
     939          }
     940          // are we in proxy mode ?
     941          $this->_proxy = $proxy;
     942  
     943          // Make cookie handling available.
     944          if ($this->isProxy()) {
     945              if (!isset($_SESSION['phpCAS'])) {
     946                  $_SESSION['phpCAS'] = array();
     947              }
     948              if (!isset($_SESSION['phpCAS']['service_cookies'])) {
     949                  $_SESSION['phpCAS']['service_cookies'] = array();
     950              }
     951              $this->_serviceCookieJar = new CAS_CookieJar(
     952                  $_SESSION['phpCAS']['service_cookies']
     953              );
     954          }
     955  
     956          // check version
     957          $supportedProtocols = phpCAS::getSupportedProtocols();
     958          if (isset($supportedProtocols[$server_version]) === false) {
     959              phpCAS::error(
     960                  'this version of CAS (`'.$server_version
     961                  .'\') is not supported by phpCAS '.phpCAS::getVersion()
     962              );
     963          }
     964  
     965          if ($server_version === CAS_VERSION_1_0 && $this->isProxy()) {
     966              phpCAS::error(
     967                  'CAS proxies are not supported in CAS '.$server_version
     968              );
     969          }
     970  
     971          $this->_server['version'] = $server_version;
     972  
     973          // check hostname
     974          if ( empty($server_hostname)
     975              || !preg_match('/[\.\d\-a-z]*/', $server_hostname)
     976          ) {
     977              phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
     978          }
     979          $this->_server['hostname'] = $server_hostname;
     980  
     981          // check port
     982          if ( $server_port == 0
     983              || !is_int($server_port)
     984          ) {
     985              phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
     986          }
     987          $this->_server['port'] = $server_port;
     988  
     989          // check URI
     990          if ( !preg_match('/[\.\d\-_a-z\/]*/', $server_uri) ) {
     991              phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
     992          }
     993          // add leading and trailing `/' and remove doubles
     994          if(strstr($server_uri, '?') === false) $server_uri .= '/';
     995          $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri);
     996          $this->_server['uri'] = $server_uri;
     997  
     998          // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
     999          if ( $this->isProxy() ) {
    1000              if(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])) {
    1001                  $this->_setCallbackMode(true);
    1002                  $this->_setCallbackModeUsingPost(false);
    1003              } elseif (!empty($_POST['pgtIou'])&&!empty($_POST['pgtId'])) {
    1004                  $this->_setCallbackMode(true);
    1005                  $this->_setCallbackModeUsingPost(true);
    1006              } else {
    1007                  $this->_setCallbackMode(false);
    1008                  $this->_setCallbackModeUsingPost(false);
    1009              }
    1010  
    1011              
    1012          }
    1013  
    1014          if ( $this->_isCallbackMode() ) {
    1015              //callback mode: check that phpCAS is secured
    1016              if ( !$this->_isHttps() ) {
    1017                  phpCAS::error(
    1018                      'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'
    1019                  );
    1020              }
    1021          } else {
    1022              //normal mode: get ticket and remove it from CGI parameters for
    1023              // developers
    1024              $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
    1025              if (preg_match('/^[SP]T-/', $ticket) ) {
    1026                  phpCAS::trace('Ticket \''.$ticket.'\' found');
    1027                  $this->setTicket($ticket);
    1028                  unset($_GET['ticket']);
    1029              } else if ( !empty($ticket) ) {
    1030                  //ill-formed ticket, halt
    1031                  phpCAS::error(
    1032                      'ill-formed ticket found in the URL (ticket=`'
    1033                      .htmlentities($ticket).'\')'
    1034                  );
    1035              }
    1036  
    1037          }
    1038          phpCAS::traceEnd();
    1039      }
    1040  
    1041      /** @} */
    1042  
    1043      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1044      // XX                                                                    XX
    1045      // XX                           Session Handling                         XX
    1046      // XX                                                                    XX
    1047      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1048  
    1049      /**
    1050       * @addtogroup internalConfig
    1051       * @{
    1052       */
    1053  
    1054  
    1055      /**
    1056       * @var bool A variable to whether phpcas will use its own session handling. Default = true
    1057       * @hideinitializer
    1058       */
    1059      private $_change_session_id = true;
    1060  
    1061      /**
    1062       * Set a parameter whether to allow phpCAS to change session_id
    1063       *
    1064       * @param bool $allowed allow phpCAS to change session_id
    1065       *
    1066       * @return void
    1067       */
    1068      private function _setChangeSessionID($allowed)
    1069      {
    1070          $this->_change_session_id = $allowed;
    1071      }
    1072  
    1073      /**
    1074       * Get whether phpCAS is allowed to change session_id
    1075       *
    1076       * @return bool
    1077       */
    1078      public function getChangeSessionID()
    1079      {
    1080          return $this->_change_session_id;
    1081      }
    1082  
    1083      /** @} */
    1084  
    1085      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1086      // XX                                                                    XX
    1087      // XX                           AUTHENTICATION                           XX
    1088      // XX                                                                    XX
    1089      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1090  
    1091      /**
    1092       * @addtogroup internalAuthentication
    1093       * @{
    1094       */
    1095  
    1096      /**
    1097       * The Authenticated user. Written by CAS_Client::_setUser(), read by
    1098       * CAS_Client::getUser().
    1099       *
    1100       * @hideinitializer
    1101       */
    1102      private $_user = '';
    1103  
    1104      /**
    1105       * This method sets the CAS user's login name.
    1106       *
    1107       * @param string $user the login name of the authenticated user.
    1108       *
    1109       * @return void
    1110       */
    1111      private function _setUser($user)
    1112      {
    1113          $this->_user = $user;
    1114      }
    1115  
    1116      /**
    1117       * This method returns the CAS user's login name.
    1118       *
    1119       * @return string the login name of the authenticated user
    1120       *
    1121       * @warning should be called only after CAS_Client::forceAuthentication() or
    1122       * CAS_Client::isAuthenticated(), otherwise halt with an error.
    1123       */
    1124      public function getUser()
    1125      {
    1126      	 // Sequence validation
    1127      	 $this->ensureAuthenticationCallSuccessful();
    1128  
    1129      	 return $this->_getUser();
    1130      }
    1131  
    1132      /**
    1133       * This method returns the CAS user's login name.
    1134       *
    1135       * @return string the login name of the authenticated user
    1136       *
    1137       * @warning should be called only after CAS_Client::forceAuthentication() or
    1138       * CAS_Client::isAuthenticated(), otherwise halt with an error.
    1139       */
    1140      private function _getUser()
    1141      {
    1142      	 // This is likely a duplicate check that could be removed....
    1143          if ( empty($this->_user) ) {
    1144              phpCAS::error(
    1145                  'this method should be used only after '.__CLASS__
    1146                  .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
    1147              );
    1148          }
    1149          return $this->_user;
    1150      }
    1151  
    1152      /**
    1153       * The Authenticated users attributes. Written by
    1154       * CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
    1155       * @attention client applications should use phpCAS::getAttributes().
    1156       *
    1157       * @hideinitializer
    1158       */
    1159      private $_attributes = array();
    1160  
    1161      /**
    1162       * Set an array of attributes
    1163       *
    1164       * @param array $attributes a key value array of attributes
    1165       *
    1166       * @return void
    1167       */
    1168      public function setAttributes($attributes)
    1169      {
    1170          $this->_attributes = $attributes;
    1171      }
    1172  
    1173      /**
    1174       * Get an key values arry of attributes
    1175       *
    1176       * @return array of attributes
    1177       */
    1178      public function getAttributes()
    1179      {
    1180      	 // Sequence validation
    1181      	 $this->ensureAuthenticationCallSuccessful();
    1182      	 // This is likely a duplicate check that could be removed....
    1183          if ( empty($this->_user) ) {
    1184              // if no user is set, there shouldn't be any attributes also...
    1185              phpCAS::error(
    1186                  'this method should be used only after '.__CLASS__
    1187                  .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
    1188              );
    1189          }
    1190          return $this->_attributes;
    1191      }
    1192  
    1193      /**
    1194       * Check whether attributes are available
    1195       *
    1196       * @return bool attributes available
    1197       */
    1198      public function hasAttributes()
    1199      {
    1200      	 // Sequence validation
    1201      	 $this->ensureAuthenticationCallSuccessful();
    1202  
    1203          return !empty($this->_attributes);
    1204      }
    1205      /**
    1206       * Check whether a specific attribute with a name is available
    1207       *
    1208       * @param string $key name of attribute
    1209       *
    1210       * @return bool is attribute available
    1211       */
    1212      public function hasAttribute($key)
    1213      {
    1214      	 // Sequence validation
    1215      	 $this->ensureAuthenticationCallSuccessful();
    1216  
    1217          return $this->_hasAttribute($key);
    1218      }
    1219  
    1220      /**
    1221       * Check whether a specific attribute with a name is available
    1222       *
    1223       * @param string $key name of attribute
    1224       *
    1225       * @return bool is attribute available
    1226       */
    1227      private function _hasAttribute($key)
    1228      {
    1229          return (is_array($this->_attributes)
    1230              && array_key_exists($key, $this->_attributes));
    1231      }
    1232  
    1233      /**
    1234       * Get a specific attribute by name
    1235       *
    1236       * @param string $key name of attribute
    1237       *
    1238       * @return string attribute values
    1239       */
    1240      public function getAttribute($key)
    1241      {
    1242      	 // Sequence validation
    1243      	 $this->ensureAuthenticationCallSuccessful();
    1244  
    1245          if ($this->_hasAttribute($key)) {
    1246              return $this->_attributes[$key];
    1247          }
    1248      }
    1249  
    1250      /**
    1251       * This method is called to renew the authentication of the user
    1252       * If the user is authenticated, renew the connection
    1253       * If not, redirect to CAS
    1254       *
    1255       * @return bool true when the user is authenticated; otherwise halt.
    1256       */
    1257      public function renewAuthentication()
    1258      {
    1259          phpCAS::traceBegin();
    1260          // Either way, the user is authenticated by CAS
    1261          if (isset( $_SESSION['phpCAS']['auth_checked'])) {
    1262              unset($_SESSION['phpCAS']['auth_checked']);
    1263          }
    1264          if ( $this->isAuthenticated(true) ) {
    1265              phpCAS::trace('user already authenticated');
    1266              $res = true;
    1267          } else {
    1268              $this->redirectToCas(false, true);
    1269              // never reached
    1270              $res = false;
    1271          }
    1272          phpCAS::traceEnd();
    1273          return $res;
    1274      }
    1275  
    1276      /**
    1277       * This method is called to be sure that the user is authenticated. When not
    1278       * authenticated, halt by redirecting to the CAS server; otherwise return true.
    1279       *
    1280       * @return bool true when the user is authenticated; otherwise halt.
    1281       */
    1282      public function forceAuthentication()
    1283      {
    1284          phpCAS::traceBegin();
    1285  
    1286          if ( $this->isAuthenticated() ) {
    1287              // the user is authenticated, nothing to be done.
    1288              phpCAS::trace('no need to authenticate');
    1289              $res = true;
    1290          } else {
    1291              // the user is not authenticated, redirect to the CAS server
    1292              if (isset($_SESSION['phpCAS']['auth_checked'])) {
    1293                  unset($_SESSION['phpCAS']['auth_checked']);
    1294              }
    1295              $this->redirectToCas(false/* no gateway */);
    1296              // never reached
    1297              $res = false;
    1298          }
    1299          phpCAS::traceEnd($res);
    1300          return $res;
    1301      }
    1302  
    1303      /**
    1304       * An integer that gives the number of times authentication will be cached
    1305       * before rechecked.
    1306       *
    1307       * @hideinitializer
    1308       */
    1309      private $_cache_times_for_auth_recheck = 0;
    1310  
    1311      /**
    1312       * Set the number of times authentication will be cached before rechecked.
    1313       *
    1314       * @param int $n number of times to wait for a recheck
    1315       *
    1316       * @return void
    1317       */
    1318      public function setCacheTimesForAuthRecheck($n)
    1319      {
    1320      	 if (gettype($n) != 'integer')
    1321          	 throw new CAS_TypeMismatchException($n, '$n', 'string');
    1322  
    1323          $this->_cache_times_for_auth_recheck = $n;
    1324      }
    1325  
    1326      /**
    1327       * This method is called to check whether the user is authenticated or not.
    1328       *
    1329       * @return bool true when the user is authenticated, false when a previous
    1330       * gateway login failed or  the function will not return if the user is
    1331       * redirected to the cas server for a gateway login attempt
    1332       */
    1333      public function checkAuthentication()
    1334      {
    1335          phpCAS::traceBegin();
    1336          if ( $this->isAuthenticated() ) {
    1337              phpCAS::trace('user is authenticated');
    1338              /* The 'auth_checked' variable is removed just in case it's set. */
    1339              unset($_SESSION['phpCAS']['auth_checked']);
    1340              $res = true;
    1341          } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
    1342              // the previous request has redirected the client to the CAS server
    1343              // with gateway=true
    1344              unset($_SESSION['phpCAS']['auth_checked']);
    1345              $res = false;
    1346          } else {
    1347              // avoid a check against CAS on every request
    1348              if (!isset($_SESSION['phpCAS']['unauth_count'])) {
    1349                  $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
    1350              }
    1351  
    1352              if (($_SESSION['phpCAS']['unauth_count'] != -2
    1353                  && $this->_cache_times_for_auth_recheck == -1)
    1354                  || ($_SESSION['phpCAS']['unauth_count'] >= 0
    1355                  && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)
    1356              ) {
    1357                  $res = false;
    1358  
    1359                  if ($this->_cache_times_for_auth_recheck != -1) {
    1360                      $_SESSION['phpCAS']['unauth_count']++;
    1361                      phpCAS::trace(
    1362                          'user is not authenticated (cached for '
    1363                          .$_SESSION['phpCAS']['unauth_count'].' times of '
    1364                          .$this->_cache_times_for_auth_recheck.')'
    1365                      );
    1366                  } else {
    1367                      phpCAS::trace(
    1368                          'user is not authenticated (cached for until login pressed)'
    1369                      );
    1370                  }
    1371              } else {
    1372                  $_SESSION['phpCAS']['unauth_count'] = 0;
    1373                  $_SESSION['phpCAS']['auth_checked'] = true;
    1374                  phpCAS::trace('user is not authenticated (cache reset)');
    1375                  $this->redirectToCas(true/* gateway */);
    1376                  // never reached
    1377                  $res = false;
    1378              }
    1379          }
    1380          phpCAS::traceEnd($res);
    1381          return $res;
    1382      }
    1383  
    1384      /**
    1385       * This method is called to check if the user is authenticated (previously or by
    1386       * tickets given in the URL).
    1387       *
    1388       * @param bool $renew true to force the authentication with the CAS server
    1389       *
    1390       * @return bool true when the user is authenticated. Also may redirect to the
    1391       * same URL without the ticket.
    1392       */
    1393      public function isAuthenticated($renew=false)
    1394      {
    1395          phpCAS::traceBegin();
    1396          $res = false;
    1397          $validate_url = '';
    1398          if ( $this->_wasPreviouslyAuthenticated() ) {
    1399              if ($this->hasTicket()) {
    1400                  // User has a additional ticket but was already authenticated
    1401                  phpCAS::trace(
    1402                      'ticket was present and will be discarded, use renewAuthenticate()'
    1403                  );
    1404                  if ($this->_clearTicketsFromUrl) {
    1405                      phpCAS::trace("Prepare redirect to : ".$this->getURL());
    1406                      session_write_close();
    1407                      header('Location: '.$this->getURL());
    1408                      flush();
    1409                      phpCAS::traceExit();
    1410                      throw new CAS_GracefullTerminationException();
    1411                  } else {
    1412                      phpCAS::trace(
    1413                          'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'
    1414                      );
    1415                      $res = true;
    1416                  }
    1417              } else {
    1418                  // the user has already (previously during the session) been
    1419                  // authenticated, nothing to be done.
    1420                  phpCAS::trace(
    1421                      'user was already authenticated, no need to look for tickets'
    1422                  );
    1423                  $res = true;
    1424              }
    1425  
    1426              // Mark the auth-check as complete to allow post-authentication
    1427              // callbacks to make use of phpCAS::getUser() and similar methods
    1428              $this->markAuthenticationCall($res);
    1429          } else {
    1430              if ($this->hasTicket()) {
    1431                  switch ($this->getServerVersion()) {
    1432                  case CAS_VERSION_1_0:
    1433                      // if a Service Ticket was given, validate it
    1434                      phpCAS::trace(
    1435                          'CAS 1.0 ticket `'.$this->getTicket().'\' is present'
    1436                      );
    1437                      $this->validateCAS10(
    1438                          $validate_url, $text_response, $tree_response, $renew
    1439                      ); // if it fails, it halts
    1440                      phpCAS::trace(
    1441                          'CAS 1.0 ticket `'.$this->getTicket().'\' was validated'
    1442                      );
    1443                      $_SESSION['phpCAS']['user'] = $this->_getUser();
    1444                      $res = true;
    1445                      $logoutTicket = $this->getTicket();
    1446                      break;
    1447                  case CAS_VERSION_2_0:
    1448                  case CAS_VERSION_3_0:
    1449                      // if a Proxy Ticket was given, validate it
    1450                      phpCAS::trace(
    1451                          'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present'
    1452                      );
    1453                      $this->validateCAS20(
    1454                          $validate_url, $text_response, $tree_response, $renew
    1455                      ); // note: if it fails, it halts
    1456                      phpCAS::trace(
    1457                          'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated'
    1458                      );
    1459                      if ( $this->isProxy() ) {
    1460                          $this->_validatePGT(
    1461                              $validate_url, $text_response, $tree_response
    1462                          ); // idem
    1463                          phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated');
    1464                          $_SESSION['phpCAS']['pgt'] = $this->_getPGT();
    1465                      }
    1466                      $_SESSION['phpCAS']['user'] = $this->_getUser();
    1467                      if (!empty($this->_attributes)) {
    1468                          $_SESSION['phpCAS']['attributes'] = $this->_attributes;
    1469                      }
    1470                      $proxies = $this->getProxies();
    1471                      if (!empty($proxies)) {
    1472                          $_SESSION['phpCAS']['proxies'] = $this->getProxies();
    1473                      }
    1474                      $res = true;
    1475                      $logoutTicket = $this->getTicket();
    1476                      break;
    1477                  case SAML_VERSION_1_1:
    1478                      // if we have a SAML ticket, validate it.
    1479                      phpCAS::trace(
    1480                          'SAML 1.1 ticket `'.$this->getTicket().'\' is present'
    1481                      );
    1482                      $this->validateSA(
    1483                          $validate_url, $text_response, $tree_response, $renew
    1484                      ); // if it fails, it halts
    1485                      phpCAS::trace(
    1486                          'SAML 1.1 ticket `'.$this->getTicket().'\' was validated'
    1487                      );
    1488                      $_SESSION['phpCAS']['user'] = $this->_getUser();
    1489                      $_SESSION['phpCAS']['attributes'] = $this->_attributes;
    1490                      $res = true;
    1491                      $logoutTicket = $this->getTicket();
    1492                      break;
    1493                  default:
    1494                      phpCAS::trace('Protocoll error');
    1495                      break;
    1496                  }
    1497              } else {
    1498                  // no ticket given, not authenticated
    1499                  phpCAS::trace('no ticket found');
    1500              }
    1501  
    1502              // Mark the auth-check as complete to allow post-authentication
    1503              // callbacks to make use of phpCAS::getUser() and similar methods
    1504              $this->markAuthenticationCall($res);
    1505  
    1506              if ($res) {
    1507                  // call the post-authenticate callback if registered.
    1508                  if ($this->_postAuthenticateCallbackFunction) {
    1509                      $args = $this->_postAuthenticateCallbackArgs;
    1510                      array_unshift($args, $logoutTicket);
    1511                      call_user_func_array(
    1512                          $this->_postAuthenticateCallbackFunction, $args
    1513                      );
    1514                  }
    1515  
    1516                  // if called with a ticket parameter, we need to redirect to the
    1517                  // app without the ticket so that CAS-ification is transparent
    1518                  // to the browser (for later POSTS) most of the checks and
    1519                  // errors should have been made now, so we're safe for redirect
    1520                  // without masking error messages. remove the ticket as a
    1521                  // security precaution to prevent a ticket in the HTTP_REFERRER
    1522                  if ($this->_clearTicketsFromUrl) {
    1523                      phpCAS::trace("Prepare redirect to : ".$this->getURL());
    1524                      session_write_close();
    1525                      header('Location: '.$this->getURL());
    1526                      flush();
    1527                      phpCAS::traceExit();
    1528                      throw new CAS_GracefullTerminationException();
    1529                  }
    1530              }
    1531          }
    1532          phpCAS::traceEnd($res);
    1533          return $res;
    1534      }
    1535  
    1536      /**
    1537       * This method tells if the current session is authenticated.
    1538       *
    1539       * @return bool true if authenticated based soley on $_SESSION variable
    1540       */
    1541      public function isSessionAuthenticated ()
    1542      {
    1543          return !empty($_SESSION['phpCAS']['user']);
    1544      }
    1545  
    1546      /**
    1547       * This method tells if the user has already been (previously) authenticated
    1548       * by looking into the session variables.
    1549       *
    1550       * @note This function switches to callback mode when needed.
    1551       *
    1552       * @return bool true when the user has already been authenticated; false otherwise.
    1553       */
    1554      private function _wasPreviouslyAuthenticated()
    1555      {
    1556          phpCAS::traceBegin();
    1557  
    1558          if ( $this->_isCallbackMode() ) {
    1559              // Rebroadcast the pgtIou and pgtId to all nodes
    1560              if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) {
    1561                  $this->_rebroadcast(self::PGTIOU);
    1562              }
    1563              $this->_callback();
    1564          }
    1565  
    1566          $auth = false;
    1567  
    1568          if ( $this->isProxy() ) {
    1569              // CAS proxy: username and PGT must be present
    1570              if ( $this->isSessionAuthenticated()
    1571                  && !empty($_SESSION['phpCAS']['pgt'])
    1572              ) {
    1573                  // authentication already done
    1574                  $this->_setUser($_SESSION['phpCAS']['user']);
    1575                  if (isset($_SESSION['phpCAS']['attributes'])) {
    1576                      $this->setAttributes($_SESSION['phpCAS']['attributes']);
    1577                  }
    1578                  $this->_setPGT($_SESSION['phpCAS']['pgt']);
    1579                  phpCAS::trace(
    1580                      'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'
    1581                      .$_SESSION['phpCAS']['pgt'].'\''
    1582                  );
    1583  
    1584                  // Include the list of proxies
    1585                  if (isset($_SESSION['phpCAS']['proxies'])) {
    1586                      $this->_setProxies($_SESSION['phpCAS']['proxies']);
    1587                      phpCAS::trace(
    1588                          'proxies = "'
    1589                          .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
    1590                      );
    1591                  }
    1592  
    1593                  $auth = true;
    1594              } elseif ( $this->isSessionAuthenticated()
    1595                  && empty($_SESSION['phpCAS']['pgt'])
    1596              ) {
    1597                  // these two variables should be empty or not empty at the same time
    1598                  phpCAS::trace(
    1599                      'username found (`'.$_SESSION['phpCAS']['user']
    1600                      .'\') but PGT is empty'
    1601                  );
    1602                  // unset all tickets to enforce authentication
    1603                  unset($_SESSION['phpCAS']);
    1604                  $this->setTicket('');
    1605              } elseif ( !$this->isSessionAuthenticated()
    1606                  && !empty($_SESSION['phpCAS']['pgt'])
    1607              ) {
    1608                  // these two variables should be empty or not empty at the same time
    1609                  phpCAS::trace(
    1610                      'PGT found (`'.$_SESSION['phpCAS']['pgt']
    1611                      .'\') but username is empty'
    1612                  );
    1613                  // unset all tickets to enforce authentication
    1614                  unset($_SESSION['phpCAS']);
    1615                  $this->setTicket('');
    1616              } else {
    1617                  phpCAS::trace('neither user nor PGT found');
    1618              }
    1619          } else {
    1620              // `simple' CAS client (not a proxy): username must be present
    1621              if ( $this->isSessionAuthenticated() ) {
    1622                  // authentication already done
    1623                  $this->_setUser($_SESSION['phpCAS']['user']);
    1624                  if (isset($_SESSION['phpCAS']['attributes'])) {
    1625                      $this->setAttributes($_SESSION['phpCAS']['attributes']);
    1626                  }
    1627                  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
    1628  
    1629                  // Include the list of proxies
    1630                  if (isset($_SESSION['phpCAS']['proxies'])) {
    1631                      $this->_setProxies($_SESSION['phpCAS']['proxies']);
    1632                      phpCAS::trace(
    1633                          'proxies = "'
    1634                          .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
    1635                      );
    1636                  }
    1637  
    1638                  $auth = true;
    1639              } else {
    1640                  phpCAS::trace('no user found');
    1641              }
    1642          }
    1643  
    1644          phpCAS::traceEnd($auth);
    1645          return $auth;
    1646      }
    1647  
    1648      /**
    1649       * This method is used to redirect the client to the CAS server.
    1650       * It is used by CAS_Client::forceAuthentication() and
    1651       * CAS_Client::checkAuthentication().
    1652       *
    1653       * @param bool $gateway true to check authentication, false to force it
    1654       * @param bool $renew   true to force the authentication with the CAS server
    1655       *
    1656       * @return void
    1657       */
    1658      public function redirectToCas($gateway=false,$renew=false)
    1659      {
    1660          phpCAS::traceBegin();
    1661          $cas_url = $this->getServerLoginURL($gateway, $renew);
    1662          session_write_close();
    1663          if (php_sapi_name() === 'cli') {
    1664              @header('Location: '.$cas_url);
    1665          } else {
    1666              header('Location: '.$cas_url);
    1667          }
    1668          phpCAS::trace("Redirect to : ".$cas_url);
    1669          $lang = $this->getLangObj();
    1670          $this->printHTMLHeader($lang->getAuthenticationWanted());
    1671          printf('<p>'. $lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
    1672          $this->printHTMLFooter();
    1673          phpCAS::traceExit();
    1674          throw new CAS_GracefullTerminationException();
    1675      }
    1676  
    1677  
    1678      /**
    1679       * This method is used to logout from CAS.
    1680       *
    1681       * @param array $params an array that contains the optional url and service
    1682       * parameters that will be passed to the CAS server
    1683       *
    1684       * @return void
    1685       */
    1686      public function logout($params)
    1687      {
    1688          phpCAS::traceBegin();
    1689          $cas_url = $this->getServerLogoutURL();
    1690          $paramSeparator = '?';
    1691          if (isset($params['url'])) {
    1692              $cas_url = $cas_url . $paramSeparator . "url="
    1693                  . urlencode($params['url']);
    1694              $paramSeparator = '&';
    1695          }
    1696          if (isset($params['service'])) {
    1697              $cas_url = $cas_url . $paramSeparator . "service="
    1698                  . urlencode($params['service']);
    1699          }
    1700          header('Location: '.$cas_url);
    1701          phpCAS::trace("Prepare redirect to : ".$cas_url);
    1702  
    1703          phpCAS::trace("Destroying session : ".session_id());
    1704          session_unset();
    1705          session_destroy();
    1706          if (session_status() === PHP_SESSION_NONE) {
    1707              phpCAS::trace("Session terminated");
    1708          } else {
    1709              phpCAS::error("Session was not terminated");
    1710              phpCAS::trace("Session was not terminated");
    1711          }
    1712          $lang = $this->getLangObj();
    1713          $this->printHTMLHeader($lang->getLogout());
    1714          printf('<p>'.$lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
    1715          $this->printHTMLFooter();
    1716          phpCAS::traceExit();
    1717          throw new CAS_GracefullTerminationException();
    1718      }
    1719  
    1720      /**
    1721       * Check of the current request is a logout request
    1722       *
    1723       * @return bool is logout request.
    1724       */
    1725      private function _isLogoutRequest()
    1726      {
    1727          return !empty($_POST['logoutRequest']);
    1728      }
    1729  
    1730      /**
    1731       * This method handles logout requests.
    1732       *
    1733       * @param bool $check_client    true to check the client bofore handling
    1734       * the request, false not to perform any access control. True by default.
    1735       * @param array $allowed_clients an array of host names allowed to send
    1736       * logout requests.
    1737       *
    1738       * @return void
    1739       */
    1740      public function handleLogoutRequests($check_client=true, $allowed_clients=array())
    1741      {
    1742          phpCAS::traceBegin();
    1743          if (!$this->_isLogoutRequest()) {
    1744              phpCAS::trace("Not a logout request");
    1745              phpCAS::traceEnd();
    1746              return;
    1747          }
    1748          if (!$this->getChangeSessionID()
    1749              && is_null($this->_signoutCallbackFunction)
    1750          ) {
    1751              phpCAS::trace(
    1752                  "phpCAS can't handle logout requests if it is not allowed to change session_id."
    1753              );
    1754          }
    1755          phpCAS::trace("Logout requested");
    1756          $decoded_logout_rq = urldecode($_POST['logoutRequest']);
    1757          phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq);
    1758          $allowed = false;
    1759          if ($check_client) {
    1760              if ($allowed_clients === array()) {
    1761                  $allowed_clients = array( $this->_getServerHostname() );
    1762              }
    1763              $client_ip = $_SERVER['REMOTE_ADDR'];
    1764              $client = gethostbyaddr($client_ip);
    1765              phpCAS::trace("Client: ".$client."/".$client_ip);
    1766              foreach ($allowed_clients as $allowed_client) {
    1767                  if (($client == $allowed_client)
    1768                      || ($client_ip == $allowed_client)
    1769                  ) {
    1770                      phpCAS::trace(
    1771                          "Allowed client '".$allowed_client
    1772                          ."' matches, logout request is allowed"
    1773                      );
    1774                      $allowed = true;
    1775                      break;
    1776                  } else {
    1777                      phpCAS::trace(
    1778                          "Allowed client '".$allowed_client."' does not match"
    1779                      );
    1780                  }
    1781              }
    1782          } else {
    1783              phpCAS::trace("No access control set");
    1784              $allowed = true;
    1785          }
    1786          // If Logout command is permitted proceed with the logout
    1787          if ($allowed) {
    1788              phpCAS::trace("Logout command allowed");
    1789              // Rebroadcast the logout request
    1790              if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) {
    1791                  $this->_rebroadcast(self::LOGOUT);
    1792              }
    1793              // Extract the ticket from the SAML Request
    1794              preg_match(
    1795                  "|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|",
    1796                  $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3
    1797              );
    1798              $wrappedSamlSessionIndex = preg_replace(
    1799                  '|<samlp:SessionIndex>|', '', $tick[0][0]
    1800              );
    1801              $ticket2logout = preg_replace(
    1802                  '|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex
    1803              );
    1804              phpCAS::trace("Ticket to logout: ".$ticket2logout);
    1805  
    1806              // call the post-authenticate callback if registered.
    1807              if ($this->_signoutCallbackFunction) {
    1808                  $args = $this->_signoutCallbackArgs;
    1809                  array_unshift($args, $ticket2logout);
    1810                  call_user_func_array($this->_signoutCallbackFunction, $args);
    1811              }
    1812  
    1813              // If phpCAS is managing the session_id, destroy session thanks to
    1814              // session_id.
    1815              if ($this->getChangeSessionID()) {
    1816                  $session_id = $this->_sessionIdForTicket($ticket2logout);
    1817                  phpCAS::trace("Session id: ".$session_id);
    1818  
    1819                  // destroy a possible application session created before phpcas
    1820                  if (session_id() !== "") {
    1821                      session_unset();
    1822                      session_destroy();
    1823                  }
    1824                  // fix session ID
    1825                  session_id($session_id);
    1826                  $_COOKIE[session_name()]=$session_id;
    1827                  $_GET[session_name()]=$session_id;
    1828  
    1829                  // Overwrite session
    1830                  session_start();
    1831                  session_unset();
    1832                  session_destroy();
    1833                  phpCAS::trace("Session ". $session_id . " destroyed");
    1834              }
    1835          } else {
    1836              phpCAS::error("Unauthorized logout request from client '".$client."'");
    1837              phpCAS::trace("Unauthorized logout request from client '".$client."'");
    1838          }
    1839          flush();
    1840          phpCAS::traceExit();
    1841          throw new CAS_GracefullTerminationException();
    1842  
    1843      }
    1844  
    1845      /** @} */
    1846  
    1847      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1848      // XX                                                                    XX
    1849      // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
    1850      // XX                                                                    XX
    1851      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    1852  
    1853      // ########################################################################
    1854      //  ST
    1855      // ########################################################################
    1856      /**
    1857      * @addtogroup internalBasic
    1858      * @{
    1859      */
    1860  
    1861      /**
    1862       * The Ticket provided in the URL of the request if present
    1863       * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
    1864       * CAS_Client::getTicket() and CAS_Client::_hasPGT().
    1865       *
    1866       * @hideinitializer
    1867       */
    1868      private $_ticket = '';
    1869  
    1870      /**
    1871       * This method returns the Service Ticket provided in the URL of the request.
    1872       *
    1873       * @return string service ticket.
    1874       */
    1875      public  function getTicket()
    1876      {
    1877          return $this->_ticket;
    1878      }
    1879  
    1880      /**
    1881       * This method stores the Service Ticket.
    1882       *
    1883       * @param string $st The Service Ticket.
    1884       *
    1885       * @return void
    1886       */
    1887      public function setTicket($st)
    1888      {
    1889          $this->_ticket = $st;
    1890      }
    1891  
    1892      /**
    1893       * This method tells if a Service Ticket was stored.
    1894       *
    1895       * @return bool if a Service Ticket has been stored.
    1896       */
    1897      public function hasTicket()
    1898      {
    1899          return !empty($this->_ticket);
    1900      }
    1901  
    1902      /** @} */
    1903  
    1904      // ########################################################################
    1905      //  ST VALIDATION
    1906      // ########################################################################
    1907      /**
    1908      * @addtogroup internalBasic
    1909      * @{
    1910      */
    1911  
    1912      /**
    1913       * @var  string the certificate of the CAS server CA.
    1914       *
    1915       * @hideinitializer
    1916       */
    1917      private $_cas_server_ca_cert = null;
    1918  
    1919  
    1920      /**
    1921  
    1922       * validate CN of the CAS server certificate
    1923  
    1924       *
    1925  
    1926       * @hideinitializer
    1927  
    1928       */
    1929  
    1930      private $_cas_server_cn_validate = true;
    1931  
    1932      /**
    1933       * Set to true not to validate the CAS server.
    1934       *
    1935       * @hideinitializer
    1936       */
    1937      private $_no_cas_server_validation = false;
    1938  
    1939  
    1940      /**
    1941       * Set the CA certificate of the CAS server.
    1942       *
    1943       * @param string $cert        the PEM certificate file name of the CA that emited
    1944       * the cert of the server
    1945       * @param bool   $validate_cn valiate CN of the CAS server certificate
    1946       *
    1947       * @return void
    1948       */
    1949      public function setCasServerCACert($cert, $validate_cn)
    1950      {
    1951      // Argument validation
    1952          if (gettype($cert) != 'string') {
    1953              throw new CAS_TypeMismatchException($cert, '$cert', 'string');
    1954          }
    1955          if (gettype($validate_cn) != 'boolean') {
    1956              throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean');
    1957          }
    1958          if ( !file_exists($cert) && $this->_requestImplementation !== 'CAS_TestHarness_DummyRequest'){
    1959              throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation);
    1960          }
    1961          $this->_cas_server_ca_cert = $cert;
    1962          $this->_cas_server_cn_validate = $validate_cn;
    1963      }
    1964  
    1965      /**
    1966       * Set no SSL validation for the CAS server.
    1967       *
    1968       * @return void
    1969       */
    1970      public function setNoCasServerValidation()
    1971      {
    1972          $this->_no_cas_server_validation = true;
    1973      }
    1974  
    1975      /**
    1976       * This method is used to validate a CAS 1,0 ticket; halt on failure, and
    1977       * sets $validate_url, $text_reponse and $tree_response on success.
    1978       *
    1979       * @param string &$validate_url  reference to the the URL of the request to
    1980       * the CAS server.
    1981       * @param string &$text_response reference to the response of the CAS
    1982       * server, as is (XML text).
    1983       * @param string &$tree_response reference to the response of the CAS
    1984       * server, as a DOM XML tree.
    1985       * @param bool   $renew          true to force the authentication with the CAS server
    1986       *
    1987       * @return bool true when successfull and issue a CAS_AuthenticationException
    1988       * and false on an error
    1989       * @throws  CAS_AuthenticationException
    1990       */
    1991      public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false)
    1992      {
    1993          phpCAS::traceBegin();
    1994          // build the URL to validate the ticket
    1995          $validate_url = $this->getServerServiceValidateURL()
    1996              .'&ticket='.urlencode($this->getTicket());
    1997  
    1998          if ( $renew ) {
    1999              // pass the renew
    2000              $validate_url .= '&renew=true';
    2001          }
    2002  
    2003          // open and read the URL
    2004          if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
    2005              phpCAS::trace(
    2006                  'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
    2007              );
    2008              throw new CAS_AuthenticationException(
    2009                  $this, 'CAS 1.0 ticket not validated', $validate_url,
    2010                  true/*$no_response*/
    2011              );
    2012          }
    2013  
    2014          if (preg_match('/^no\n/', $text_response)) {
    2015              phpCAS::trace('Ticket has not been validated');
    2016              throw new CAS_AuthenticationException(
    2017                  $this, 'ST not validated', $validate_url, false/*$no_response*/,
    2018                  false/*$bad_response*/, $text_response
    2019              );
    2020          } else if (!preg_match('/^yes\n/', $text_response)) {
    2021              phpCAS::trace('ill-formed response');
    2022              throw new CAS_AuthenticationException(
    2023                  $this, 'Ticket not validated', $validate_url,
    2024                  false/*$no_response*/, true/*$bad_response*/, $text_response
    2025              );
    2026          }
    2027          // ticket has been validated, extract the user name
    2028          $arr = preg_split('/\n/', $text_response);
    2029          $this->_setUser(trim($arr[1]));
    2030  
    2031          $this->_renameSession($this->getTicket());
    2032  
    2033          // at this step, ticket has been validated and $this->_user has been set,
    2034          phpCAS::traceEnd(true);
    2035          return true;
    2036      }
    2037  
    2038      /** @} */
    2039  
    2040  
    2041      // ########################################################################
    2042      //  SAML VALIDATION
    2043      // ########################################################################
    2044      /**
    2045      * @addtogroup internalSAML
    2046      * @{
    2047      */
    2048  
    2049      /**
    2050       * This method is used to validate a SAML TICKET; halt on failure, and sets
    2051       * $validate_url, $text_reponse and $tree_response on success. These
    2052       * parameters are used later by CAS_Client::_validatePGT() for CAS proxies.
    2053       *
    2054       * @param string &$validate_url  reference to the the URL of the request to
    2055       * the CAS server.
    2056       * @param string &$text_response reference to the response of the CAS
    2057       * server, as is (XML text).
    2058       * @param string &$tree_response reference to the response of the CAS
    2059       * server, as a DOM XML tree.
    2060       * @param bool   $renew          true to force the authentication with the CAS server
    2061       *
    2062       * @return bool true when successfull and issue a CAS_AuthenticationException
    2063       * and false on an error
    2064       *
    2065       * @throws  CAS_AuthenticationException
    2066       */
    2067      public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false)
    2068      {
    2069          phpCAS::traceBegin();
    2070          $result = false;
    2071          // build the URL to validate the ticket
    2072          $validate_url = $this->getServerSamlValidateURL();
    2073  
    2074          if ( $renew ) {
    2075              // pass the renew
    2076              $validate_url .= '&renew=true';
    2077          }
    2078  
    2079          // open and read the URL
    2080          if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
    2081              phpCAS::trace(
    2082                  'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
    2083              );
    2084              throw new CAS_AuthenticationException(
    2085                  $this, 'SA not validated', $validate_url, true/*$no_response*/
    2086              );
    2087          }
    2088  
    2089          phpCAS::trace('server version: '.$this->getServerVersion());
    2090  
    2091          // analyze the result depending on the version
    2092          switch ($this->getServerVersion()) {
    2093          case SAML_VERSION_1_1:
    2094              // create new DOMDocument Object
    2095              $dom = new DOMDocument();
    2096              // Fix possible whitspace problems
    2097              $dom->preserveWhiteSpace = false;
    2098              // read the response of the CAS server into a DOM object
    2099              if (!($dom->loadXML($text_response))) {
    2100                  phpCAS::trace('dom->loadXML() failed');
    2101                  throw new CAS_AuthenticationException(
    2102                      $this, 'SA not validated', $validate_url,
    2103                      false/*$no_response*/, true/*$bad_response*/,
    2104                      $text_response
    2105                  );
    2106              }
    2107              // read the root node of the XML tree
    2108              if (!($tree_response = $dom->documentElement)) {
    2109                  phpCAS::trace('documentElement() failed');
    2110                  throw new CAS_AuthenticationException(
    2111                      $this, 'SA not validated', $validate_url,
    2112                      false/*$no_response*/, true/*$bad_response*/,
    2113                      $text_response
    2114                  );
    2115              } else if ( $tree_response->localName != 'Envelope' ) {
    2116                  // insure that tag name is 'Envelope'
    2117                  phpCAS::trace(
    2118                      'bad XML root node (should be `Envelope\' instead of `'
    2119                      .$tree_response->localName.'\''
    2120                  );
    2121                  throw new CAS_AuthenticationException(
    2122                      $this, 'SA not validated', $validate_url,
    2123                      false/*$no_response*/, true/*$bad_response*/,
    2124                      $text_response
    2125                  );
    2126              } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
    2127                  // check for the NameIdentifier tag in the SAML response
    2128                  $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
    2129                  phpCAS::trace('NameIdentifier found');
    2130                  $user = trim($success_elements->item(0)->nodeValue);
    2131                  phpCAS::trace('user = `'.$user.'`');
    2132                  $this->_setUser($user);
    2133                  $this->_setSessionAttributes($text_response);
    2134                  $result = true;
    2135              } else {
    2136                  phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
    2137                  throw new CAS_AuthenticationException(
    2138                      $this, 'SA not validated', $validate_url,
    2139                      false/*$no_response*/, true/*$bad_response*/,
    2140                      $text_response
    2141                  );
    2142              }
    2143          }
    2144          if ($result) {
    2145              $this->_renameSession($this->getTicket());
    2146          }
    2147          // at this step, ST has been validated and $this->_user has been set,
    2148          phpCAS::traceEnd($result);
    2149          return $result;
    2150      }
    2151  
    2152      /**
    2153       * This method will parse the DOM and pull out the attributes from the SAML
    2154       * payload and put them into an array, then put the array into the session.
    2155       *
    2156       * @param string $text_response the SAML payload.
    2157       *
    2158       * @return bool true when successfull and false if no attributes a found
    2159       */
    2160      private function _setSessionAttributes($text_response)
    2161      {
    2162          phpCAS::traceBegin();
    2163  
    2164          $result = false;
    2165  
    2166          $attr_array = array();
    2167  
    2168          // create new DOMDocument Object
    2169          $dom = new DOMDocument();
    2170          // Fix possible whitspace problems
    2171          $dom->preserveWhiteSpace = false;
    2172          if (($dom->loadXML($text_response))) {
    2173              $xPath = new DOMXPath($dom);
    2174              $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
    2175              $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
    2176              $nodelist = $xPath->query("//saml:Attribute");
    2177  
    2178              if ($nodelist) {
    2179                  foreach ($nodelist as $node) {
    2180                      $xres = $xPath->query("saml:AttributeValue", $node);
    2181                      $name = $node->getAttribute("AttributeName");
    2182                      $value_array = array();
    2183                      foreach ($xres as $node2) {
    2184                          $value_array[] = $node2->nodeValue;
    2185                      }
    2186                      $attr_array[$name] = $value_array;
    2187                  }
    2188                  // UGent addition...
    2189                  foreach ($attr_array as $attr_key => $attr_value) {
    2190                      if (count($attr_value) > 1) {
    2191                          $this->_attributes[$attr_key] = $attr_value;
    2192                          phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true));
    2193                      } else {
    2194                          $this->_attributes[$attr_key] = $attr_value[0];
    2195                          phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
    2196                      }
    2197                  }
    2198                  $result = true;
    2199              } else {
    2200                  phpCAS::trace("SAML Attributes are empty");
    2201                  $result = false;
    2202              }
    2203          }
    2204          phpCAS::traceEnd($result);
    2205          return $result;
    2206      }
    2207  
    2208      /** @} */
    2209  
    2210      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    2211      // XX                                                                    XX
    2212      // XX                     PROXY FEATURES (CAS 2.0)                       XX
    2213      // XX                                                                    XX
    2214      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    2215  
    2216      // ########################################################################
    2217      //  PROXYING
    2218      // ########################################################################
    2219      /**
    2220      * @addtogroup internalProxy
    2221      * @{
    2222      */
    2223  
    2224      /**
    2225       * @var  bool is the client a proxy
    2226       * A boolean telling if the client is a CAS proxy or not. Written by
    2227       * CAS_Client::CAS_Client(), read by CAS_Client::isProxy().
    2228       */
    2229      private $_proxy;
    2230  
    2231      /**
    2232       * @var  CAS_CookieJar Handler for managing service cookies.
    2233       */
    2234      private $_serviceCookieJar;
    2235  
    2236      /**
    2237       * Tells if a CAS client is a CAS proxy or not
    2238       *
    2239       * @return bool true when the CAS client is a CAS proxy, false otherwise
    2240       */
    2241      public function isProxy()
    2242      {
    2243          return $this->_proxy;
    2244      }
    2245  
    2246  
    2247      /** @} */
    2248      // ########################################################################
    2249      //  PGT
    2250      // ########################################################################
    2251      /**
    2252      * @addtogroup internalProxy
    2253      * @{
    2254      */
    2255  
    2256      /**
    2257       * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
    2258       * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and
    2259       * CAS_Client::_hasPGT().
    2260       *
    2261       * @hideinitializer
    2262       */
    2263      private $_pgt = '';
    2264  
    2265      /**
    2266       * This method returns the Proxy Granting Ticket given by the CAS server.
    2267       *
    2268       * @return string the Proxy Granting Ticket.
    2269       */
    2270      private function _getPGT()
    2271      {
    2272          return $this->_pgt;
    2273      }
    2274  
    2275      /**
    2276       * This method stores the Proxy Granting Ticket.
    2277       *
    2278       * @param string $pgt The Proxy Granting Ticket.
    2279       *
    2280       * @return void
    2281       */
    2282      private function _setPGT($pgt)
    2283      {
    2284          $this->_pgt = $pgt;
    2285      }
    2286  
    2287      /**
    2288       * This method tells if a Proxy Granting Ticket was stored.
    2289       *
    2290       * @return bool true if a Proxy Granting Ticket has been stored.
    2291       */
    2292      private function _hasPGT()
    2293      {
    2294          return !empty($this->_pgt);
    2295      }
    2296  
    2297      /** @} */
    2298  
    2299      // ########################################################################
    2300      //  CALLBACK MODE
    2301      // ########################################################################
    2302      /**
    2303      * @addtogroup internalCallback
    2304      * @{
    2305      */
    2306      /**
    2307       * each PHP script using phpCAS in proxy mode is its own callback to get the
    2308       * PGT back from the CAS server. callback_mode is detected by the constructor
    2309       * thanks to the GET parameters.
    2310       */
    2311  
    2312      /**
    2313       * @var bool a boolean to know if the CAS client is running in callback mode. Written by
    2314       * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode().
    2315       *
    2316       * @hideinitializer
    2317       */
    2318      private $_callback_mode = false;
    2319  
    2320      /**
    2321       * This method sets/unsets callback mode.
    2322       *
    2323       * @param bool $callback_mode true to set callback mode, false otherwise.
    2324       *
    2325       * @return void
    2326       */
    2327      private function _setCallbackMode($callback_mode)
    2328      {
    2329          $this->_callback_mode = $callback_mode;
    2330      }
    2331  
    2332      /**
    2333       * This method returns true when the CAS client is running in callback mode,
    2334       * false otherwise.
    2335       *
    2336       * @return bool A boolean.
    2337       */
    2338      private function _isCallbackMode()
    2339      {
    2340          return $this->_callback_mode;
    2341      }
    2342  
    2343      /**
    2344       * @var bool a boolean to know if the CAS client is using POST parameters when in callback mode.
    2345       * Written by CAS_Client::_setCallbackModeUsingPost(), read by CAS_Client::_isCallbackModeUsingPost().
    2346       *
    2347       * @hideinitializer
    2348       */
    2349      private $_callback_mode_using_post = false;
    2350  
    2351      /**
    2352       * This method sets/unsets usage of POST parameters in callback mode (default/false is GET parameters)
    2353       *
    2354       * @param bool $callback_mode_using_post true to use POST, false to use GET (default).
    2355       *
    2356       * @return void
    2357       */
    2358      private function _setCallbackModeUsingPost($callback_mode_using_post)
    2359      {
    2360          $this->_callback_mode_using_post = $callback_mode_using_post;
    2361      }
    2362  
    2363      /**
    2364       * This method returns true when the callback mode is using POST, false otherwise.
    2365       *
    2366       * @return bool A boolean.
    2367       */
    2368      private function _isCallbackModeUsingPost()
    2369      {
    2370          return $this->_callback_mode_using_post;
    2371      }
    2372  
    2373      /**
    2374       * the URL that should be used for the PGT callback (in fact the URL of the
    2375       * current request without any CGI parameter). Written and read by
    2376       * CAS_Client::_getCallbackURL().
    2377       *
    2378       * @hideinitializer
    2379       */
    2380      private $_callback_url = '';
    2381  
    2382      /**
    2383       * This method returns the URL that should be used for the PGT callback (in
    2384       * fact the URL of the current request without any CGI parameter, except if
    2385       * phpCAS::setFixedCallbackURL() was used).
    2386       *
    2387       * @return string The callback URL
    2388       */
    2389      private function _getCallbackURL()
    2390      {
    2391          // the URL is built when needed only
    2392          if ( empty($this->_callback_url) ) {
    2393              // remove the ticket if present in the URL
    2394              $final_uri = 'https://';
    2395              $final_uri .= $this->_getClientUrl();
    2396              $request_uri = $_SERVER['REQUEST_URI'];
    2397              $request_uri = preg_replace('/\?.*$/', '', $request_uri);
    2398              $final_uri .= $request_uri;
    2399              $this->_callback_url = $final_uri;
    2400          }
    2401          return $this->_callback_url;
    2402      }
    2403  
    2404      /**
    2405       * This method sets the callback url.
    2406       *
    2407       * @param string $url url to set callback
    2408       *
    2409       * @return string the callback url
    2410       */
    2411      public function setCallbackURL($url)
    2412      {
    2413      	 // Sequence validation
    2414          $this->ensureIsProxy();
    2415      	 // Argument Validation
    2416      	 if (gettype($url) != 'string')
    2417          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
    2418  
    2419          return $this->_callback_url = $url;
    2420      }
    2421  
    2422      /**
    2423       * This method is called by CAS_Client::CAS_Client() when running in callback
    2424       * mode. It stores the PGT and its PGT Iou, prints its output and halts.
    2425       *
    2426       * @return void
    2427       */
    2428      private function _callback()
    2429      {
    2430          phpCAS::traceBegin();
    2431          if ($this->_isCallbackModeUsingPost()) {
    2432              $pgtId = $_POST['pgtId'];
    2433              $pgtIou = $_POST['pgtIou'];
    2434          } else {
    2435              $pgtId = $_GET['pgtId'];
    2436              $pgtIou = $_GET['pgtIou'];
    2437          }
    2438          if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgtIou)) {
    2439              if (preg_match('/^[PT]GT-[\.\-\w]+$/', $pgtId)) {
    2440                  phpCAS::trace('Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\')');
    2441                  $this->_storePGT($pgtId, $pgtIou);
    2442                  if (array_key_exists('HTTP_ACCEPT', $_SERVER) &&
    2443                      (   $_SERVER['HTTP_ACCEPT'] == 'application/xml' ||
    2444                          $_SERVER['HTTP_ACCEPT'] == 'text/xml'
    2445                      )
    2446                  ) {
    2447                      echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n";
    2448                      echo '<proxySuccess xmlns="http://www.yale.edu/tp/cas" />';
    2449                      phpCAS::traceExit("XML response sent");
    2450                  } else {
    2451                      $this->printHTMLHeader('phpCAS callback');
    2452                      echo '<p>Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\').</p>';
    2453                      $this->printHTMLFooter();
    2454                      phpCAS::traceExit("HTML response sent");
    2455                  }
    2456                  phpCAS::traceExit("Successfull Callback");
    2457              } else {
    2458                  phpCAS::error('PGT format invalid' . $pgtId);
    2459                  phpCAS::traceExit('PGT format invalid' . $pgtId);
    2460              }
    2461          } else {
    2462              phpCAS::error('PGTiou format invalid' . $pgtIou);
    2463              phpCAS::traceExit('PGTiou format invalid' . $pgtIou);
    2464          }
    2465  
    2466          // Flush the buffer to prevent from sending anything other then a 200
    2467          // Success Status back to the CAS Server. The Exception would normally
    2468          // report as a 500 error.
    2469          flush();
    2470          throw new CAS_GracefullTerminationException();
    2471      }
    2472  
    2473  
    2474      /** @} */
    2475  
    2476      // ########################################################################
    2477      //  PGT STORAGE
    2478      // ########################################################################
    2479      /**
    2480      * @addtogroup internalPGTStorage
    2481      * @{
    2482      */
    2483  
    2484      /**
    2485       * @var  CAS_PGTStorage_AbstractStorage
    2486       * an instance of a class inheriting of PGTStorage, used to deal with PGT
    2487       * storage. Created by CAS_Client::setPGTStorageFile(), used
    2488       * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage().
    2489       *
    2490       * @hideinitializer
    2491       */
    2492      private $_pgt_storage = null;
    2493  
    2494      /**
    2495       * This method is used to initialize the storage of PGT's.
    2496       * Halts on error.
    2497       *
    2498       * @return void
    2499       */
    2500      private function _initPGTStorage()
    2501      {
    2502          // if no SetPGTStorageXxx() has been used, default to file
    2503          if ( !is_object($this->_pgt_storage) ) {
    2504              $this->setPGTStorageFile();
    2505          }
    2506  
    2507          // initializes the storage
    2508          $this->_pgt_storage->init();
    2509      }
    2510  
    2511      /**
    2512       * This method stores a PGT. Halts on error.
    2513       *
    2514       * @param string $pgt     the PGT to store
    2515       * @param string $pgt_iou its corresponding Iou
    2516       *
    2517       * @return void
    2518       */
    2519      private function _storePGT($pgt,$pgt_iou)
    2520      {
    2521          // ensure that storage is initialized
    2522          $this->_initPGTStorage();
    2523          // writes the PGT
    2524          $this->_pgt_storage->write($pgt, $pgt_iou);
    2525      }
    2526  
    2527      /**
    2528       * This method reads a PGT from its Iou and deletes the corresponding
    2529       * storage entry.
    2530       *
    2531       * @param string $pgt_iou the PGT Iou
    2532       *
    2533       * @return string mul The PGT corresponding to the Iou, false when not found.
    2534       */
    2535      private function _loadPGT($pgt_iou)
    2536      {
    2537          // ensure that storage is initialized
    2538          $this->_initPGTStorage();
    2539          // read the PGT
    2540          return $this->_pgt_storage->read($pgt_iou);
    2541      }
    2542  
    2543      /**
    2544       * This method can be used to set a custom PGT storage object.
    2545       *
    2546       * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that
    2547       * inherits from the CAS_PGTStorage_AbstractStorage class
    2548       *
    2549       * @return void
    2550       */
    2551      public function setPGTStorage($storage)
    2552      {
    2553      	 // Sequence validation
    2554          $this->ensureIsProxy();
    2555  
    2556          // check that the storage has not already been set
    2557          if ( is_object($this->_pgt_storage) ) {
    2558              phpCAS::error('PGT storage already defined');
    2559          }
    2560  
    2561          // check to make sure a valid storage object was specified
    2562          if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) )
    2563              throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object');
    2564  
    2565          // store the PGTStorage object
    2566          $this->_pgt_storage = $storage;
    2567      }
    2568  
    2569      /**
    2570       * This method is used to tell phpCAS to store the response of the
    2571       * CAS server to PGT requests in a database.
    2572       *
    2573       * @param string|PDO $dsn_or_pdo     a dsn string to use for creating a PDO
    2574       * object or a PDO object
    2575       * @param string $username       the username to use when connecting to the
    2576       * database
    2577       * @param string $password       the password to use when connecting to the
    2578       * database
    2579       * @param string $table          the table to use for storing and retrieving
    2580       * PGTs
    2581       * @param string $driver_options any driver options to use when connecting
    2582       * to the database
    2583       *
    2584       * @return void
    2585       */
    2586      public function setPGTStorageDb(
    2587          $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null
    2588      ) {
    2589      	 // Sequence validation
    2590          $this->ensureIsProxy();
    2591  
    2592      	 // Argument validation
    2593      	 if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && !is_string($dsn_or_pdo))
    2594  	 	 	 throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object');
    2595      	 if (gettype($username) != 'string')
    2596          	 throw new CAS_TypeMismatchException($username, '$username', 'string');
    2597          if (gettype($password) != 'string')
    2598          	 throw new CAS_TypeMismatchException($password, '$password', 'string');
    2599          if (gettype($table) != 'string')
    2600          	 throw new CAS_TypeMismatchException($table, '$password', 'string');
    2601  
    2602          // create the storage object
    2603          $this->setPGTStorage(
    2604              new CAS_PGTStorage_Db(
    2605                  $this, $dsn_or_pdo, $username, $password, $table, $driver_options
    2606              )
    2607          );
    2608      }
    2609  
    2610      /**
    2611       * This method is used to tell phpCAS to store the response of the
    2612       * CAS server to PGT requests onto the filesystem.
    2613       *
    2614       * @param string $path the path where the PGT's should be stored
    2615       *
    2616       * @return void
    2617       */
    2618      public function setPGTStorageFile($path='')
    2619      {
    2620      	 // Sequence validation
    2621          $this->ensureIsProxy();
    2622  
    2623      	 // Argument validation
    2624      	 if (gettype($path) != 'string')
    2625          	 throw new CAS_TypeMismatchException($path, '$path', 'string');
    2626  
    2627          // create the storage object
    2628          $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
    2629      }
    2630  
    2631  
    2632      // ########################################################################
    2633      //  PGT VALIDATION
    2634      // ########################################################################
    2635      /**
    2636      * This method is used to validate a PGT; halt on failure.
    2637      *
    2638      * @param string &$validate_url the URL of the request to the CAS server.
    2639      * @param string $text_response the response of the CAS server, as is
    2640      *                              (XML text); result of
    2641      *                              CAS_Client::validateCAS10() or
    2642      *                              CAS_Client::validateCAS20().
    2643      * @param DOMElement $tree_response the response of the CAS server, as a DOM XML
    2644      * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
    2645      *
    2646      * @return bool true when successfull and issue a CAS_AuthenticationException
    2647      * and false on an error
    2648      *
    2649      * @throws CAS_AuthenticationException
    2650      */
    2651      private function _validatePGT(&$validate_url,$text_response,$tree_response)
    2652      {
    2653          phpCAS::traceBegin();
    2654          if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
    2655              phpCAS::trace('<proxyGrantingTicket> not found');
    2656              // authentication succeded, but no PGT Iou was transmitted
    2657              throw new CAS_AuthenticationException(
    2658                  $this, 'Ticket validated but no PGT Iou transmitted',
    2659                  $validate_url, false/*$no_response*/, false/*$bad_response*/,
    2660                  $text_response
    2661              );
    2662          } else {
    2663              // PGT Iou transmitted, extract it
    2664              $pgt_iou = trim(
    2665                  $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue
    2666              );
    2667              if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) {
    2668                  $pgt = $this->_loadPGT($pgt_iou);
    2669                  if ( $pgt == false ) {
    2670                      phpCAS::trace('could not load PGT');
    2671                      throw new CAS_AuthenticationException(
    2672                          $this,
    2673                          'PGT Iou was transmitted but PGT could not be retrieved',
    2674                          $validate_url, false/*$no_response*/,
    2675                          false/*$bad_response*/, $text_response
    2676                      );
    2677                  }
    2678                  $this->_setPGT($pgt);
    2679              } else {
    2680                  phpCAS::trace('PGTiou format error');
    2681                  throw new CAS_AuthenticationException(
    2682                      $this, 'PGT Iou was transmitted but has wrong format',
    2683                      $validate_url, false/*$no_response*/, false/*$bad_response*/,
    2684                      $text_response
    2685                  );
    2686              }
    2687          }
    2688          phpCAS::traceEnd(true);
    2689          return true;
    2690      }
    2691  
    2692      // ########################################################################
    2693      //  PGT VALIDATION
    2694      // ########################################################################
    2695  
    2696      /**
    2697       * This method is used to retrieve PT's from the CAS server thanks to a PGT.
    2698       *
    2699       * @param string $target_service the service to ask for with the PT.
    2700       * @param int &$err_code      an error code (PHPCAS_SERVICE_OK on success).
    2701       * @param string &$err_msg       an error message (empty on success).
    2702       *
    2703       * @return string|false a Proxy Ticket, or false on error.
    2704       */
    2705      public function retrievePT($target_service,&$err_code,&$err_msg)
    2706      {
    2707      	 // Argument validation
    2708      	 if (gettype($target_service) != 'string')
    2709          	 throw new CAS_TypeMismatchException($target_service, '$target_service', 'string');
    2710  
    2711          phpCAS::traceBegin();
    2712  
    2713          // by default, $err_msg is set empty and $pt to true. On error, $pt is
    2714          // set to false and $err_msg to an error message. At the end, if $pt is false
    2715          // and $error_msg is still empty, it is set to 'invalid response' (the most
    2716          // commonly encountered error).
    2717          $err_msg = '';
    2718  
    2719          // build the URL to retrieve the PT
    2720          $cas_url = $this->getServerProxyURL().'?targetService='
    2721              .urlencode($target_service).'&pgt='.$this->_getPGT();
    2722  
    2723          // open and read the URL
    2724          if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) {
    2725              phpCAS::trace(
    2726                  'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'
    2727              );
    2728              $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
    2729              $err_msg = 'could not retrieve PT (no response from the CAS server)';
    2730              phpCAS::traceEnd(false);
    2731              return false;
    2732          }
    2733  
    2734          $bad_response = false;
    2735  
    2736          // create new DOMDocument object
    2737          $dom = new DOMDocument();
    2738          // Fix possible whitspace problems
    2739          $dom->preserveWhiteSpace = false;
    2740          // read the response of the CAS server into a DOM object
    2741          if ( !($dom->loadXML($cas_response))) {
    2742              phpCAS::trace('dom->loadXML() failed');
    2743              // read failed
    2744              $bad_response = true;
    2745          }
    2746  
    2747          if ( !$bad_response ) {
    2748              // read the root node of the XML tree
    2749              if ( !($root = $dom->documentElement) ) {
    2750                  phpCAS::trace('documentElement failed');
    2751                  // read failed
    2752                  $bad_response = true;
    2753              }
    2754          }
    2755  
    2756          if ( !$bad_response ) {
    2757              // insure that tag name is 'serviceResponse'
    2758              if ( $root->localName != 'serviceResponse' ) {
    2759                  phpCAS::trace('localName failed');
    2760                  // bad root node
    2761                  $bad_response = true;
    2762              }
    2763          }
    2764  
    2765          if ( !$bad_response ) {
    2766              // look for a proxySuccess tag
    2767              if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
    2768                  $proxy_success_list = $root->getElementsByTagName("proxySuccess");
    2769  
    2770                  // authentication succeded, look for a proxyTicket tag
    2771                  if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
    2772                      $err_code = PHPCAS_SERVICE_OK;
    2773                      $err_msg = '';
    2774                      $pt = trim(
    2775                          $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue
    2776                      );
    2777                      phpCAS::trace('original PT: '.trim($pt));
    2778                      phpCAS::traceEnd($pt);
    2779                      return $pt;
    2780                  } else {
    2781                      phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
    2782                  }
    2783              } else if ($root->getElementsByTagName("proxyFailure")->length != 0) {
    2784                  // look for a proxyFailure tag
    2785                  $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
    2786  
    2787                  // authentication failed, extract the error
    2788                  $err_code = PHPCAS_SERVICE_PT_FAILURE;
    2789                  $err_msg = 'PT retrieving failed (code=`'
    2790                  .$proxy_failure_list->item(0)->getAttribute('code')
    2791                  .'\', message=`'
    2792                  .trim($proxy_failure_list->item(0)->nodeValue)
    2793                  .'\')';
    2794                  phpCAS::traceEnd(false);
    2795                  return false;
    2796              } else {
    2797                  phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
    2798              }
    2799          }
    2800  
    2801          // at this step, we are sure that the response of the CAS server was
    2802          // illformed
    2803          $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
    2804          $err_msg = 'Invalid response from the CAS server (response=`'
    2805              .$cas_response.'\')';
    2806  
    2807          phpCAS::traceEnd(false);
    2808          return false;
    2809      }
    2810  
    2811      /** @} */
    2812  
    2813      // ########################################################################
    2814      // READ CAS SERVER ANSWERS
    2815      // ########################################################################
    2816  
    2817      /**
    2818       * @addtogroup internalMisc
    2819       * @{
    2820       */
    2821  
    2822      /**
    2823       * This method is used to acces a remote URL.
    2824       *
    2825       * @param string $url      the URL to access.
    2826       * @param string &$headers an array containing the HTTP header lines of the
    2827       * response (an empty array on failure).
    2828       * @param string &$body    the body of the response, as a string (empty on
    2829       * failure).
    2830       * @param string &$err_msg an error message, filled on failure.
    2831       *
    2832       * @return bool true on success, false otherwise (in this later case, $err_msg
    2833       * contains an error message).
    2834       */
    2835      private function _readURL($url, &$headers, &$body, &$err_msg)
    2836      {
    2837          phpCAS::traceBegin();
    2838          $className = $this->_requestImplementation;
    2839          $request = new $className();
    2840  
    2841          if (count($this->_curl_options)) {
    2842              $request->setCurlOptions($this->_curl_options);
    2843          }
    2844  
    2845          $request->setUrl($url);
    2846  
    2847          if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
    2848              phpCAS::error(
    2849                  'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'
    2850              );
    2851          }
    2852          if ($this->_cas_server_ca_cert != '') {
    2853              $request->setSslCaCert(
    2854                  $this->_cas_server_ca_cert, $this->_cas_server_cn_validate
    2855              );
    2856          }
    2857  
    2858          // add extra stuff if SAML
    2859          if ($this->getServerVersion() == SAML_VERSION_1_1) {
    2860              $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
    2861              $request->addHeader("cache-control: no-cache");
    2862              $request->addHeader("pragma: no-cache");
    2863              $request->addHeader("accept: text/xml");
    2864              $request->addHeader("connection: keep-alive");
    2865              $request->addHeader("content-type: text/xml");
    2866              $request->makePost();
    2867              $request->setPostBody($this->_buildSAMLPayload());
    2868          }
    2869  
    2870          if ($request->send()) {
    2871              $headers = $request->getResponseHeaders();
    2872              $body = $request->getResponseBody();
    2873              $err_msg = '';
    2874              phpCAS::traceEnd(true);
    2875              return true;
    2876          } else {
    2877              $headers = '';
    2878              $body = '';
    2879              $err_msg = $request->getErrorMessage();
    2880              phpCAS::traceEnd(false);
    2881              return false;
    2882          }
    2883      }
    2884  
    2885      /**
    2886       * This method is used to build the SAML POST body sent to /samlValidate URL.
    2887       *
    2888       * @return string the SOAP-encased SAMLP artifact (the ticket).
    2889       */
    2890      private function _buildSAMLPayload()
    2891      {
    2892          phpCAS::traceBegin();
    2893  
    2894          //get the ticket
    2895          $sa = urlencode($this->getTicket());
    2896  
    2897          $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST
    2898              .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE
    2899              .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
    2900  
    2901          phpCAS::traceEnd($body);
    2902          return ($body);
    2903      }
    2904  
    2905      /** @} **/
    2906  
    2907      // ########################################################################
    2908      // ACCESS TO EXTERNAL SERVICES
    2909      // ########################################################################
    2910  
    2911      /**
    2912       * @addtogroup internalProxyServices
    2913       * @{
    2914       */
    2915  
    2916  
    2917      /**
    2918       * Answer a proxy-authenticated service handler.
    2919       *
    2920       * @param string $type The service type. One of:
    2921       * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST,
    2922       * PHPCAS_PROXIED_SERVICE_IMAP
    2923       *
    2924       * @return CAS_ProxiedService
    2925       * @throws InvalidArgumentException If the service type is unknown.
    2926       */
    2927      public function getProxiedService ($type)
    2928      {
    2929      	 // Sequence validation
    2930          $this->ensureIsProxy();
    2931      	 $this->ensureAuthenticationCallSuccessful();
    2932  
    2933      	 // Argument validation
    2934      	 if (gettype($type) != 'string')
    2935          	 throw new CAS_TypeMismatchException($type, '$type', 'string');
    2936  
    2937          switch ($type) {
    2938          case PHPCAS_PROXIED_SERVICE_HTTP_GET:
    2939          case PHPCAS_PROXIED_SERVICE_HTTP_POST:
    2940              $requestClass = $this->_requestImplementation;
    2941              $request = new $requestClass();
    2942              if (count($this->_curl_options)) {
    2943                  $request->setCurlOptions($this->_curl_options);
    2944              }
    2945              $proxiedService = new $type($request, $this->_serviceCookieJar);
    2946              if ($proxiedService instanceof CAS_ProxiedService_Testable) {
    2947                  $proxiedService->setCasClient($this);
    2948              }
    2949              return $proxiedService;
    2950          case PHPCAS_PROXIED_SERVICE_IMAP;
    2951              $proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
    2952              if ($proxiedService instanceof CAS_ProxiedService_Testable) {
    2953                  $proxiedService->setCasClient($this);
    2954              }
    2955              return $proxiedService;
    2956          default:
    2957              throw new CAS_InvalidArgumentException(
    2958                  "Unknown proxied-service type, $type."
    2959              );
    2960          }
    2961      }
    2962  
    2963      /**
    2964       * Initialize a proxied-service handler with the proxy-ticket it should use.
    2965       *
    2966       * @param CAS_ProxiedService $proxiedService service handler
    2967       *
    2968       * @return void
    2969       *
    2970       * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
    2971       *	 	 The code of the Exception will be one of:
    2972       *	 	 	 PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
    2973       *	 	 	 PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
    2974       *	 	 	 PHPCAS_SERVICE_PT_FAILURE
    2975       * @throws CAS_ProxiedService_Exception If there is a failure getting the
    2976       * url from the proxied service.
    2977       */
    2978      public function initializeProxiedService (CAS_ProxiedService $proxiedService)
    2979      {
    2980      	 // Sequence validation
    2981          $this->ensureIsProxy();
    2982      	 $this->ensureAuthenticationCallSuccessful();
    2983  
    2984          $url = $proxiedService->getServiceUrl();
    2985          if (!is_string($url)) {
    2986              throw new CAS_ProxiedService_Exception(
    2987                  "Proxied Service ".get_class($proxiedService)
    2988                  ."->getServiceUrl() should have returned a string, returned a "
    2989                  .gettype($url)." instead."
    2990              );
    2991          }
    2992          $pt = $this->retrievePT($url, $err_code, $err_msg);
    2993          if (!$pt) {
    2994              throw new CAS_ProxyTicketException($err_msg, $err_code);
    2995          }
    2996          $proxiedService->setProxyTicket($pt);
    2997      }
    2998  
    2999      /**
    3000       * This method is used to access an HTTP[S] service.
    3001       *
    3002       * @param string $url       the service to access.
    3003       * @param int    &$err_code an error code Possible values are
    3004       * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
    3005       * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
    3006       * PHPCAS_SERVICE_NOT_AVAILABLE.
    3007       * @param string &$output   the output of the service (also used to give an error
    3008       * message on failure).
    3009       *
    3010       * @return bool true on success, false otherwise (in this later case, $err_code
    3011       * gives the reason why it failed and $output contains an error message).
    3012       */
    3013      public function serviceWeb($url,&$err_code,&$output)
    3014      {
    3015      	 // Sequence validation
    3016          $this->ensureIsProxy();
    3017      	 $this->ensureAuthenticationCallSuccessful();
    3018  
    3019      	 // Argument validation
    3020      	 if (gettype($url) != 'string')
    3021          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
    3022  
    3023          try {
    3024              $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
    3025              $service->setUrl($url);
    3026              $service->send();
    3027              $output = $service->getResponseBody();
    3028              $err_code = PHPCAS_SERVICE_OK;
    3029              return true;
    3030          } catch (CAS_ProxyTicketException $e) {
    3031              $err_code = $e->getCode();
    3032              $output = $e->getMessage();
    3033              return false;
    3034          } catch (CAS_ProxiedService_Exception $e) {
    3035              $lang = $this->getLangObj();
    3036              $output = sprintf(
    3037                  $lang->getServiceUnavailable(), $url, $e->getMessage()
    3038              );
    3039              $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
    3040              return false;
    3041          }
    3042      }
    3043  
    3044      /**
    3045       * This method is used to access an IMAP/POP3/NNTP service.
    3046       *
    3047       * @param string $url        a string giving the URL of the service, including
    3048       * the mailing box for IMAP URLs, as accepted by imap_open().
    3049       * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket
    3050       * @param string $flags      options given to imap_open().
    3051       * @param int    &$err_code  an error code Possible values are
    3052       * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
    3053       * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
    3054       *  PHPCAS_SERVICE_NOT_AVAILABLE.
    3055       * @param string &$err_msg   an error message on failure
    3056       * @param string &$pt        the Proxy Ticket (PT) retrieved from the CAS
    3057       * server to access the URL on success, false on error).
    3058       *
    3059       * @return object|false an IMAP stream on success, false otherwise (in this later
    3060       *  case, $err_code gives the reason why it failed and $err_msg contains an
    3061       *  error message).
    3062       */
    3063      public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
    3064      {
    3065      	 // Sequence validation
    3066          $this->ensureIsProxy();
    3067      	 $this->ensureAuthenticationCallSuccessful();
    3068  
    3069      	 // Argument validation
    3070      	 if (gettype($url) != 'string')
    3071          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
    3072          if (gettype($serviceUrl) != 'string')
    3073          	 throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string');
    3074          if (gettype($flags) != 'integer')
    3075          	 throw new CAS_TypeMismatchException($flags, '$flags', 'string');
    3076  
    3077          try {
    3078              $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
    3079              $service->setServiceUrl($serviceUrl);
    3080              $service->setMailbox($url);
    3081              $service->setOptions($flags);
    3082  
    3083              $stream = $service->open();
    3084              $err_code = PHPCAS_SERVICE_OK;
    3085              $pt = $service->getImapProxyTicket();
    3086              return $stream;
    3087          } catch (CAS_ProxyTicketException $e) {
    3088              $err_msg = $e->getMessage();
    3089              $err_code = $e->getCode();
    3090              $pt = false;
    3091              return false;
    3092          } catch (CAS_ProxiedService_Exception $e) {
    3093              $lang = $this->getLangObj();
    3094              $err_msg = sprintf(
    3095                  $lang->getServiceUnavailable(),
    3096                  $url,
    3097                  $e->getMessage()
    3098              );
    3099              $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
    3100              $pt = false;
    3101              return false;
    3102          }
    3103      }
    3104  
    3105      /** @} **/
    3106  
    3107      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    3108      // XX                                                                    XX
    3109      // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
    3110      // XX                                                                    XX
    3111      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    3112  
    3113      // ########################################################################
    3114      //  PT
    3115      // ########################################################################
    3116      /**
    3117      * @addtogroup internalService
    3118      * @{
    3119      */
    3120  
    3121      /**
    3122       * This array will store a list of proxies in front of this application. This
    3123       * property will only be populated if this script is being proxied rather than
    3124       * accessed directly.
    3125       *
    3126       * It is set in CAS_Client::validateCAS20() and can be read by
    3127       * CAS_Client::getProxies()
    3128       *
    3129       * @access private
    3130       */
    3131      private $_proxies = array();
    3132  
    3133      /**
    3134       * Answer an array of proxies that are sitting in front of this application.
    3135       *
    3136       * This method will only return a non-empty array if we have received and
    3137       * validated a Proxy Ticket.
    3138       *
    3139       * @return array
    3140       * @access public
    3141       */
    3142      public function getProxies()
    3143      {
    3144          return $this->_proxies;
    3145      }
    3146  
    3147      /**
    3148       * Set the Proxy array, probably from persistant storage.
    3149       *
    3150       * @param array $proxies An array of proxies
    3151       *
    3152       * @return void
    3153       * @access private
    3154       */
    3155      private function _setProxies($proxies)
    3156      {
    3157          $this->_proxies = $proxies;
    3158          if (!empty($proxies)) {
    3159              // For proxy-authenticated requests people are not viewing the URL
    3160              // directly since the client is another application making a
    3161              // web-service call.
    3162              // Because of this, stripping the ticket from the URL is unnecessary
    3163              // and causes another web-service request to be performed. Additionally,
    3164              // if session handling on either the client or the server malfunctions
    3165              // then the subsequent request will not complete successfully.
    3166              $this->setNoClearTicketsFromUrl();
    3167          }
    3168      }
    3169  
    3170      /**
    3171       * A container of patterns to be allowed as proxies in front of the cas client.
    3172       *
    3173       * @var CAS_ProxyChain_AllowedList
    3174       */
    3175      private $_allowed_proxy_chains;
    3176  
    3177      /**
    3178       * Answer the CAS_ProxyChain_AllowedList object for this client.
    3179       *
    3180       * @return CAS_ProxyChain_AllowedList
    3181       */
    3182      public function getAllowedProxyChains ()
    3183      {
    3184          if (empty($this->_allowed_proxy_chains)) {
    3185              $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList();
    3186          }
    3187          return $this->_allowed_proxy_chains;
    3188      }
    3189  
    3190      /** @} */
    3191      // ########################################################################
    3192      //  PT VALIDATION
    3193      // ########################################################################
    3194      /**
    3195      * @addtogroup internalProxied
    3196      * @{
    3197      */
    3198  
    3199      /**
    3200       * This method is used to validate a cas 2.0 ST or PT; halt on failure
    3201       * Used for all CAS 2.0 validations
    3202       *
    3203       * @param string &$validate_url  the url of the reponse
    3204       * @param string &$text_response the text of the repsones
    3205       * @param DOMElement &$tree_response the domxml tree of the respones
    3206       * @param bool   $renew          true to force the authentication with the CAS server
    3207       *
    3208       * @return bool true when successfull and issue a CAS_AuthenticationException
    3209       * and false on an error
    3210       *
    3211       * @throws  CAS_AuthenticationException
    3212       */
    3213      public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false)
    3214      {
    3215          phpCAS::traceBegin();
    3216          phpCAS::trace($text_response);
    3217          // build the URL to validate the ticket
    3218          if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
    3219              $validate_url = $this->getServerProxyValidateURL().'&ticket='
    3220                  .urlencode($this->getTicket());
    3221          } else {
    3222              $validate_url = $this->getServerServiceValidateURL().'&ticket='
    3223                  .urlencode($this->getTicket());
    3224          }
    3225  
    3226          if ( $this->isProxy() ) {
    3227              // pass the callback url for CAS proxies
    3228              $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL());
    3229          }
    3230  
    3231          if ( $renew ) {
    3232              // pass the renew
    3233              $validate_url .= '&renew=true';
    3234          }
    3235  
    3236          // open and read the URL
    3237          if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
    3238              phpCAS::trace(
    3239                  'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
    3240              );
    3241              throw new CAS_AuthenticationException(
    3242                  $this, 'Ticket not validated', $validate_url,
    3243                  true/*$no_response*/
    3244              );
    3245          }
    3246  
    3247          // create new DOMDocument object
    3248          $dom = new DOMDocument();
    3249          // Fix possible whitspace problems
    3250          $dom->preserveWhiteSpace = false;
    3251          // CAS servers should only return data in utf-8
    3252          $dom->encoding = "utf-8";
    3253          // read the response of the CAS server into a DOMDocument object
    3254          if ( !($dom->loadXML($text_response))) {
    3255              // read failed
    3256              throw new CAS_AuthenticationException(
    3257                  $this, 'Ticket not validated', $validate_url,
    3258                  false/*$no_response*/, true/*$bad_response*/, $text_response
    3259              );
    3260          } else if ( !($tree_response = $dom->documentElement) ) {
    3261              // read the root node of the XML tree
    3262              // read failed
    3263              throw new CAS_AuthenticationException(
    3264                  $this, 'Ticket not validated', $validate_url,
    3265                  false/*$no_response*/, true/*$bad_response*/, $text_response
    3266              );
    3267          } else if ($tree_response->localName != 'serviceResponse') {
    3268              // insure that tag name is 'serviceResponse'
    3269              // bad root node
    3270              throw new CAS_AuthenticationException(
    3271                  $this, 'Ticket not validated', $validate_url,
    3272                  false/*$no_response*/, true/*$bad_response*/, $text_response
    3273              );
    3274          } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
    3275              // authentication failed, extract the error code and message and throw exception
    3276              $auth_fail_list = $tree_response
    3277                  ->getElementsByTagName("authenticationFailure");
    3278              throw new CAS_AuthenticationException(
    3279                  $this, 'Ticket not validated', $validate_url,
    3280                  false/*$no_response*/, false/*$bad_response*/,
    3281                  $text_response,
    3282                  $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
    3283                  trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
    3284              );
    3285          } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
    3286              // authentication succeded, extract the user name
    3287              $success_elements = $tree_response
    3288                  ->getElementsByTagName("authenticationSuccess");
    3289              if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
    3290                  // no user specified => error
    3291                  throw new CAS_AuthenticationException(
    3292                      $this, 'Ticket not validated', $validate_url,
    3293                      false/*$no_response*/, true/*$bad_response*/, $text_response
    3294                  );
    3295              } else {
    3296                  $this->_setUser(
    3297                      trim(
    3298                          $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue
    3299                      )
    3300                  );
    3301                  $this->_readExtraAttributesCas20($success_elements);
    3302                  // Store the proxies we are sitting behind for authorization checking
    3303                  $proxyList = array();
    3304                  if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
    3305                      foreach ($arr as $proxyElem) {
    3306                          phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue);
    3307                          $proxyList[] = trim($proxyElem->nodeValue);
    3308                      }
    3309                      $this->_setProxies($proxyList);
    3310                      phpCAS::trace("Storing Proxy List");
    3311                  }
    3312                  // Check if the proxies in front of us are allowed
    3313                  if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) {
    3314                      throw new CAS_AuthenticationException(
    3315                          $this, 'Proxy not allowed', $validate_url,
    3316                          false/*$no_response*/, true/*$bad_response*/,
    3317                          $text_response
    3318                      );
    3319                  } else {
    3320                      $result = true;
    3321                  }
    3322              }
    3323          } else {
    3324              throw new CAS_AuthenticationException(
    3325                  $this, 'Ticket not validated', $validate_url,
    3326                  false/*$no_response*/, true/*$bad_response*/,
    3327                  $text_response
    3328              );
    3329          }
    3330  
    3331          $this->_renameSession($this->getTicket());
    3332  
    3333          // at this step, Ticket has been validated and $this->_user has been set,
    3334  
    3335          phpCAS::traceEnd($result);
    3336          return $result;
    3337      }
    3338  
    3339  
    3340      /**
    3341       * This method will parse the DOM and pull out the attributes from the XML
    3342       * payload and put them into an array, then put the array into the session.
    3343       *
    3344       * @param DOMNodeList $success_elements payload of the response
    3345       *
    3346       * @return bool true when successfull, halt otherwise by calling
    3347       * CAS_Client::_authError().
    3348       */
    3349      private function _readExtraAttributesCas20($success_elements)
    3350      {
    3351          phpCAS::traceBegin();
    3352  
    3353          $extra_attributes = array();
    3354  
    3355          // "Jasig Style" Attributes:
    3356          //
    3357          // 	 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    3358          // 	 	 <cas:authenticationSuccess>
    3359          // 	 	 	 <cas:user>jsmith</cas:user>
    3360          // 	 	 	 <cas:attributes>
    3361          // 	 	 	 	 <cas:attraStyle>RubyCAS</cas:attraStyle>
    3362          // 	 	 	 	 <cas:surname>Smith</cas:surname>
    3363          // 	 	 	 	 <cas:givenName>John</cas:givenName>
    3364          // 	 	 	 	 <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
    3365          // 	 	 	 	 <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
    3366          // 	 	 	 </cas:attributes>
    3367          // 	 	 	 <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
    3368          // 	 	 </cas:authenticationSuccess>
    3369          // 	 </cas:serviceResponse>
    3370          //
    3371          if ($this->_casAttributeParserCallbackFunction !== null
    3372              && is_callable($this->_casAttributeParserCallbackFunction)
    3373          ) {
    3374              array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0));
    3375              phpCAS :: trace("Calling attritubeParser callback");
    3376              $extra_attributes =  call_user_func_array(
    3377                  $this->_casAttributeParserCallbackFunction,
    3378                  $this->_casAttributeParserCallbackArgs
    3379              );
    3380          } elseif ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
    3381              $attr_nodes = $success_elements->item(0)
    3382                  ->getElementsByTagName("attributes");
    3383              phpCAS :: trace("Found nested jasig style attributes");
    3384              if ($attr_nodes->item(0)->hasChildNodes()) {
    3385                  // Nested Attributes
    3386                  foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
    3387                      phpCAS :: trace(
    3388                          "Attribute [".$attr_child->localName."] = "
    3389                          .$attr_child->nodeValue
    3390                      );
    3391                      $this->_addAttributeToArray(
    3392                          $extra_attributes, $attr_child->localName,
    3393                          $attr_child->nodeValue
    3394                      );
    3395                  }
    3396              }
    3397          } else {
    3398              // "RubyCAS Style" attributes
    3399              //
    3400              // 	 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    3401              // 	 	 <cas:authenticationSuccess>
    3402              // 	 	 	 <cas:user>jsmith</cas:user>
    3403              //
    3404              // 	 	 	 <cas:attraStyle>RubyCAS</cas:attraStyle>
    3405              // 	 	 	 <cas:surname>Smith</cas:surname>
    3406              // 	 	 	 <cas:givenName>John</cas:givenName>
    3407              // 	 	 	 <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
    3408              // 	 	 	 <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
    3409              //
    3410              // 	 	 	 <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
    3411              // 	 	 </cas:authenticationSuccess>
    3412              // 	 </cas:serviceResponse>
    3413              //
    3414              phpCAS :: trace("Testing for rubycas style attributes");
    3415              $childnodes = $success_elements->item(0)->childNodes;
    3416              foreach ($childnodes as $attr_node) {
    3417                  switch ($attr_node->localName) {
    3418                  case 'user':
    3419                  case 'proxies':
    3420                  case 'proxyGrantingTicket':
    3421                      break;
    3422                  default:
    3423                      if (strlen(trim($attr_node->nodeValue))) {
    3424                          phpCAS :: trace(
    3425                              "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue
    3426                          );
    3427                          $this->_addAttributeToArray(
    3428                              $extra_attributes, $attr_node->localName,
    3429                              $attr_node->nodeValue
    3430                          );
    3431                      }
    3432                  }
    3433              }
    3434          }
    3435  
    3436          // "Name-Value" attributes.
    3437          //
    3438          // Attribute format from these mailing list thread:
    3439          // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
    3440          // Note: This is a less widely used format, but in use by at least two institutions.
    3441          //
    3442          // 	 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    3443          // 	 	 <cas:authenticationSuccess>
    3444          // 	 	 	 <cas:user>jsmith</cas:user>
    3445          //
    3446          // 	 	 	 <cas:attribute name='attraStyle' value='Name-Value' />
    3447          // 	 	 	 <cas:attribute name='surname' value='Smith' />
    3448          // 	 	 	 <cas:attribute name='givenName' value='John' />
    3449          // 	 	 	 <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
    3450          // 	 	 	 <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
    3451          //
    3452          // 	 	 	 <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
    3453          // 	 	 </cas:authenticationSuccess>
    3454          // 	 </cas:serviceResponse>
    3455          //
    3456          if (!count($extra_attributes)
    3457              && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0
    3458          ) {
    3459              $attr_nodes = $success_elements->item(0)
    3460                  ->getElementsByTagName("attribute");
    3461              $firstAttr = $attr_nodes->item(0);
    3462              if (!$firstAttr->hasChildNodes()
    3463                  && $firstAttr->hasAttribute('name')
    3464                  && $firstAttr->hasAttribute('value')
    3465              ) {
    3466                  phpCAS :: trace("Found Name-Value style attributes");
    3467                  // Nested Attributes
    3468                  foreach ($attr_nodes as $attr_node) {
    3469                      if ($attr_node->hasAttribute('name')
    3470                          && $attr_node->hasAttribute('value')
    3471                      ) {
    3472                          phpCAS :: trace(
    3473                              "Attribute [".$attr_node->getAttribute('name')
    3474                              ."] = ".$attr_node->getAttribute('value')
    3475                          );
    3476                          $this->_addAttributeToArray(
    3477                              $extra_attributes, $attr_node->getAttribute('name'),
    3478                              $attr_node->getAttribute('value')
    3479                          );
    3480                      }
    3481                  }
    3482              }
    3483          }
    3484  
    3485          $this->setAttributes($extra_attributes);
    3486          phpCAS::traceEnd();
    3487          return true;
    3488      }
    3489  
    3490      /**
    3491       * Add an attribute value to an array of attributes.
    3492       *
    3493       * @param array  &$attributeArray reference to array
    3494       * @param string $name            name of attribute
    3495       * @param string $value           value of attribute
    3496       *
    3497       * @return void
    3498       */
    3499      private function _addAttributeToArray(array &$attributeArray, $name, $value)
    3500      {
    3501          // If multiple attributes exist, add as an array value
    3502          if (isset($attributeArray[$name])) {
    3503              // Initialize the array with the existing value
    3504              if (!is_array($attributeArray[$name])) {
    3505                  $existingValue = $attributeArray[$name];
    3506                  $attributeArray[$name] = array($existingValue);
    3507              }
    3508  
    3509              $attributeArray[$name][] = trim($value);
    3510          } else {
    3511              $attributeArray[$name] = trim($value);
    3512          }
    3513      }
    3514  
    3515      /** @} */
    3516  
    3517      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    3518      // XX                                                                    XX
    3519      // XX                               MISC                                 XX
    3520      // XX                                                                    XX
    3521      // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    3522  
    3523      /**
    3524       * @addtogroup internalMisc
    3525       * @{
    3526       */
    3527  
    3528      // ########################################################################
    3529      //  URL
    3530      // ########################################################################
    3531      /**
    3532      * the URL of the current request (without any ticket CGI parameter). Written
    3533      * and read by CAS_Client::getURL().
    3534      *
    3535      * @hideinitializer
    3536      */
    3537      private $_url = '';
    3538  
    3539  
    3540      /**
    3541       * This method sets the URL of the current request
    3542       *
    3543       * @param string $url url to set for service
    3544       *
    3545       * @return void
    3546       */
    3547      public function setURL($url)
    3548      {
    3549      	 // Argument Validation
    3550      	 if (gettype($url) != 'string')
    3551          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
    3552  
    3553          $this->_url = $url;
    3554      }
    3555  
    3556      /**
    3557       * This method returns the URL of the current request (without any ticket
    3558       * CGI parameter).
    3559       *
    3560       * @return string The URL
    3561       */
    3562      public function getURL()
    3563      {
    3564          phpCAS::traceBegin();
    3565          // the URL is built when needed only
    3566          if ( empty($this->_url) ) {
    3567              // remove the ticket if present in the URL
    3568              $final_uri = ($this->_isHttps()) ? 'https' : 'http';
    3569              $final_uri .= '://';
    3570  
    3571              $final_uri .= $this->_getClientUrl();
    3572              $request_uri	 = explode('?', $_SERVER['REQUEST_URI'], 2);
    3573              $final_uri	 	 .= $request_uri[0];
    3574  
    3575              if (isset($request_uri[1]) && $request_uri[1]) {
    3576                  $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
    3577  
    3578                  // If the query string still has anything left,
    3579                  // append it to the final URI
    3580                  if ($query_string !== '') {
    3581                      $final_uri	 .= "?$query_string";
    3582                  }
    3583              }
    3584  
    3585              phpCAS::trace("Final URI: $final_uri");
    3586              $this->setURL($final_uri);
    3587          }
    3588          phpCAS::traceEnd($this->_url);
    3589          return $this->_url;
    3590      }
    3591  
    3592      /**
    3593       * This method sets the base URL of the CAS server.
    3594       *
    3595       * @param string $url the base URL
    3596       *
    3597       * @return string base url
    3598       */
    3599      public function setBaseURL($url)
    3600      {
    3601      	 // Argument Validation
    3602      	 if (gettype($url) != 'string')
    3603          	 throw new CAS_TypeMismatchException($url, '$url', 'string');
    3604  
    3605          return $this->_server['base_url'] = $url;
    3606      }
    3607  
    3608  
    3609      /**
    3610       * Try to figure out the phpCAS client URL with possible Proxys / Ports etc.
    3611       *
    3612       * @return string Server URL with domain:port
    3613       */
    3614      private function _getClientUrl()
    3615      {
    3616          if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
    3617              // explode the host list separated by comma and use the first host
    3618              $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
    3619              // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default
    3620              return $hosts[0];
    3621          } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
    3622              $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
    3623          } else {
    3624              if (empty($_SERVER['SERVER_NAME'])) {
    3625                  $server_url = $_SERVER['HTTP_HOST'];
    3626              } else {
    3627                  $server_url = $_SERVER['SERVER_NAME'];
    3628              }
    3629          }
    3630          if (!strpos($server_url, ':')) {
    3631              if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
    3632                  $server_port = $_SERVER['SERVER_PORT'];
    3633              } else {
    3634                  $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
    3635                  $server_port = $ports[0];
    3636              }
    3637  
    3638              if ( ($this->_isHttps() && $server_port!=443)
    3639                  || (!$this->_isHttps() && $server_port!=80)
    3640              ) {
    3641                  $server_url .= ':';
    3642                  $server_url .= $server_port;
    3643              }
    3644          }
    3645          return $server_url;
    3646      }
    3647  
    3648      /**
    3649       * This method checks to see if the request is secured via HTTPS
    3650       *
    3651       * @return bool true if https, false otherwise
    3652       */
    3653      private function _isHttps()
    3654      {
    3655          if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
    3656              return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    3657          } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
    3658              return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https');
    3659          } elseif ( isset($_SERVER['HTTPS'])
    3660              && !empty($_SERVER['HTTPS'])
    3661              && strcasecmp($_SERVER['HTTPS'], 'off') !== 0
    3662          ) {
    3663              return true;
    3664          }
    3665          return false;
    3666  
    3667      }
    3668  
    3669      /**
    3670       * Removes a parameter from a query string
    3671       *
    3672       * @param string $parameterName name of parameter
    3673       * @param string $queryString   query string
    3674       *
    3675       * @return string new query string
    3676       *
    3677       * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
    3678       */
    3679      private function _removeParameterFromQueryString($parameterName, $queryString)
    3680      {
    3681          $parameterName	 = preg_quote($parameterName);
    3682          return preg_replace(
    3683              "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/",
    3684              '', $queryString
    3685          );
    3686      }
    3687  
    3688      /**
    3689       * This method is used to append query parameters to an url. Since the url
    3690       * might already contain parameter it has to be detected and to build a proper
    3691       * URL
    3692       *
    3693       * @param string $url   base url to add the query params to
    3694       * @param string $query params in query form with & separated
    3695       *
    3696       * @return string url with query params
    3697       */
    3698      private function _buildQueryUrl($url, $query)
    3699      {
    3700          $url .= (strstr($url, '?') === false) ? '?' : '&';
    3701          $url .= $query;
    3702          return $url;
    3703      }
    3704  
    3705      /**
    3706       * Renaming the session
    3707       *
    3708       * @param string $ticket name of the ticket
    3709       *
    3710       * @return void
    3711       */
    3712      private function _renameSession($ticket)
    3713      {
    3714          phpCAS::traceBegin();
    3715          if ($this->getChangeSessionID()) {
    3716              if (!empty($this->_user)) {
    3717                  $old_session = $_SESSION;
    3718                  phpCAS :: trace("Killing session: ". session_id());
    3719                  session_destroy();
    3720                  // set up a new session, of name based on the ticket
    3721                  $session_id = $this->_sessionIdForTicket($ticket);
    3722                  phpCAS :: trace("Starting session: ". $session_id);
    3723                  session_id($session_id);
    3724                  session_start();
    3725                  phpCAS :: trace("Restoring old session vars");
    3726                  $_SESSION = $old_session;
    3727              } else {
    3728                  phpCAS :: trace (
    3729                      'Session should only be renamed after successfull authentication'
    3730                  );
    3731              }
    3732          } else {
    3733              phpCAS :: trace(
    3734                  "Skipping session rename since phpCAS is not handling the session."
    3735              );
    3736          }
    3737          phpCAS::traceEnd();
    3738      }
    3739  
    3740      /**
    3741       * Answer a valid session-id given a CAS ticket.
    3742       *
    3743       * The output must be deterministic to allow single-log-out when presented with
    3744       * the ticket to log-out.
    3745       *
    3746       *
    3747       * @param string $ticket name of the ticket
    3748       *
    3749       * @return string
    3750       */
    3751      private function _sessionIdForTicket($ticket)
    3752      {
    3753        // Hash the ticket to ensure that the value meets the PHP 7.1 requirement
    3754        // that session-ids have a length between 22 and 256 characters.
    3755        return hash('sha256', $this->_sessionIdSalt . $ticket);
    3756      }
    3757  
    3758      /**
    3759       * Set a salt/seed for the session-id hash to make it harder to guess.
    3760       *
    3761       * @var string $_sessionIdSalt
    3762       */
    3763      private $_sessionIdSalt = '';
    3764  
    3765      /**
    3766       * Set a salt/seed for the session-id hash to make it harder to guess.
    3767       *
    3768       * @param string $salt
    3769       *
    3770       * @return void
    3771       */
    3772      public function setSessionIdSalt($salt) {
    3773        $this->_sessionIdSalt = (string)$salt;
    3774      }
    3775  
    3776      // ########################################################################
    3777      //  AUTHENTICATION ERROR HANDLING
    3778      // ########################################################################
    3779      /**
    3780      * This method is used to print the HTML output when the user was not
    3781      * authenticated.
    3782      *
    3783      * @param string $failure      the failure that occured
    3784      * @param string $cas_url      the URL the CAS server was asked for
    3785      * @param bool   $no_response  the response from the CAS server (other
    3786      * parameters are ignored if true)
    3787      * @param bool   $bad_response bad response from the CAS server ($err_code
    3788      * and $err_msg ignored if true)
    3789      * @param string $cas_response the response of the CAS server
    3790      * @param int    $err_code     the error code given by the CAS server
    3791      * @param string $err_msg      the error message given by the CAS server
    3792      *
    3793      * @return void
    3794      */
    3795      private function _authError(
    3796          $failure,
    3797          $cas_url,
    3798          $no_response=false,
    3799          $bad_response=false,
    3800          $cas_response='',
    3801          $err_code=-1,
    3802          $err_msg=''
    3803      ) {
    3804          phpCAS::traceBegin();
    3805          $lang = $this->getLangObj();
    3806          $this->printHTMLHeader($lang->getAuthenticationFailed());
    3807          printf(
    3808              $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()),
    3809              isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
    3810          );
    3811          phpCAS::trace('CAS URL: '.$cas_url);
    3812          phpCAS::trace('Authentication failure: '.$failure);
    3813          if ( $no_response ) {
    3814              phpCAS::trace('Reason: no response from the CAS server');
    3815          } else {
    3816              if ( $bad_response ) {
    3817                  phpCAS::trace('Reason: bad response from the CAS server');
    3818              } else {
    3819                  switch ($this->getServerVersion()) {
    3820                  case CAS_VERSION_1_0:
    3821                      phpCAS::trace('Reason: CAS error');
    3822                      break;
    3823                  case CAS_VERSION_2_0:
    3824                  case CAS_VERSION_3_0:
    3825                      if ( $err_code === -1 ) {
    3826                          phpCAS::trace('Reason: no CAS error');
    3827                      } else {
    3828                          phpCAS::trace(
    3829                              'Reason: ['.$err_code.'] CAS error: '.$err_msg
    3830                          );
    3831                      }
    3832                      break;
    3833                  }
    3834              }
    3835              phpCAS::trace('CAS response: '.$cas_response);
    3836          }
    3837          $this->printHTMLFooter();
    3838          phpCAS::traceExit();
    3839          throw new CAS_GracefullTerminationException();
    3840      }
    3841  
    3842      // ########################################################################
    3843      //  PGTIOU/PGTID and logoutRequest rebroadcasting
    3844      // ########################################################################
    3845  
    3846      /**
    3847       * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and
    3848       * array of the nodes.
    3849       */
    3850      private $_rebroadcast = false;
    3851      private $_rebroadcast_nodes = array();
    3852  
    3853      /**
    3854       * Constants used for determining rebroadcast node type.
    3855       */
    3856      const HOSTNAME = 0;
    3857      const IP = 1;
    3858  
    3859      /**
    3860       * Determine the node type from the URL.
    3861       *
    3862       * @param String $nodeURL The node URL.
    3863       *
    3864       * @return int hostname
    3865       *
    3866       */
    3867      private function _getNodeType($nodeURL)
    3868      {
    3869          phpCAS::traceBegin();
    3870          if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) {
    3871              phpCAS::traceEnd(self::IP);
    3872              return self::IP;
    3873          } else {
    3874              phpCAS::traceEnd(self::HOSTNAME);
    3875              return self::HOSTNAME;
    3876          }
    3877      }
    3878  
    3879      /**
    3880       * Store the rebroadcast node for pgtIou/pgtId and logout requests.
    3881       *
    3882       * @param string $rebroadcastNodeUrl The rebroadcast node URL.
    3883       *
    3884       * @return void
    3885       */
    3886      public function addRebroadcastNode($rebroadcastNodeUrl)
    3887      {
    3888      	 // Argument validation
    3889      	 if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl))
    3890          	 throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url');
    3891  
    3892          // Store the rebroadcast node and set flag
    3893          $this->_rebroadcast = true;
    3894          $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
    3895      }
    3896  
    3897      /**
    3898       * An array to store extra rebroadcast curl options.
    3899       */
    3900      private $_rebroadcast_headers = array();
    3901  
    3902      /**
    3903       * This method is used to add header parameters when rebroadcasting
    3904       * pgtIou/pgtId or logoutRequest.
    3905       *
    3906       * @param string $header Header to send when rebroadcasting.
    3907       *
    3908       * @return void
    3909       */
    3910      public function addRebroadcastHeader($header)
    3911      {
    3912      	 if (gettype($header) != 'string')
    3913          	 throw new CAS_TypeMismatchException($header, '$header', 'string');
    3914  
    3915          $this->_rebroadcast_headers[] = $header;
    3916      }
    3917  
    3918      /**
    3919       * Constants used for determining rebroadcast type (logout or pgtIou/pgtId).
    3920       */
    3921      const LOGOUT = 0;
    3922      const PGTIOU = 1;
    3923  
    3924      /**
    3925       * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU
    3926       *
    3927       * @param int $type type of rebroadcasting.
    3928       *
    3929       * @return void
    3930       */
    3931      private function _rebroadcast($type)
    3932      {
    3933          phpCAS::traceBegin();
    3934  
    3935          $rebroadcast_curl_options = array(
    3936          CURLOPT_FAILONERROR => 1,
    3937          CURLOPT_FOLLOWLOCATION => 1,
    3938          CURLOPT_RETURNTRANSFER => 1,
    3939          CURLOPT_CONNECTTIMEOUT => 1,
    3940          CURLOPT_TIMEOUT => 4);
    3941  
    3942          // Try to determine the IP address of the server
    3943          if (!empty($_SERVER['SERVER_ADDR'])) {
    3944              $ip = $_SERVER['SERVER_ADDR'];
    3945          } else if (!empty($_SERVER['LOCAL_ADDR'])) {
    3946              // IIS 7
    3947              $ip = $_SERVER['LOCAL_ADDR'];
    3948          }
    3949          // Try to determine the DNS name of the server
    3950          if (!empty($ip)) {
    3951              $dns = gethostbyaddr($ip);
    3952          }
    3953          $multiClassName = 'CAS_Request_CurlMultiRequest';
    3954          $multiRequest = new $multiClassName();
    3955  
    3956          for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
    3957              if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false))
    3958                  || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))
    3959              ) {
    3960                  phpCAS::trace(
    3961                      'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i]
    3962                      .$_SERVER['REQUEST_URI']
    3963                  );
    3964                  $className = $this->_requestImplementation;
    3965                  $request = new $className();
    3966  
    3967                  $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI'];
    3968                  $request->setUrl($url);
    3969  
    3970                  if (count($this->_rebroadcast_headers)) {
    3971                      $request->addHeaders($this->_rebroadcast_headers);
    3972                  }
    3973  
    3974                  $request->makePost();
    3975                  if ($type == self::LOGOUT) {
    3976                      // Logout request
    3977                      $request->setPostBody(
    3978                          'rebroadcast=false&logoutRequest='.$_POST['logoutRequest']
    3979                      );
    3980                  } else if ($type == self::PGTIOU) {
    3981                      // pgtIou/pgtId rebroadcast
    3982                      $request->setPostBody('rebroadcast=false');
    3983                  }
    3984  
    3985                  $request->setCurlOptions($rebroadcast_curl_options);
    3986  
    3987                  $multiRequest->addRequest($request);
    3988              } else {
    3989                  phpCAS::trace(
    3990                      'Rebroadcast not sent to self: '
    3991                      .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'')
    3992                      .'/'.(!empty($dns)?$dns:'')
    3993                  );
    3994              }
    3995          }
    3996          // We need at least 1 request
    3997          if ($multiRequest->getNumRequests() > 0) {
    3998              $multiRequest->send();
    3999          }
    4000          phpCAS::traceEnd();
    4001      }
    4002  
    4003      /** @} */
    4004  }