74d689302fecfc9136b8a6f4eb19bd586fcd5c97
[piwik-CASLogin.git] / CAS / CAS / client.php
1 <?php
2
3 /*
4  * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
5  * All rights reserved.
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  * 
10  *     * Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above copyright notice,
13  *       this list of conditions and the following disclaimer in the documentation
14  *       and/or other materials provided with the distribution.
15  *     * Neither the name of the ESUP-Portail consortium & the JA-SIG
16  *       Collaborative nor the names of its contributors may be used to endorse or
17  *       promote products derived from this software without specific prior
18  *       written permission.
19
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 /**
33  * @file CAS/client.php
34  * Main class of the phpCAS library
35  */
36
37 // include internationalization stuff
38 include_once(dirname(__FILE__).'/languages/languages.php');
39
40 // include PGT storage classes
41 include_once(dirname(__FILE__).'/PGTStorage/pgt-main.php');
42
43 /**
44  * @class CASClient
45  * The CASClient class is a client interface that provides CAS authentication
46  * to PHP applications.
47  *
48  * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
49  */
50
51 class CASClient
52 {
53         
54         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
55         // XX                                                                    XX
56         // XX                          CONFIGURATION                             XX
57         // XX                                                                    XX
58         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
59         
60         // ########################################################################
61         //  HTML OUTPUT
62         // ########################################################################
63         /**
64          * @addtogroup internalOutput
65          * @{
66          */  
67         
68         /**
69          * This method filters a string by replacing special tokens by appropriate values
70          * and prints it. The corresponding tokens are taken into account:
71          * - __CAS_VERSION__
72          * - __PHPCAS_VERSION__
73          * - __SERVER_BASE_URL__
74          *
75          * Used by CASClient::PrintHTMLHeader() and CASClient::printHTMLFooter().
76          *
77          * @param $str the string to filter and output
78          *
79          * @private
80          */
81         function HTMLFilterOutput($str)
82                 {
83                 $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
84                 $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
85                 $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
86                 echo $str;
87                 }
88         
89         /**
90          * A string used to print the header of HTML pages. Written by CASClient::setHTMLHeader(),
91          * read by CASClient::printHTMLHeader().
92          *
93          * @hideinitializer
94          * @private
95          * @see CASClient::setHTMLHeader, CASClient::printHTMLHeader()
96          */
97         var $_output_header = '';
98         
99         /**
100          * This method prints the header of the HTML output (after filtering). If
101          * CASClient::setHTMLHeader() was not used, a default header is output.
102          *
103          * @param $title the title of the page
104          *
105          * @see HTMLFilterOutput()
106          * @private
107          */
108         function printHTMLHeader($title)
109                 {
110                 $this->HTMLFilterOutput(str_replace('__TITLE__',
111                         $title,
112                         (empty($this->_output_header)
113                                         ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
114                                                         : $this->_output_header)
115                 )
116                 );
117                 }
118         
119         /**
120          * A string used to print the footer of HTML pages. Written by CASClient::setHTMLFooter(),
121          * read by printHTMLFooter().
122          *
123          * @hideinitializer
124          * @private
125          * @see CASClient::setHTMLFooter, CASClient::printHTMLFooter()
126          */
127         var $_output_footer = '';
128         
129         /**
130          * This method prints the footer of the HTML output (after filtering). If
131          * CASClient::setHTMLFooter() was not used, a default footer is output.
132          *
133          * @see HTMLFilterOutput()
134          * @private
135          */
136         function printHTMLFooter()
137                 {
138                 $this->HTMLFilterOutput(empty($this->_output_footer)
139                         ?('<hr><address>phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>')
140                                         :$this->_output_footer);
141                 }
142         
143         /**
144          * This method set the HTML header used for all outputs.
145          *
146          * @param $header the HTML header.
147          *
148          * @public
149          */
150         function setHTMLHeader($header)
151                 {
152                 $this->_output_header = $header;
153                 }
154         
155         /**
156          * This method set the HTML footer used for all outputs.
157          *
158          * @param $footer the HTML footer.
159          *
160          * @public
161          */
162         function setHTMLFooter($footer)
163                 {
164                 $this->_output_footer = $footer;
165                 }
166         
167         /** @} */
168         // ########################################################################
169         //  INTERNATIONALIZATION
170         // ########################################################################
171         /**
172          * @addtogroup internalLang
173          * @{
174          */  
175         /**
176          * A string corresponding to the language used by phpCAS. Written by 
177          * CASClient::setLang(), read by CASClient::getLang().
178          
179          * @note debugging information is always in english (debug purposes only).
180          *
181          * @hideinitializer
182          * @private
183          * @sa CASClient::_strings, CASClient::getString()
184          */
185         var $_lang = '';
186         
187         /**
188          * This method returns the language used by phpCAS.
189          *
190          * @return a string representing the language
191          *
192          * @private
193          */
194         function getLang()
195                 {
196                 if ( empty($this->_lang) )
197                         $this->setLang(PHPCAS_LANG_DEFAULT);
198                 return $this->_lang;
199                 }
200         
201         /**
202          * array containing the strings used by phpCAS. Written by CASClient::setLang(), read by 
203          * CASClient::getString() and used by CASClient::setLang().
204          *
205          * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php
206          *
207          * @private
208          * @see CASClient::_lang, CASClient::getString(), CASClient::setLang(), CASClient::getLang()
209          */
210         var $_strings;
211         
212         /**
213          * This method returns a string depending on the language.
214          *
215          * @param $str the index of the string in $_string.
216          *
217          * @return the string corresponding to $index in $string.
218          *
219          * @private
220          */
221         function getString($str)
222                 {
223                 // call CASclient::getLang() to be sure the language is initialized
224                 $this->getLang();
225                 
226                 if ( !isset($this->_strings[$str]) ) {
227                         trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
228                 }
229                 return $this->_strings[$str];
230                 }
231         
232         /**
233          * This method is used to set the language used by phpCAS. 
234          * @note Can be called only once.
235          *
236          * @param $lang a string representing the language.
237          *
238          * @public
239          * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH
240          */
241         function setLang($lang)
242                 {
243                 // include the corresponding language file
244                 include_once(dirname(__FILE__).'/languages/'.$lang.'.php');
245                 
246                 if ( !is_array($this->_strings) ) {
247                         trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
248                 }
249                 $this->_lang = $lang;
250                 }
251         
252         /** @} */
253         // ########################################################################
254         //  CAS SERVER CONFIG
255         // ########################################################################
256         /**
257          * @addtogroup internalConfig
258          * @{
259          */  
260         
261         /**
262          * a record to store information about the CAS server.
263          * - $_server["version"]: the version of the CAS server
264          * - $_server["hostname"]: the hostname of the CAS server
265          * - $_server["port"]: the port the CAS server is running on
266          * - $_server["uri"]: the base URI the CAS server is responding on
267          * - $_server["base_url"]: the base URL of the CAS server
268          * - $_server["login_url"]: the login URL of the CAS server
269          * - $_server["service_validate_url"]: the service validating URL of the CAS server
270          * - $_server["proxy_url"]: the proxy URL of the CAS server
271          * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server
272          * - $_server["logout_url"]: the logout URL of the CAS server
273          *
274          * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"]
275          * are written by CASClient::CASClient(), read by CASClient::getServerVersion(), 
276          * CASClient::getServerHostname(), CASClient::getServerPort() and CASClient::getServerURI().
277          *
278          * The other fields are written and read by CASClient::getServerBaseURL(), 
279          * CASClient::getServerLoginURL(), CASClient::getServerServiceValidateURL(), 
280          * CASClient::getServerProxyValidateURL() and CASClient::getServerLogoutURL().
281          *
282          * @hideinitializer
283          * @private
284          */
285         var $_server = array(
286                 'version' => -1,
287                 'hostname' => 'none',
288                 'port' => -1,
289                 'uri' => 'none'
290         );
291         
292         /**
293          * This method is used to retrieve the version of the CAS server.
294          * @return the version of the CAS server.
295          * @private
296          */
297         function getServerVersion()
298                 { 
299                 return $this->_server['version']; 
300                 }
301         
302         /**
303          * This method is used to retrieve the hostname of the CAS server.
304          * @return the hostname of the CAS server.
305          * @private
306          */
307         function getServerHostname()
308                 { return $this->_server['hostname']; }
309         
310         /**
311          * This method is used to retrieve the port of the CAS server.
312          * @return the port of the CAS server.
313          * @private
314          */
315         function getServerPort()
316                 { return $this->_server['port']; }
317         
318         /**
319          * This method is used to retrieve the URI of the CAS server.
320          * @return a URI.
321          * @private
322          */
323         function getServerURI()
324                 { return $this->_server['uri']; }
325         
326         /**
327          * This method is used to retrieve the base URL of the CAS server.
328          * @return a URL.
329          * @private
330          */
331         function getServerBaseURL()
332                 { 
333                         // the URL is build only when needed
334                         if ( empty($this->_server['base_url']) ) {
335                                 $this->_server['base_url'] = 'https://' . $this->getServerHostname();
336                                 if ($this->getServerPort()!=443) {
337                                         $this->_server['base_url'] .= ':'
338                                         .$this->getServerPort();
339                                 }
340                                 $this->_server['base_url'] .= $this->getServerURI();
341                         }
342                         return $this->_server['base_url'];
343                 }
344         
345         /**
346          * This method is used to retrieve the login URL of the CAS server.
347          * @param $gateway true to check authentication, false to force it
348          * @param $renew true to force the authentication with the CAS server
349          * NOTE : It is recommended that CAS implementations ignore the
350          "gateway" parameter if "renew" is set
351          * @return a URL.
352          * @private
353          */
354         function getServerLoginURL($gateway=false,$renew=false) {
355                 phpCAS::traceBegin();
356                 // the URL is build only when needed
357                 if ( empty($this->_server['login_url']) ) {
358                         $this->_server['login_url'] = $this->getServerBaseURL();
359                         $this->_server['login_url'] .= 'login?service=';
360                         // $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL());
361                         $this->_server['login_url'] .= urlencode($this->getURL());
362                         if($renew) {
363                                 // It is recommended that when the "renew" parameter is set, its value be "true"
364                                 $this->_server['login_url'] .= '&renew=true';
365                         } elseif ($gateway) {
366                                 // It is recommended that when the "gateway" parameter is set, its value be "true"
367                                 $this->_server['login_url'] .= '&gateway=true';
368                         }
369                 }
370                 phpCAS::traceEnd($this->_server['login_url']);
371                 return $this->_server['login_url'];
372         } 
373         
374         /**
375          * This method sets the login URL of the CAS server.
376          * @param $url the login URL
377          * @private
378          * @since 0.4.21 by Wyman Chan
379          */
380         function setServerLoginURL($url)
381                 {
382                 return $this->_server['login_url'] = $url;
383                 }
384         
385         
386         /**
387          * This method sets the serviceValidate URL of the CAS server.
388          * @param $url the serviceValidate URL
389          * @private
390          * @since 1.1.0 by Joachim Fritschi
391          */
392         function setServerServiceValidateURL($url)
393                 {
394                 return $this->_server['service_validate_url'] = $url;
395                 }
396         
397         
398         /**
399          * This method sets the proxyValidate URL of the CAS server.
400          * @param $url the proxyValidate URL
401          * @private
402          * @since 1.1.0 by Joachim Fritschi
403          */
404         function setServerProxyValidateURL($url)
405                 {
406                 return $this->_server['proxy_validate_url'] = $url;
407                 }
408         
409         
410         /**
411          * This method sets the samlValidate URL of the CAS server.
412          * @param $url the samlValidate URL
413          * @private
414          * @since 1.1.0 by Joachim Fritschi
415          */
416         function setServerSamlValidateURL($url)
417                 {
418                 return $this->_server['saml_validate_url'] = $url;
419                 }
420         
421         
422         /**
423          * This method is used to retrieve the service validating URL of the CAS server.
424          * @return a URL.
425          * @private
426          */
427         function getServerServiceValidateURL()
428                 { 
429                 // the URL is build only when needed
430                 if ( empty($this->_server['service_validate_url']) ) {
431                         switch ($this->getServerVersion()) {
432                                 case CAS_VERSION_1_0:
433                                         $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
434                                         break;
435                                 case CAS_VERSION_2_0:
436                                         $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
437                                         break;
438                         }
439                 }
440                 //      return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); 
441                 return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL()); 
442                 }
443         /**
444          * This method is used to retrieve the SAML validating URL of the CAS server.
445          * @return a URL.
446          * @private
447          */
448         function getServerSamlValidateURL()
449                 {
450                 phpCAS::traceBegin();
451                 // the URL is build only when needed
452                 if ( empty($this->_server['saml_validate_url']) ) {
453                         switch ($this->getServerVersion()) {
454                                 case SAML_VERSION_1_1:
455                                         $this->_server['saml_validate_url'] = $this->getServerBaseURL().'samlValidate';
456                                         break;
457                         }
458                 }
459                 phpCAS::traceEnd($this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL()));
460                 return $this->_server['saml_validate_url'].'?TARGET='.urlencode($this->getURL());
461                 }
462         /**
463          * This method is used to retrieve the proxy validating URL of the CAS server.
464          * @return a URL.
465          * @private
466          */
467         function getServerProxyValidateURL()
468                 { 
469                 // the URL is build only when needed
470                 if ( empty($this->_server['proxy_validate_url']) ) {
471                         switch ($this->getServerVersion()) {
472                                 case CAS_VERSION_1_0:
473                                         $this->_server['proxy_validate_url'] = '';
474                                         break;
475                                 case CAS_VERSION_2_0:
476                                         $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
477                                         break;
478                         }
479                 }
480                 //      return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); 
481                 return $this->_server['proxy_validate_url'].'?service='.urlencode($this->getURL()); 
482                 }
483         
484         /**
485          * This method is used to retrieve the proxy URL of the CAS server.
486          * @return a URL.
487          * @private
488          */
489         function getServerProxyURL()
490                 { 
491                 // the URL is build only when needed
492                 if ( empty($this->_server['proxy_url']) ) {
493                         switch ($this->getServerVersion()) {
494                                 case CAS_VERSION_1_0:
495                                         $this->_server['proxy_url'] = '';
496                                         break;
497                                 case CAS_VERSION_2_0:
498                                         $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
499                                         break;
500                         }
501                 }
502                 return $this->_server['proxy_url']; 
503                 }
504         
505         /**
506          * This method is used to retrieve the logout URL of the CAS server.
507          * @return a URL.
508          * @private
509          */
510         function getServerLogoutURL()
511                 { 
512                 // the URL is build only when needed
513                 if ( empty($this->_server['logout_url']) ) {
514                         $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
515                 }
516                 return $this->_server['logout_url']; 
517                 }
518         
519         /**
520          * This method sets the logout URL of the CAS server.
521          * @param $url the logout URL
522          * @private
523          * @since 0.4.21 by Wyman Chan
524          */
525         function setServerLogoutURL($url)
526                 {
527                 return $this->_server['logout_url'] = $url;
528                 }
529         
530         /**
531          * An array to store extra curl options.
532          */     
533         var $_curl_options = array();
534         
535         /**
536          * This method is used to set additional user curl options.
537          */
538         function setExtraCurlOption($key, $value)
539                 {
540                 $this->_curl_options[$key] = $value;
541                 }
542         
543         /**
544          * This method checks to see if the request is secured via HTTPS
545          * @return true if https, false otherwise
546          * @private
547          */
548         function isHttps() {
549                 //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) {
550                 //0.4.24 by Hinnack
551                 if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
552                         return true;
553                 } else {
554                         return false;
555                 }
556         }
557         
558         // ########################################################################
559         //  CONSTRUCTOR
560         // ########################################################################
561         /**
562          * CASClient constructor.
563          *
564          * @param $server_version the version of the CAS server
565          * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise
566          * @param $server_hostname the hostname of the CAS server
567          * @param $server_port the port the CAS server is running on
568          * @param $server_uri the URI the CAS server is responding on
569          * @param $start_session Have phpCAS start PHP sessions (default true)
570          *
571          * @return a newly created CASClient object
572          *
573          * @public
574          */
575         function CASClient(
576                                            $server_version,
577                                            $proxy,
578                                            $server_hostname,
579                                            $server_port,
580                                            $server_uri,
581                                            $start_session = true) {
582                 
583                 phpCAS::traceBegin();
584                 
585                 // the redirect header() call and DOM parsing code from domxml-php4-php5.php won't work in PHP4 compatibility mode
586                 if (version_compare(PHP_VERSION,'5','>=') && ini_get('zend.ze1_compatibility_mode')) {
587                         phpCAS::error('phpCAS cannot support zend.ze1_compatibility_mode. Sorry.');
588                 }
589                 $this->_start_session = $start_session;
590
591                 if ($this->_start_session && session_id() !== "")
592                 {
593                         phpCAS :: error("Another session was started before phpcas. Either disable the session" .
594                                 " handling for phpcas in the client() call or modify your application to leave" .
595                                 " session handling to phpcas");                 
596                 }
597                 // skip Session Handling for logout requests and if don't want it'
598                 if ($start_session && !$this->isLogoutRequest())
599                 {
600                         phpCAS :: trace("Starting a new session");
601                         session_start();
602                 }
603                 
604                 
605                 // are we in proxy mode ?
606                 $this->_proxy = $proxy;
607                 
608                 //check version
609                 switch ($server_version) {
610                         case CAS_VERSION_1_0:
611                                 if ( $this->isProxy() )
612                                         phpCAS::error('CAS proxies are not supported in CAS '
613                                                 .$server_version);
614                                 break;
615                         case CAS_VERSION_2_0:
616                                 break;
617                         case SAML_VERSION_1_1:
618                                 break;
619                         default:
620                                 phpCAS::error('this version of CAS (`'
621                                         .$server_version
622                                         .'\') is not supported by phpCAS '
623                                         .phpCAS::getVersion());
624                 }
625                 $this->_server['version'] = $server_version;
626                 
627                 // check hostname
628                 if ( empty($server_hostname) 
629                                 || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
630                         phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
631                 }
632                 $this->_server['hostname'] = $server_hostname;
633                 
634                 // check port
635                 if ( $server_port == 0 
636                         || !is_int($server_port) ) {
637                         phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
638                 }
639                 $this->_server['port'] = $server_port;
640                 
641                 // check URI
642                 if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
643                         phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
644                 }
645                 // add leading and trailing `/' and remove doubles      
646                 $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
647                 $this->_server['uri'] = $server_uri;
648                 
649                 // set to callback mode if PgtIou and PgtId CGI GET parameters are provided 
650                 if ( $this->isProxy() ) {
651                         $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
652                 }
653                 
654                 if ( $this->isCallbackMode() ) {
655                         //callback mode: check that phpCAS is secured
656                         if ( !$this->isHttps() ) {
657                                 phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
658                         }
659                 } else {
660                         //normal mode: get ticket and remove it from CGI parameters for developpers
661                         $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null);
662                         switch ($this->getServerVersion()) {
663                                 case CAS_VERSION_1_0: // check for a Service Ticket
664                                         if( preg_match('/^ST-/',$ticket) ) {
665                                                 phpCAS::trace('ST \''.$ticket.'\' found');
666                                                 //ST present
667                                                 $this->setST($ticket);
668                                                 //ticket has been taken into account, unset it to hide it to applications
669                                                 unset($_GET['ticket']);
670                                         } else if ( !empty($ticket) ) {
671                                                 //ill-formed ticket, halt
672                                                 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
673                                         }
674                                         break;
675                                 case CAS_VERSION_2_0: // check for a Service or Proxy Ticket
676                                         if( preg_match('/^[SP]T-/',$ticket) ) {
677                                                 phpCAS::trace('ST or PT \''.$ticket.'\' found');
678                                                 $this->setPT($ticket);
679                                                 unset($_GET['ticket']);
680                                         } else if ( !empty($ticket) ) {
681                                                 //ill-formed ticket, halt
682                                                 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
683                                         } 
684                                         break;
685                                 case SAML_VERSION_1_1: // SAML just does Service Tickets
686                                         if( preg_match('/^[SP]T-/',$ticket) ) {
687                                                 phpCAS::trace('SA \''.$ticket.'\' found');
688                                                 $this->setSA($ticket);
689                                                 unset($_GET['ticket']);
690                                         } else if ( !empty($ticket) ) {
691                                                 //ill-formed ticket, halt
692                                                 phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
693                                         }
694                                         break;
695                         }
696                 }
697                 phpCAS::traceEnd();
698         }
699         
700         /** @} */
701         
702         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
703         // XX                                                                    XX
704         // XX                           Session Handling                         XX
705         // XX                                                                    XX
706         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
707
708         /**
709         * A variable to whether phpcas will use its own session handling. Default = true
710         * @hideinitializer
711         * @private
712         */
713         var $_start_session = true;
714
715         function setStartSession($session)
716         {
717                 $this->_start_session = session;
718         }
719
720         function getStartSession($session)
721         {
722                 $this->_start_session = session;
723         }
724
725                 /**
726          * Renaming the session 
727          */
728         function renameSession($ticket)
729         {
730                 phpCAS::traceBegin();
731                 if($this->_start_session){
732                         if (!empty ($this->_user))
733                         {
734                                 $old_session = $_SESSION;
735                                 session_destroy();
736                                 // set up a new session, of name based on the ticket
737                                 $session_id = preg_replace('/[^\w]/', '', $ticket);
738                                 phpCAS :: trace("Session ID: ".$session_id);
739                                 session_id($session_id);
740                                 session_start();
741                                 phpCAS :: trace("Restoring old session vars");
742                                 $_SESSION = $old_session;
743                         } else
744                         {
745                                 phpCAS :: error('Session should only be renamed after successfull authentication');
746                         }
747                 }else{
748                         phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");                   
749                 }
750                 phpCAS::traceEnd();             
751         }       
752         
753         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
754         // XX                                                                    XX
755         // XX                           AUTHENTICATION                           XX
756         // XX                                                                    XX
757         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
758         
759         /**
760          * @addtogroup internalAuthentication
761          * @{
762          */  
763         
764         /**
765          * The Authenticated user. Written by CASClient::setUser(), read by CASClient::getUser().
766          * @attention client applications should use phpCAS::getUser().
767          *
768          * @hideinitializer
769          * @private
770          */
771         var $_user = '';
772         
773         /**
774          * This method sets the CAS user's login name.
775          *
776          * @param $user the login name of the authenticated user.
777          *
778          * @private
779          */
780         function setUser($user)
781                 {
782                 $this->_user = $user;
783                 }
784         
785         /**
786          * This method returns the CAS user's login name.
787          * @warning should be called only after CASClient::forceAuthentication() or 
788          * CASClient::isAuthenticated(), otherwise halt with an error.
789          *
790          * @return the login name of the authenticated user
791          */
792         function getUser()
793                 {
794                 if ( empty($this->_user) ) {
795                         phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
796                 }
797                 return $this->_user;
798                 }
799         
800         
801         
802         /***********************************************************************************************************************
803          * Atrributes section
804          * 
805          * @author Matthias Crauwels <matthias.crauwels@ugent.be>, Ghent University, Belgium
806          * 
807          ***********************************************************************************************************************/
808         /**
809          * The Authenticated users attributes. Written by CASClient::setAttributes(), read by CASClient::getAttributes().
810          * @attention client applications should use phpCAS::getAttributes().
811          *
812          * @hideinitializer
813          * @private
814          */     
815         var $_attributes = array();
816         
817         function setAttributes($attributes)     
818                 { $this->_attributes = $attributes; }
819         
820         function getAttributes() {
821                 if ( empty($this->_user) ) { // if no user is set, there shouldn't be any attributes also...
822                         phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
823                 }
824                 return $this->_attributes;
825         }
826         
827         function hasAttributes()
828                 { return !empty($this->_attributes); }
829         
830         function hasAttribute($key)
831                 { return (is_array($this->_attributes) && array_key_exists($key, $this->_attributes)); }
832         
833         function getAttribute($key)     {
834                 if($this->hasAttribute($key)) {
835                         return $this->_attributes[$key];
836                 }
837         }
838         
839         /**
840          * This method is called to renew the authentication of the user
841          * If the user is authenticated, renew the connection
842          * If not, redirect to CAS
843          * @public
844          */
845         function renewAuthentication(){
846                 phpCAS::traceBegin();
847                 // Either way, the user is authenticated by CAS
848                 if( isset( $_SESSION['phpCAS']['auth_checked'] ) )
849                         unset($_SESSION['phpCAS']['auth_checked']);
850                 if ( $this->isAuthenticated() ) {
851                         phpCAS::trace('user already authenticated; renew');
852                         $this->redirectToCas(false,true);
853                 } else {
854                         $this->redirectToCas();
855                 }
856                 phpCAS::traceEnd();
857         }
858         
859         /**
860          * This method is called to be sure that the user is authenticated. When not 
861          * authenticated, halt by redirecting to the CAS server; otherwise return TRUE.
862          * @return TRUE when the user is authenticated; otherwise halt.
863          * @public
864          */
865         function forceAuthentication()
866                 {
867                 phpCAS::traceBegin();
868                 
869                 if ( $this->isAuthenticated() ) {
870                         // the user is authenticated, nothing to be done.
871                         phpCAS::trace('no need to authenticate');
872                         $res = TRUE;
873                 } else {
874                         // the user is not authenticated, redirect to the CAS server
875                         if (isset($_SESSION['phpCAS']['auth_checked'])) {
876                                 unset($_SESSION['phpCAS']['auth_checked']);
877                         }
878                         $this->redirectToCas(FALSE/* no gateway */);    
879                         // never reached
880                         $res = FALSE;
881                 }
882                 phpCAS::traceEnd($res);
883                 return $res;
884                 }
885         
886         /**
887          * An integer that gives the number of times authentication will be cached before rechecked.
888          *
889          * @hideinitializer
890          * @private
891          */
892         var $_cache_times_for_auth_recheck = 0;
893         
894         /**
895          * Set the number of times authentication will be cached before rechecked.
896          *
897          * @param $n an integer.
898          *
899          * @public
900          */
901         function setCacheTimesForAuthRecheck($n)
902                 {
903                 $this->_cache_times_for_auth_recheck = $n;
904                 }
905         
906         /**
907          * This method is called to check whether the user is authenticated or not.
908          * @return TRUE when the user is authenticated, FALSE otherwise.
909          * @public
910          */
911         function checkAuthentication()
912                 {
913                 phpCAS::traceBegin();
914                 
915                 if ( $this->isAuthenticated() ) {
916                         phpCAS::trace('user is authenticated');
917                         $res = TRUE;
918                 } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
919                         // the previous request has redirected the client to the CAS server with gateway=true
920                         unset($_SESSION['phpCAS']['auth_checked']);
921                         $res = FALSE;
922                 } else {
923                         //        $_SESSION['phpCAS']['auth_checked'] = true;
924                         //          $this->redirectToCas(TRUE/* gateway */);    
925                         //          // never reached
926                         //          $res = FALSE;
927                         // avoid a check against CAS on every request
928                         if (! isset($_SESSION['phpCAS']['unauth_count']) )
929                                 $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized
930                         
931                         if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1) 
932                                         || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck))
933                         {
934                                 $res = FALSE;
935                                 
936                                 if ($this->_cache_times_for_auth_recheck != -1)
937                                 {
938                                         $_SESSION['phpCAS']['unauth_count']++;
939                                         phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')');
940                                 }
941                                 else
942                                 {
943                                         phpCAS::trace('user is not authenticated (cached for until login pressed)');
944                                 }
945                         }
946                         else
947                         {
948                                 $_SESSION['phpCAS']['unauth_count'] = 0;
949                                 $_SESSION['phpCAS']['auth_checked'] = true;
950                                 phpCAS::trace('user is not authenticated (cache reset)');
951                                 $this->redirectToCas(TRUE/* gateway */);        
952                                 // never reached
953                                 $res = FALSE;
954                         }
955                 }
956                 phpCAS::traceEnd($res);
957                 return $res;
958                 }
959         
960         /**
961          * This method is called to check if the user is authenticated (previously or by
962          * tickets given in the URL).
963          *
964          * @return TRUE when the user is authenticated. Also may redirect to the same URL without the ticket.
965          *
966          * @public
967          */
968         function isAuthenticated()
969                 {
970                 phpCAS::traceBegin();
971                 $res = FALSE;
972                 $validate_url = '';
973                 
974                 if ( $this->wasPreviouslyAuthenticated() ) {
975                         if($this->hasST() || $this->hasPT() || $this->hasSA()){
976                                 // User has a additional ticket but was already authenticated
977                                 phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
978                                 header('Location: '.$this->getURL());
979                                 phpCAS::log( "Prepare redirect to remove ticket: ".$this->getURL() );
980                                 phpCAS::traceExit();
981                                 exit();
982                         }else{
983                                 // the user has already (previously during the session) been
984                                 // authenticated, nothing to be done.
985                                 phpCAS::trace('user was already authenticated, no need to look for tickets');
986                                 $res = TRUE;
987                         }
988                 }
989                 else {
990                         if ( $this->hasST() ) {
991                                 // if a Service Ticket was given, validate it
992                                 phpCAS::trace('ST `'.$this->getST().'\' is present');
993                                 $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
994                                 phpCAS::trace('ST `'.$this->getST().'\' was validated');
995                                 if ( $this->isProxy() ) {
996                                         $this->validatePGT($validate_url,$text_response,$tree_response); // idem
997                                         phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
998                                         $_SESSION['phpCAS']['pgt'] = $this->getPGT();
999                                 }
1000                                 $_SESSION['phpCAS']['user'] = $this->getUser();
1001                                 $res = TRUE;
1002                         }
1003                         elseif ( $this->hasPT() ) {
1004                                 // if a Proxy Ticket was given, validate it
1005                                 phpCAS::trace('PT `'.$this->getPT().'\' is present');
1006                                 $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
1007                                 phpCAS::trace('PT `'.$this->getPT().'\' was validated');
1008                                 if ( $this->isProxy() ) {
1009                                         $this->validatePGT($validate_url,$text_response,$tree_response); // idem
1010                                         phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
1011                                         $_SESSION['phpCAS']['pgt'] = $this->getPGT();
1012                                 }
1013                                 $_SESSION['phpCAS']['user'] = $this->getUser();
1014                                 $res = TRUE;
1015                         }
1016                         elseif ( $this->hasSA() ) {
1017                                 // if we have a SAML ticket, validate it.
1018                                 phpCAS::trace('SA `'.$this->getSA().'\' is present');
1019                                 $this->validateSA($validate_url,$text_response,$tree_response); // if it fails, it halts
1020                                 phpCAS::trace('SA `'.$this->getSA().'\' was validated');
1021                                 $_SESSION['phpCAS']['user'] = $this->getUser();
1022                                 $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
1023                                 $res = TRUE;
1024                         }
1025                         else {
1026                                 // no ticket given, not authenticated
1027                                 phpCAS::trace('no ticket found');
1028                         }
1029                         if ($res) {
1030                                 // if called with a ticket parameter, we need to redirect to the app without the ticket so that CAS-ification is transparent to the browser (for later POSTS)
1031                                 // most of the checks and errors should have been made now, so we're safe for redirect without masking error messages.
1032                                 // remove the ticket as a security precaution to prevent a ticket in the HTTP_REFERRER
1033                                 header('Location: '.$this->getURL());
1034                                 phpCAS::log( "Prepare redirect to : ".$this->getURL() );
1035                                 phpCAS::traceExit();
1036                                 exit();
1037                         }
1038                 }
1039                 
1040                 phpCAS::traceEnd($res);
1041                 return $res;
1042                 }
1043         
1044         /**
1045          * This method tells if the current session is authenticated.
1046          * @return true if authenticated based soley on $_SESSION variable
1047          * @since 0.4.22 by Brendan Arnold
1048          */
1049         function isSessionAuthenticated ()
1050                 {
1051                 return !empty($_SESSION['phpCAS']['user']);
1052                 }
1053         
1054         /**
1055          * This method tells if the user has already been (previously) authenticated
1056          * by looking into the session variables.
1057          *
1058          * @note This function switches to callback mode when needed.
1059          *
1060          * @return TRUE when the user has already been authenticated; FALSE otherwise.
1061          *
1062          * @private
1063          */
1064         function wasPreviouslyAuthenticated()
1065                 {
1066                 phpCAS::traceBegin();
1067                 
1068                 if ( $this->isCallbackMode() ) {
1069                         $this->callback();
1070                 }
1071                 
1072                 $auth = FALSE;
1073                 
1074                 if ( $this->isProxy() ) {
1075                         // CAS proxy: username and PGT must be present
1076                         if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1077                                 // authentication already done
1078                                 $this->setUser($_SESSION['phpCAS']['user']);
1079                                 $this->setPGT($_SESSION['phpCAS']['pgt']);
1080                                 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\''); 
1081                                 $auth = TRUE;
1082                         } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
1083                                 // these two variables should be empty or not empty at the same time
1084                                 phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
1085                                 // unset all tickets to enforce authentication
1086                                 unset($_SESSION['phpCAS']);
1087                                 $this->setST('');
1088                                 $this->setPT('');
1089                         } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
1090                                 // these two variables should be empty or not empty at the same time
1091                                 phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty'); 
1092                                 // unset all tickets to enforce authentication
1093                                 unset($_SESSION['phpCAS']);
1094                                 $this->setST('');
1095                                 $this->setPT('');
1096                         } else {
1097                                 phpCAS::trace('neither user not PGT found'); 
1098                         }
1099                 } else {
1100                         // `simple' CAS client (not a proxy): username must be present
1101                         if ( $this->isSessionAuthenticated() ) {
1102                                 // authentication already done
1103                                 $this->setUser($_SESSION['phpCAS']['user']);
1104                                 if(isset($_SESSION['phpCAS']['attributes'])){
1105                                         $this->setAttributes($_SESSION['phpCAS']['attributes']);
1106                                 }
1107                                 phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\''); 
1108                                 $auth = TRUE;
1109                         } else {
1110                                 phpCAS::trace('no user found');
1111                         }
1112                 }
1113                 
1114                 phpCAS::traceEnd($auth);
1115                 return $auth;
1116                 }
1117         
1118         /**
1119          * This method is used to redirect the client to the CAS server.
1120          * It is used by CASClient::forceAuthentication() and CASClient::checkAuthentication().
1121          * @param $gateway true to check authentication, false to force it
1122          * @param $renew true to force the authentication with the CAS server
1123          * @public
1124          */
1125         function redirectToCas($gateway=false,$renew=false){
1126                 phpCAS::traceBegin();
1127                 $cas_url = $this->getServerLoginURL($gateway,$renew);
1128                 header('Location: '.$cas_url);
1129                 phpCAS::log( "Redirect to : ".$cas_url );
1130                 
1131                 $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED));
1132                 
1133                 printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1134                 $this->printHTMLFooter();
1135                 
1136                 phpCAS::traceExit();
1137                 exit();
1138         }
1139         
1140         
1141         /**
1142          * This method is used to logout from CAS.
1143          * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server
1144          * @public
1145          */
1146         function logout($params) {
1147                 phpCAS::traceBegin();
1148                 $cas_url = $this->getServerLogoutURL();
1149                 $paramSeparator = '?';
1150                 if (isset($params['url'])) {
1151                         $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']); 
1152                         $paramSeparator = '&';
1153                 }
1154                 if (isset($params['service'])) {
1155                         $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']); 
1156                 }
1157                 header('Location: '.$cas_url);
1158                 phpCAS::log( "Prepare redirect to : ".$cas_url );
1159                 
1160                 session_unset();
1161                 session_destroy();
1162                 
1163                 $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
1164                 printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
1165                 $this->printHTMLFooter();
1166                 
1167                 phpCAS::traceExit();
1168                 exit();
1169         }
1170         
1171         /**
1172          * @return true if the current request is a logout request.
1173          * @private
1174          */
1175         function isLogoutRequest() {
1176                 return !empty($_POST['logoutRequest']);
1177         }
1178         
1179         /**
1180          * @return true if a logout request is allowed.
1181          * @private
1182          */
1183         function isLogoutRequestAllowed() {
1184         }
1185         
1186         /**
1187          * This method handles logout requests.
1188          * @param $check_client true to check the client bofore handling the request, 
1189          * false not to perform any access control. True by default.
1190          * @param $allowed_clients an array of host names allowed to send logout requests. 
1191          * By default, only the CAs server (declared in the constructor) will be allowed.
1192          * @public
1193          */
1194         function handleLogoutRequests($check_client=true, $allowed_clients=false) {
1195                 phpCAS::traceBegin();
1196                 if (!$this->isLogoutRequest()) {
1197                         phpCAS::log("Not a logout request");
1198                         phpCAS::traceEnd();
1199                         return;
1200                 }
1201                 if(!$this->_start_session){
1202                         phpCAS::log("phpCAS can't handle logout requests if it does not manage the session.");
1203                 }
1204                 phpCAS::log("Logout requested");
1205                 phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']);
1206                 if ($check_client) {
1207                         if (!$allowed_clients) {
1208                                 $allowed_clients = array( $this->getServerHostname() ); 
1209                         }
1210                         $client_ip = $_SERVER['REMOTE_ADDR'];
1211                         $client = gethostbyaddr($client_ip);
1212                         phpCAS::log("Client: ".$client."/".$client_ip); 
1213                         $allowed = false;
1214                         foreach ($allowed_clients as $allowed_client) {
1215                                 if (($client == $allowed_client) or ($client_ip == $allowed_client)) { 
1216                                         phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed");
1217                                         $allowed = true;
1218                                         break;
1219                                 } else {
1220                                         phpCAS::log("Allowed client '".$allowed_client."' does not match");
1221                                 }
1222                         }
1223                         if (!$allowed) {
1224                                 phpCAS::error("Unauthorized logout request from client '".$client."'");
1225                                 printf("Unauthorized!");
1226                                 phpCAS::traceExit();
1227                                 exit();
1228                         }
1229                 } else {
1230                         phpCAS::log("No access control set");
1231                 }
1232                 // Extract the ticket from the SAML Request
1233                 preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3);
1234                 $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|','',$tick[0][0]);
1235                 $ticket2logout = preg_replace('|</samlp:SessionIndex>|','',$wrappedSamlSessionIndex);
1236                 phpCAS::log("Ticket to logout: ".$ticket2logout);
1237                 $session_id = preg_replace('/[^\w]/','',$ticket2logout);
1238                 phpCAS::log("Session id: ".$session_id);
1239                 
1240                 // destroy a possible application session created before phpcas
1241                 if(session_id()  !== ""){
1242                         session_unset();
1243                         session_destroy();
1244                 }
1245                 // fix session ID
1246                 session_id($session_id);
1247                 $_COOKIE[session_name()]=$session_id;
1248                 $_GET[session_name()]=$session_id;
1249                 
1250                 // Overwrite session
1251                 session_start();        
1252                 session_unset();
1253                 session_destroy();
1254                 printf("Disconnected!");
1255                 phpCAS::traceExit();
1256                 exit();
1257         }
1258         
1259         /** @} */
1260         
1261         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1262         // XX                                                                    XX
1263         // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
1264         // XX                                                                    XX
1265         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1266         
1267         // ########################################################################
1268         //  ST
1269         // ########################################################################
1270         /**
1271          * @addtogroup internalBasic
1272          * @{
1273          */  
1274         
1275         /**
1276          * the Service Ticket provided in the URL of the request if present
1277          * (empty otherwise). Written by CASClient::CASClient(), read by 
1278          * CASClient::getST() and CASClient::hasPGT().
1279          *
1280          * @hideinitializer
1281          * @private
1282          */
1283         var $_st = '';
1284         
1285         /**
1286          * This method returns the Service Ticket provided in the URL of the request.
1287          * @return The service ticket.
1288          * @private
1289          */
1290         function getST()
1291                 { return $this->_st; }
1292         
1293         /**
1294          * This method stores the Service Ticket.
1295          * @param $st The Service Ticket.
1296          * @private
1297          */
1298         function setST($st)
1299                 { $this->_st = $st; }
1300         
1301         /**
1302          * This method tells if a Service Ticket was stored.
1303          * @return TRUE if a Service Ticket has been stored.
1304          * @private
1305          */
1306         function hasST()
1307                 { return !empty($this->_st); }
1308         
1309         /** @} */
1310         
1311         // ########################################################################
1312         //  ST VALIDATION
1313         // ########################################################################
1314         /**
1315          * @addtogroup internalBasic
1316          * @{
1317          */  
1318         
1319         /**
1320          * the certificate of the CAS server.
1321          *
1322          * @hideinitializer
1323          * @private
1324          */
1325         var $_cas_server_cert = '';
1326         
1327         /**
1328          * the certificate of the CAS server CA.
1329          *
1330          * @hideinitializer
1331          * @private
1332          */
1333         var $_cas_server_ca_cert = '';
1334         
1335         /**
1336          * Set to true not to validate the CAS server.
1337          *
1338          * @hideinitializer
1339          * @private
1340          */
1341         var $_no_cas_server_validation = false;
1342         
1343         /**
1344          * Set the certificate of the CAS server.
1345          *
1346          * @param $cert the PEM certificate
1347          */
1348         function setCasServerCert($cert)
1349                 {
1350                 $this->_cas_server_cert = $cert;
1351                 }
1352         
1353         /**
1354          * Set the CA certificate of the CAS server.
1355          *
1356          * @param $cert the PEM certificate of the CA that emited the cert of the server
1357          */
1358         function setCasServerCACert($cert)
1359                 {
1360                 $this->_cas_server_ca_cert = $cert;
1361                 }
1362         
1363         /**
1364          * Set no SSL validation for the CAS server.
1365          */
1366         function setNoCasServerValidation()
1367                 {
1368                 $this->_no_cas_server_validation = true;
1369                 }
1370         
1371         /**
1372          * This method is used to validate a ST; halt on failure, and sets $validate_url,
1373          * $text_reponse and $tree_response on success. These parameters are used later
1374          * by CASClient::validatePGT() for CAS proxies.
1375          * Used for all CAS 1.0 validations
1376          * @param $validate_url the URL of the request to the CAS server.
1377          * @param $text_response the response of the CAS server, as is (XML text).
1378          * @param $tree_response the response of the CAS server, as a DOM XML tree.
1379          *
1380          * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1381          *
1382          * @private
1383          */
1384         function validateST($validate_url,&$text_response,&$tree_response)
1385                 {
1386                 phpCAS::traceBegin();
1387                 // build the URL to validate the ticket
1388                 $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
1389                 if ( $this->isProxy() ) {
1390                         // pass the callback url for CAS proxies
1391                         $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
1392                 }
1393                 
1394                 // open and read the URL
1395                 if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1396                         phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1397                         $this->authError('ST not validated',
1398                                 $validate_url,
1399                                 TRUE/*$no_response*/);
1400                 }
1401                 
1402                 // analyze the result depending on the version
1403                 switch ($this->getServerVersion()) {
1404                         case CAS_VERSION_1_0:
1405                                 if (preg_match('/^no\n/',$text_response)) {
1406                                         phpCAS::trace('ST has not been validated');
1407                                         $this->authError('ST not validated',
1408                                                 $validate_url,
1409                                                 FALSE/*$no_response*/,
1410                                                 FALSE/*$bad_response*/,
1411                                                 $text_response);
1412                                 }
1413                                 if (!preg_match('/^yes\n/',$text_response)) {
1414                                         phpCAS::trace('ill-formed response');
1415                                         $this->authError('ST not validated',
1416                                                 $validate_url,
1417                                                 FALSE/*$no_response*/,
1418                                                 TRUE/*$bad_response*/,
1419                                                 $text_response);
1420                                 }
1421                                 // ST has been validated, extract the user name
1422                                 $arr = preg_split('/\n/',$text_response);
1423                                 $this->setUser(trim($arr[1]));
1424                                 break;
1425                         case CAS_VERSION_2_0:
1426                                 // read the response of the CAS server into a DOM object
1427                                 if ( !($dom = domxml_open_mem($text_response))) {
1428                                         phpCAS::trace('domxml_open_mem() failed');
1429                                         $this->authError('ST not validated',
1430                                                 $validate_url,
1431                                                 FALSE/*$no_response*/,
1432                                                 TRUE/*$bad_response*/,
1433                                                 $text_response);
1434                                 }
1435                                 // read the root node of the XML tree
1436                                 if ( !($tree_response = $dom->document_element()) ) {
1437                                         phpCAS::trace('document_element() failed');
1438                                         $this->authError('ST not validated',
1439                                                 $validate_url,
1440                                                 FALSE/*$no_response*/,
1441                                                 TRUE/*$bad_response*/,
1442                                                 $text_response);
1443                                 }
1444                                 // insure that tag name is 'serviceResponse'
1445                                 if ( $tree_response->node_name() != 'serviceResponse' ) {
1446                                         phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name().'\'');
1447                                         $this->authError('ST not validated',
1448                                                 $validate_url,
1449                                                 FALSE/*$no_response*/,
1450                                                 TRUE/*$bad_response*/,
1451                                                 $text_response);
1452                                 }
1453                                 if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
1454                                         // authentication succeded, extract the user name
1455                                         if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
1456                                                 phpCAS::trace('<authenticationSuccess> found, but no <user>');
1457                                                 $this->authError('ST not validated',
1458                                                         $validate_url,
1459                                                         FALSE/*$no_response*/,
1460                                                         TRUE/*$bad_response*/,
1461                                                         $text_response);
1462                                         }
1463                                         $user = trim($user_elements[0]->get_content());
1464                                         phpCAS::trace('user = `'.$user);
1465                                         $this->setUser($user);
1466                                         
1467                                 } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
1468                                         phpCAS::trace('<authenticationFailure> found');
1469                                         // authentication failed, extract the error code and message
1470                                         $this->authError('ST not validated',
1471                                                 $validate_url,
1472                                                 FALSE/*$no_response*/,
1473                                                 FALSE/*$bad_response*/,
1474                                                 $text_response,
1475                                                 $failure_elements[0]->get_attribute('code')/*$err_code*/,
1476                                                 trim($failure_elements[0]->get_content())/*$err_msg*/);
1477                                 } else {
1478                                         phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
1479                                         $this->authError('ST not validated',
1480                                                 $validate_url,
1481                                                 FALSE/*$no_response*/,
1482                                                 TRUE/*$bad_response*/,
1483                                                 $text_response);
1484                                 }
1485                                 break;
1486                 }
1487                 $this->renameSession($this->getST());
1488                 // at this step, ST has been validated and $this->_user has been set,
1489                 phpCAS::traceEnd(TRUE);
1490                 return TRUE;
1491                 }
1492         
1493         // ########################################################################
1494         //  SAML VALIDATION
1495         // ########################################################################
1496         /**
1497          * @addtogroup internalBasic
1498          * @{
1499          */
1500         
1501         /**
1502          * This method is used to validate a SAML TICKET; halt on failure, and sets $validate_url,
1503          * $text_reponse and $tree_response on success. These parameters are used later
1504          * by CASClient::validatePGT() for CAS proxies.
1505          *
1506          * @param $validate_url the URL of the request to the CAS server.
1507          * @param $text_response the response of the CAS server, as is (XML text).
1508          * @param $tree_response the response of the CAS server, as a DOM XML tree.
1509          *
1510          * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1511          *
1512          * @private
1513          */
1514         function validateSA($validate_url,&$text_response,&$tree_response)
1515                 {
1516                 phpCAS::traceBegin();
1517                 
1518                 // build the URL to validate the ticket
1519                 $validate_url = $this->getServerSamlValidateURL();
1520                 
1521                 // open and read the URL
1522                 if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1523                         phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1524                         $this->authError('SA not validated', $validate_url, TRUE/*$no_response*/);
1525                 }
1526                 
1527                 phpCAS::trace('server version: '.$this->getServerVersion());
1528                 
1529                 // analyze the result depending on the version
1530                 switch ($this->getServerVersion()) {
1531                         case SAML_VERSION_1_1:
1532                                 
1533                                 // read the response of the CAS server into a DOM object
1534                                 if ( !($dom = domxml_open_mem($text_response))) {
1535                                         phpCAS::trace('domxml_open_mem() failed');
1536                                         $this->authError('SA not validated',
1537                                                 $validate_url,
1538                                                 FALSE/*$no_response*/,
1539                                                 TRUE/*$bad_response*/,
1540                                                 $text_response);
1541                                 }
1542                                 // read the root node of the XML tree
1543                                 if ( !($tree_response = $dom->document_element()) ) {
1544                                         phpCAS::trace('document_element() failed');
1545                                         $this->authError('SA not validated',
1546                                                 $validate_url,
1547                                                 FALSE/*$no_response*/,
1548                                                 TRUE/*$bad_response*/,
1549                                                 $text_response);
1550                                 }
1551                                 // insure that tag name is 'Envelope'
1552                                 if ( $tree_response->node_name() != 'Envelope' ) {
1553                                         phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->node_name().'\'');
1554                                         $this->authError('SA not validated',
1555                                                 $validate_url,
1556                                                 FALSE/*$no_response*/,
1557                                                 TRUE/*$bad_response*/,
1558                                                 $text_response);
1559                                 }
1560                                 // check for the NameIdentifier tag in the SAML response
1561                                 if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("NameIdentifier")) != 0) {
1562                                         phpCAS::trace('NameIdentifier found');
1563                                         $user = trim($success_elements[0]->get_content());
1564                                         phpCAS::trace('user = `'.$user.'`');
1565                                         $this->setUser($user);
1566                                         $this->setSessionAttributes($text_response);
1567                                 } else {
1568                                         phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
1569                                         $this->authError('SA not validated',
1570                                                 $validate_url,
1571                                                 FALSE/*$no_response*/,
1572                                                 TRUE/*$bad_response*/,
1573                                                 $text_response);
1574                                 }
1575                                 break;
1576                 }
1577                 $this->renameSession($this->getSA());
1578                 // at this step, ST has been validated and $this->_user has been set,
1579                 phpCAS::traceEnd(TRUE);
1580                 return TRUE;
1581                 }
1582         
1583         /**
1584          * This method will parse the DOM and pull out the attributes from the SAML
1585          * payload and put them into an array, then put the array into the session.
1586          *
1587          * @param $text_response the SAML payload.
1588          * @return bool TRUE when successfull and FALSE if no attributes a found
1589          *
1590          * @private
1591          */
1592         function setSessionAttributes($text_response)
1593                 {
1594                 phpCAS::traceBegin();
1595                 
1596                 $result = FALSE;
1597                 
1598                 if (isset($_SESSION[SAML_ATTRIBUTES])) {
1599                         phpCAS::trace("session attrs already set.");  //testbml - do we care?
1600                 }
1601                 
1602                 $attr_array = array();
1603                 
1604                 if (($dom = domxml_open_mem($text_response))) {
1605                         $xPath = $dom->xpath_new_context();
1606                         $xPath->xpath_register_ns('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
1607                         $xPath->xpath_register_ns('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
1608                         $nodelist = $xPath->xpath_eval("//saml:Attribute");
1609                         if($nodelist){
1610                                 $attrs = $nodelist->nodeset;
1611                                 foreach($attrs as $attr){
1612                                         $xres = $xPath->xpath_eval("saml:AttributeValue", $attr);
1613                                         $name = $attr->get_attribute("AttributeName");
1614                                         $value_array = array();
1615                                         foreach($xres->nodeset as $node){
1616                                                 $value_array[] = $node->get_content();
1617                                         }
1618                                         $attr_array[$name] = $value_array;
1619                                 }
1620                                 $_SESSION[SAML_ATTRIBUTES] = $attr_array;
1621                                 // UGent addition...
1622                                 foreach($attr_array as $attr_key => $attr_value) {
1623                                         if(count($attr_value) > 1) {
1624                                                 $this->_attributes[$attr_key] = $attr_value;
1625                                                 phpCAS::trace("* " . $attr_key . "=" . $attr_value);
1626                                         }
1627                                         else {
1628                                                 $this->_attributes[$attr_key] = $attr_value[0];
1629                                                 phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
1630                                         }
1631                                 }
1632                                 $result = TRUE;
1633                         }else{
1634                                 phpCAS::trace("SAML Attributes are empty");
1635                                 $result = FALSE;
1636                         }
1637                 }
1638                 phpCAS::traceEnd($result);
1639                 return $result;
1640                 }
1641         
1642         /** @} */
1643         
1644         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1645         // XX                                                                    XX
1646         // XX                     PROXY FEATURES (CAS 2.0)                       XX
1647         // XX                                                                    XX
1648         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1649         
1650         // ########################################################################
1651         //  PROXYING
1652         // ########################################################################
1653         /**
1654          * @addtogroup internalProxy
1655          * @{
1656          */
1657         
1658         /**
1659          * A boolean telling if the client is a CAS proxy or not. Written by CASClient::CASClient(), 
1660          * read by CASClient::isProxy().
1661          *
1662          * @private
1663          */
1664         var $_proxy;
1665         
1666         /**
1667          * Tells if a CAS client is a CAS proxy or not
1668          *
1669          * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise
1670          *
1671          * @private
1672          */
1673         function isProxy()
1674                 {
1675                 return $this->_proxy;
1676                 }
1677         
1678         /** @} */
1679         // ########################################################################
1680         //  PGT
1681         // ########################################################################
1682         /**
1683          * @addtogroup internalProxy
1684          * @{
1685          */  
1686         
1687         /**
1688          * the Proxy Grnting Ticket given by the CAS server (empty otherwise). 
1689          * Written by CASClient::setPGT(), read by CASClient::getPGT() and CASClient::hasPGT().
1690          *
1691          * @hideinitializer
1692          * @private
1693          */
1694         var $_pgt = '';
1695         
1696         /**
1697          * This method returns the Proxy Granting Ticket given by the CAS server.
1698          * @return The Proxy Granting Ticket.
1699          * @private
1700          */
1701         function getPGT()
1702                 { return $this->_pgt; }
1703         
1704         /**
1705          * This method stores the Proxy Granting Ticket.
1706          * @param $pgt The Proxy Granting Ticket.
1707          * @private
1708          */
1709         function setPGT($pgt)
1710                 { $this->_pgt = $pgt; }
1711         
1712         /**
1713          * This method tells if a Proxy Granting Ticket was stored.
1714          * @return TRUE if a Proxy Granting Ticket has been stored.
1715          * @private
1716          */
1717         function hasPGT()
1718                 { return !empty($this->_pgt); }
1719         
1720         /** @} */
1721         
1722         // ########################################################################
1723         //  CALLBACK MODE
1724         // ########################################################################
1725         /**
1726          * @addtogroup internalCallback
1727          * @{
1728          */  
1729         /**
1730          * each PHP script using phpCAS in proxy mode is its own callback to get the
1731          * PGT back from the CAS server. callback_mode is detected by the constructor
1732          * thanks to the GET parameters.
1733          */
1734         
1735         /**
1736          * a boolean to know if the CAS client is running in callback mode. Written by
1737          * CASClient::setCallBackMode(), read by CASClient::isCallbackMode().
1738          *
1739          * @hideinitializer
1740          * @private
1741          */
1742         var $_callback_mode = FALSE;
1743         
1744         /**
1745          * This method sets/unsets callback mode.
1746          *
1747          * @param $callback_mode TRUE to set callback mode, FALSE otherwise.
1748          *
1749          * @private
1750          */
1751         function setCallbackMode($callback_mode)
1752                 {
1753                 $this->_callback_mode = $callback_mode;
1754                 }
1755         
1756         /**
1757          * This method returns TRUE when the CAs client is running i callback mode, 
1758          * FALSE otherwise.
1759          *
1760          * @return A boolean.
1761          *
1762          * @private
1763          */
1764         function isCallbackMode()
1765                 {
1766                 return $this->_callback_mode;
1767                 }
1768         
1769         /**
1770          * the URL that should be used for the PGT callback (in fact the URL of the 
1771          * current request without any CGI parameter). Written and read by 
1772          * CASClient::getCallbackURL().
1773          *
1774          * @hideinitializer
1775          * @private
1776          */
1777         var $_callback_url = '';
1778         
1779         /**
1780          * This method returns the URL that should be used for the PGT callback (in
1781          * fact the URL of the current request without any CGI parameter, except if
1782          * phpCAS::setFixedCallbackURL() was used).
1783          *
1784          * @return The callback URL
1785          *
1786          * @private
1787          */
1788         function getCallbackURL()
1789                 {
1790                 // the URL is built when needed only
1791                 if ( empty($this->_callback_url) ) {
1792                         $final_uri = '';
1793                         // remove the ticket if present in the URL
1794                         $final_uri = 'https://';
1795                         /* replaced by Julien Marchal - v0.4.6
1796                          * $this->uri .= $_SERVER['SERVER_NAME'];
1797                          */
1798                         if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1799                                 /* replaced by teedog - v0.4.12
1800                                  * $final_uri .= $_SERVER['SERVER_NAME'];
1801                                  */
1802                                 if (empty($_SERVER['SERVER_NAME'])) {
1803                                         $final_uri .= $_SERVER['HTTP_HOST'];
1804                                 } else {
1805                                         $final_uri .= $_SERVER['SERVER_NAME'];
1806                                 }
1807                         } else {
1808                                 $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1809                         }
1810                         if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
1811                                         || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
1812                                 $final_uri .= ':';
1813                                 $final_uri .= $_SERVER['SERVER_PORT'];
1814                         }
1815                         $request_uri = $_SERVER['REQUEST_URI'];
1816                         $request_uri = preg_replace('/\?.*$/','',$request_uri);
1817                         $final_uri .= $request_uri;
1818                         $this->setCallbackURL($final_uri);
1819                 }
1820                 return $this->_callback_url;
1821                 }
1822         
1823         /**
1824          * This method sets the callback url.
1825          *
1826          * @param $callback_url url to set callback 
1827          *
1828          * @private
1829          */
1830         function setCallbackURL($url)
1831                 {
1832                 return $this->_callback_url = $url;
1833                 }
1834         
1835         /**
1836          * This method is called by CASClient::CASClient() when running in callback
1837          * mode. It stores the PGT and its PGT Iou, prints its output and halts.
1838          *
1839          * @private
1840          */
1841         function callback()
1842                 {
1843                 phpCAS::traceBegin();
1844                 if (preg_match('/PGTIOU-[\.\-\w]/', $_GET['pgtIou'])){
1845                         if(preg_match('/[PT]GT-[\.\-\w]/', $_GET['pgtId'])){
1846                                 $this->printHTMLHeader('phpCAS callback');
1847                                 $pgt_iou = $_GET['pgtIou'];
1848                                 $pgt = $_GET['pgtId'];
1849                                 phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
1850                                 echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
1851                                 $this->storePGT($pgt,$pgt_iou);
1852                                 $this->printHTMLFooter();
1853                         }else{
1854                                 phpCAS::error('PGT format invalid' . $_GET['pgtId']);
1855                         }
1856                 }else{
1857                         phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']);
1858                 }
1859                 phpCAS::traceExit();
1860                 exit();
1861                 }
1862         
1863         /** @} */
1864         
1865         // ########################################################################
1866         //  PGT STORAGE
1867         // ########################################################################
1868         /**
1869          * @addtogroup internalPGTStorage
1870          * @{
1871          */  
1872         
1873         /**
1874          * an instance of a class inheriting of PGTStorage, used to deal with PGT
1875          * storage. Created by CASClient::setPGTStorageFile() or CASClient::setPGTStorageDB(), used 
1876          * by CASClient::setPGTStorageFile(), CASClient::setPGTStorageDB() and CASClient::initPGTStorage().
1877          *
1878          * @hideinitializer
1879          * @private
1880          */
1881         var $_pgt_storage = null;
1882         
1883         /**
1884          * This method is used to initialize the storage of PGT's.
1885          * Halts on error.
1886          *
1887          * @private
1888          */
1889         function initPGTStorage()
1890                 {
1891                 // if no SetPGTStorageXxx() has been used, default to file
1892                 if ( !is_object($this->_pgt_storage) ) {
1893                         $this->setPGTStorageFile();
1894                 }
1895                 
1896                 // initializes the storage
1897                 $this->_pgt_storage->init();
1898                 }
1899         
1900         /**
1901          * This method stores a PGT. Halts on error.
1902          *
1903          * @param $pgt the PGT to store
1904          * @param $pgt_iou its corresponding Iou
1905          *
1906          * @private
1907          */
1908         function storePGT($pgt,$pgt_iou)
1909                 {
1910                 // ensure that storage is initialized
1911                 $this->initPGTStorage();
1912                 // writes the PGT
1913                 $this->_pgt_storage->write($pgt,$pgt_iou);
1914                 }
1915         
1916         /**
1917          * This method reads a PGT from its Iou and deletes the corresponding storage entry.
1918          *
1919          * @param $pgt_iou the PGT Iou
1920          *
1921          * @return The PGT corresponding to the Iou, FALSE when not found.
1922          *
1923          * @private
1924          */
1925         function loadPGT($pgt_iou)
1926                 {
1927                 // ensure that storage is initialized
1928                 $this->initPGTStorage();
1929                 // read the PGT
1930                 return $this->_pgt_storage->read($pgt_iou);
1931                 }
1932         
1933         /**
1934          * This method is used to tell phpCAS to store the response of the
1935          * CAS server to PGT requests onto the filesystem. 
1936          *
1937          * @param $format the format used to store the PGT's (`plain' and `xml' allowed)
1938          * @param $path the path where the PGT's should be stored
1939          *
1940          * @public
1941          */
1942         function setPGTStorageFile($format='',
1943                 $path='')
1944                 {
1945                 // check that the storage has not already been set
1946                 if ( is_object($this->_pgt_storage) ) {
1947                         phpCAS::error('PGT storage already defined');
1948                 }
1949                 
1950                 // create the storage object
1951                 $this->_pgt_storage = new PGTStorageFile($this,$format,$path);
1952                 }
1953         
1954         // ########################################################################
1955         //  PGT VALIDATION
1956         // ########################################################################
1957         /**
1958          * This method is used to validate a PGT; halt on failure.
1959          * 
1960          * @param $validate_url the URL of the request to the CAS server.
1961          * @param $text_response the response of the CAS server, as is (XML text); result
1962          * of CASClient::validateST() or CASClient::validatePT().
1963          * @param $tree_response the response of the CAS server, as a DOM XML tree; result
1964          * of CASClient::validateST() or CASClient::validatePT().
1965          *
1966          * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
1967          *
1968          * @private
1969          */
1970         function validatePGT(&$validate_url,$text_response,$tree_response)
1971                 {
1972                 // here cannot use phpCAS::traceBegin(); alongside domxml-php4-to-php5.php
1973                 phpCAS::log('start validatePGT()');
1974                 if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
1975                         phpCAS::trace('<proxyGrantingTicket> not found');
1976                         // authentication succeded, but no PGT Iou was transmitted
1977                         $this->authError('Ticket validated but no PGT Iou transmitted',
1978                                 $validate_url,
1979                                 FALSE/*$no_response*/,
1980                                 FALSE/*$bad_response*/,
1981                                 $text_response);
1982                 } else {
1983                         // PGT Iou transmitted, extract it
1984                         $pgt_iou = trim($arr[0]->get_content());
1985                         if(preg_match('/PGTIOU-[\.\-\w]/',$pgt_iou)){ 
1986                                 $pgt = $this->loadPGT($pgt_iou);
1987                                 if ( $pgt == FALSE ) {
1988                                         phpCAS::trace('could not load PGT');
1989                                         $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
1990                                                 $validate_url,
1991                                                 FALSE/*$no_response*/,
1992                                                 FALSE/*$bad_response*/,
1993                                                 $text_response);
1994                                 }
1995                                 $this->setPGT($pgt);
1996                         }else{
1997                                 phpCAS::trace('PGTiou format error');
1998                                 $this->authError('PGT Iou was transmitted but has wrong fromat',
1999                                         $validate_url,
2000                                         FALSE/*$no_response*/,
2001                                         FALSE/*$bad_response*/,
2002                                         $text_response);
2003                         }
2004                         
2005                 }
2006                 // here, cannot use     phpCAS::traceEnd(TRUE); alongside domxml-php4-to-php5.php
2007                 phpCAS::log('end validatePGT()');
2008                 return TRUE;
2009                 }
2010         
2011         // ########################################################################
2012         //  PGT VALIDATION
2013         // ########################################################################
2014         
2015         /**
2016          * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2017          * 
2018          * @param $target_service the service to ask for with the PT.
2019          * @param $err_code an error code (PHPCAS_SERVICE_OK on success).
2020          * @param $err_msg an error message (empty on success).
2021          *
2022          * @return a Proxy Ticket, or FALSE on error.
2023          *
2024          * @private
2025          */
2026         function retrievePT($target_service,&$err_code,&$err_msg)
2027                 {
2028                 phpCAS::traceBegin();
2029                 
2030                 // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
2031                 // set to false and $err_msg to an error message. At the end, if $pt is FALSE 
2032                 // and $error_msg is still empty, it is set to 'invalid response' (the most
2033                 // commonly encountered error).
2034                 $err_msg = '';
2035                 
2036                 // build the URL to retrieve the PT
2037                 //      $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
2038                 $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT();
2039                 
2040                 // open and read the URL
2041                 if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) {
2042                         phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
2043                         $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2044                         $err_msg = 'could not retrieve PT (no response from the CAS server)';
2045                         phpCAS::traceEnd(FALSE);
2046                         return FALSE;
2047                 }
2048                 
2049                 $bad_response = FALSE;
2050                 
2051                 if ( !$bad_response ) {
2052                         // read the response of the CAS server into a DOM object
2053                         if ( !($dom = @domxml_open_mem($cas_response))) {
2054                                 phpCAS::trace('domxml_open_mem() failed');
2055                                 // read failed
2056                                 $bad_response = TRUE;
2057                         } 
2058                 }
2059                 
2060                 if ( !$bad_response ) {
2061                         // read the root node of the XML tree
2062                         if ( !($root = $dom->document_element()) ) {
2063                                 phpCAS::trace('document_element() failed');
2064                                 // read failed
2065                                 $bad_response = TRUE;
2066                         } 
2067                 }
2068                 
2069                 if ( !$bad_response ) {
2070                         // insure that tag name is 'serviceResponse'
2071                         if ( $root->node_name() != 'serviceResponse' ) {
2072                                 phpCAS::trace('node_name() failed');
2073                                 // bad root node
2074                                 $bad_response = TRUE;
2075                         } 
2076                 }
2077                 
2078                 if ( !$bad_response ) {
2079                         // look for a proxySuccess tag
2080                         if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
2081                                 // authentication succeded, look for a proxyTicket tag
2082                                 if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
2083                                         $err_code = PHPCAS_SERVICE_OK;
2084                                         $err_msg = '';
2085                                         phpCAS::trace('original PT: '.trim($arr[0]->get_content()));
2086                                         $pt = trim($arr[0]->get_content());
2087                                         phpCAS::traceEnd($pt);
2088                                         return $pt;
2089                                 } else {
2090                                         phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
2091                                 }
2092                         } 
2093                         // look for a proxyFailure tag
2094                         else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
2095                                 // authentication failed, extract the error
2096                                 $err_code = PHPCAS_SERVICE_PT_FAILURE;
2097                                 $err_msg = 'PT retrieving failed (code=`'
2098                                         .$arr[0]->get_attribute('code')
2099                                         .'\', message=`'
2100                                         .trim($arr[0]->get_content())
2101                                         .'\')';
2102                                 phpCAS::traceEnd(FALSE);
2103                                 return FALSE;
2104                         } else {
2105                                 phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
2106                         }
2107                 }
2108                 
2109                 // at this step, we are sure that the response of the CAS server was ill-formed
2110                 $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
2111                 $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
2112                 
2113                 phpCAS::traceEnd(FALSE);
2114                 return FALSE;
2115                 }
2116         
2117         // ########################################################################
2118         // ACCESS TO EXTERNAL SERVICES
2119         // ########################################################################
2120         
2121         /**
2122          * This method is used to acces a remote URL.
2123          *
2124          * @param $url the URL to access.
2125          * @param $cookies an array containing cookies strings such as 'name=val'
2126          * @param $headers an array containing the HTTP header lines of the response
2127          * (an empty array on failure).
2128          * @param $body the body of the response, as a string (empty on failure).
2129          * @param $err_msg an error message, filled on failure.
2130          *
2131          * @return TRUE on success, FALSE otherwise (in this later case, $err_msg
2132          * contains an error message).
2133          *
2134          * @private
2135          */
2136         function readURL($url,$cookies,&$headers,&$body,&$err_msg)
2137                 {
2138                 phpCAS::traceBegin();
2139                 $headers = '';
2140                 $body = '';
2141                 $err_msg = '';
2142                 
2143                 $res = TRUE;
2144                 
2145                 // initialize the CURL session
2146                 $ch = curl_init($url);
2147                 
2148                 if (version_compare(PHP_VERSION,'5.1.3','>=')) {
2149                         //only avaible in php5
2150                         curl_setopt_array($ch, $this->_curl_options);
2151                 } else {
2152                         foreach ($this->_curl_options as $key => $value) {
2153                                 curl_setopt($ch, $key, $value);
2154                         }
2155                 }
2156                 
2157                 if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) {
2158                         phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
2159                 }
2160                 if ($this->_cas_server_cert != '' && $this->_cas_server_ca_cert != '') {
2161                         // This branch added by IDMS. Seems phpCAS implementor got a bit confused about the curl options CURLOPT_SSLCERT and CURLOPT_CAINFO
2162                         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2163                         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2164                         curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2165                         curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2166                         curl_setopt($ch, CURLOPT_VERBOSE, '1');
2167                         phpCAS::trace('CURL: Set all required opts for mutual authentication ------');
2168                 } else if ($this->_cas_server_cert != '' ) {
2169                         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2170                         curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert);
2171                 } else if ($this->_cas_server_ca_cert != '') {
2172                         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
2173                         curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert);
2174                 } else {
2175                         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
2176                         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
2177                 }
2178                 
2179                 // return the CURL output into a variable
2180                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2181                 // get the HTTP header with a callback
2182                 $this->_curl_headers = array(); // empty the headers array
2183                 curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers'));
2184                 // add cookies headers
2185                 if ( is_array($cookies) ) {
2186                         curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
2187                 }
2188                 // add extra stuff if SAML
2189                 if ($this->hasSA()) {
2190                         $more_headers = array ("soapaction: http://www.oasis-open.org/committees/security",
2191                                 "cache-control: no-cache",
2192                                 "pragma: no-cache",
2193                                 "accept: text/xml",
2194                                 "connection: keep-alive",
2195                         "content-type: text/xml");
2196                         
2197                         curl_setopt($ch, CURLOPT_HTTPHEADER, $more_headers);
2198                         curl_setopt($ch, CURLOPT_POST, 1);
2199                         $data = $this->buildSAMLPayload();
2200                         //phpCAS::trace('SAML Payload: '.print_r($data, TRUE));
2201                         curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
2202                 }
2203                 // perform the query
2204                 $buf = curl_exec ($ch);
2205                 //phpCAS::trace('CURL: Call completed. Response body is: \''.$buf.'\'');
2206                 if ( $buf === FALSE ) {
2207                         phpCAS::trace('curl_exec() failed');
2208                         $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
2209                         //phpCAS::trace('curl error: '.$err_msg);
2210                         // close the CURL session
2211                         curl_close ($ch);
2212                         $res = FALSE;
2213                 } else {
2214                         // close the CURL session
2215                         curl_close ($ch);
2216                         
2217                         $headers = $this->_curl_headers;
2218                         $body = $buf;
2219                 }
2220                 
2221                 phpCAS::traceEnd($res);
2222                 return $res;
2223                 }
2224         
2225         /**
2226          * This method is used to build the SAML POST body sent to /samlValidate URL.
2227          *
2228          * @return the SOAP-encased SAMLP artifact (the ticket).
2229          *
2230          * @private
2231          */
2232         function buildSAMLPayload()
2233                 {
2234                 phpCAS::traceBegin();
2235                 
2236                 //get the ticket
2237                 $sa = $this->getSA();
2238                 //phpCAS::trace("SA: ".$sa);
2239                 
2240                 $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
2241                 
2242                 phpCAS::traceEnd($body);
2243                 return ($body);
2244                 }
2245         
2246         /**
2247          * This method is the callback used by readURL method to request HTTP headers.
2248          */
2249         var $_curl_headers = array();
2250         function _curl_read_headers($ch, $header)
2251                 {
2252                 $this->_curl_headers[] = $header;
2253                 return strlen($header);
2254                 }
2255         
2256         /**
2257          * This method is used to access an HTTP[S] service.
2258          * 
2259          * @param $url the service to access.
2260          * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2261          * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2262          * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
2263          * @param $output the output of the service (also used to give an error
2264          * message on failure).
2265          *
2266          * @return TRUE on success, FALSE otherwise (in this later case, $err_code
2267          * gives the reason why it failed and $output contains an error message).
2268          *
2269          * @public
2270          */
2271         function serviceWeb($url,&$err_code,&$output)
2272                 {
2273                 phpCAS::traceBegin();
2274                 // at first retrieve a PT
2275                 $pt = $this->retrievePT($url,$err_code,$output);
2276                 
2277                 $res = TRUE;
2278                 
2279                 // test if PT was retrieved correctly
2280                 if ( !$pt ) {
2281                         // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2282                         phpCAS::trace('PT was not retrieved correctly');
2283                         $res = FALSE;
2284                 } else {
2285                         // add cookies if necessary
2286                         $cookies = $this->getCookies($url);
2287                         
2288                         // build the URL including the PT
2289                         if ( strstr($url,'?') === FALSE ) {
2290                                 $service_url = $url.'?ticket='.$pt;
2291                         } else {
2292                                 $service_url = $url.'&ticket='.$pt;
2293                         }
2294                         
2295                         phpCAS::trace('reading URL`'.$service_url.'\'');
2296                         if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) {
2297                                 phpCAS::trace('could not read URL`'.$service_url.'\'');
2298                                 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2299                                 // give an error message
2300                                 $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2301                                         $service_url,
2302                                         $err_msg);
2303                                 $res = FALSE;
2304                         } else {
2305                                 // URL has been fetched, extract the cookies
2306                                 phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:');
2307                                 $this->setCookies($headers,$url);
2308                                 // Check for a possible redirect (phpCAS authenticiation redirect after ticket removal)
2309                                 foreach($headers as $header){
2310                                         if (preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches))
2311                                         {
2312                                                 $redirect_url = trim(array_pop($matches));
2313                                                 phpCAS :: trace('Found redirect:'.$redirect_url);
2314                                                 $cookies = $this->getCookies($redirect_url);
2315                                                 phpCAS::trace('reading URL`'.$redirect_url.'\'');
2316                                                 if ( !$this->readURL($redirect_url,$cookies,$headers,$output,$err_msg) ) {
2317                                                         phpCAS::trace('could not read URL`'.$redirect_url.'\'');
2318                                                         $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2319                                                         // give an error message
2320                                                         $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2321                                                                 $service_url,
2322                                                                 $err_msg);
2323                                                         $res = FALSE;
2324                                                 } else {
2325                                                         // URL has been fetched, extract the cookies
2326                                                         phpCAS::trace('URL`'.$redirect_url.'\' has been read, storing cookies:');
2327                                                         $this->setCookies($headers,$redirect_url);
2328                                                 }
2329                                                 break;
2330                                         }
2331                                 }
2332                         }
2333                 }
2334                 
2335                 phpCAS::traceEnd($res);
2336                 return $res;
2337                 }
2338         
2339         /**
2340          * This method stores cookies from a HTTP Header in the session
2341          * @param $header HTTP Header
2342          * @param $url the url the Header is from
2343          */
2344         
2345         function setCookies($headers,$url){
2346                 phpCAS::traceBegin();
2347                 foreach ( $headers as $header ) {
2348                         // test if the header is a cookie
2349                         if ( preg_match('/^Set-Cookie:/',$header) ) {
2350                                 // the header is a cookie, remove the beginning
2351                                 $header_val = preg_replace('/^Set-Cookie: */','',$header);
2352                                 // extract interesting information
2353                                 $name_val = strtok($header_val,'; ');
2354                                 // extract the name and the value of the cookie
2355                                 $cookie_name = strtok($name_val,'=');
2356                                 $cookie_val = strtok('=');
2357                                 // store the cookie 
2358                                 $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
2359                                 phpCAS::trace($cookie_name.' -> '.$cookie_val);
2360                         }
2361                 }
2362                 phpCAS::traceEnd();
2363         }
2364         
2365         /**
2366          * This method get the cookies from the session
2367          */
2368          
2369         function getCookies($url){
2370                 $cookies = array();
2371                 if ( isset($_SESSION['phpCAS']['services'][$url]['cookies']) && 
2372                                 is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
2373                         foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) { 
2374                                 $cookies[] = $name.'='.$val;
2375                         }
2376                 }
2377                 return $cookies;
2378         }
2379         
2380         /**
2381          * This method is used to access an IMAP/POP3/NNTP service.
2382          * 
2383          * @param $url a string giving the URL of the service, including the mailing box
2384          * for IMAP URLs, as accepted by imap_open().
2385          * @param $service a string giving for CAS retrieve Proxy ticket
2386          * @param $flags options given to imap_open().
2387          * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on
2388          * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
2389          * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE.
2390          * @param $err_msg an error message on failure
2391          * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL
2392          * on success, FALSE on error).
2393          *
2394          * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code
2395          * gives the reason why it failed and $err_msg contains an error message).
2396          *
2397          * @public
2398          */
2399         function serviceMail($url,$service,$flags,&$err_code,&$err_msg,&$pt)
2400                 {
2401                 phpCAS::traceBegin();
2402                 // at first retrieve a PT
2403                 $pt = $this->retrievePT($service,$err_code,$output);
2404                 
2405                 $stream = FALSE;
2406                 
2407                 // test if PT was retrieved correctly
2408                 if ( !$pt ) {
2409                         // note: $err_code and $err_msg are filled by CASClient::retrievePT()
2410                         phpCAS::trace('PT was not retrieved correctly');
2411                 } else {
2412                         phpCAS::trace('opening IMAP URL `'.$url.'\'...');
2413                         $stream = @imap_open($url,$this->getUser(),$pt,$flags);
2414                         if ( !$stream ) {
2415                                 phpCAS::trace('could not open URL');
2416                                 $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
2417                                 // give an error message
2418                                 $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
2419                                         $service_url,
2420                                         var_export(imap_errors(),TRUE));
2421                                 $pt = FALSE;
2422                                 $stream = FALSE;
2423                         } else {
2424                                 phpCAS::trace('ok');
2425                         }
2426                 }
2427                 
2428                 phpCAS::traceEnd($stream);
2429                 return $stream;
2430                 }
2431         
2432         /** @} */
2433         
2434         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2435         // XX                                                                    XX
2436         // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
2437         // XX                                                                    XX
2438         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2439         
2440         // ########################################################################
2441         //  PT
2442         // ########################################################################
2443         /**
2444          * @addtogroup internalProxied
2445          * @{
2446          */  
2447         
2448         /**
2449          * the Proxy Ticket provided in the URL of the request if present
2450          * (empty otherwise). Written by CASClient::CASClient(), read by 
2451          * CASClient::getPT() and CASClient::hasPGT().
2452          *
2453          * @hideinitializer
2454          * @private
2455          */
2456         var $_pt = '';
2457         
2458         /**
2459          * This method returns the Proxy Ticket provided in the URL of the request.
2460          * @return The proxy ticket.
2461          * @private
2462          */
2463         function getPT()
2464                 {
2465                 //      return 'ST'.substr($this->_pt, 2);
2466                 return $this->_pt;
2467                 }
2468         
2469         /**
2470          * This method stores the Proxy Ticket.
2471          * @param $pt The Proxy Ticket.
2472          * @private
2473          */
2474         function setPT($pt)
2475                 { $this->_pt = $pt; }
2476         
2477         /**
2478          * This method tells if a Proxy Ticket was stored.
2479          * @return TRUE if a Proxy Ticket has been stored.
2480          * @private
2481          */
2482         function hasPT()
2483                 { return !empty($this->_pt); }
2484         /**
2485          * This method returns the SAML Ticket provided in the URL of the request.
2486          * @return The SAML ticket.
2487          * @private
2488          */
2489         function getSA()
2490                 { return 'ST'.substr($this->_sa, 2); }
2491         
2492         /**
2493          * This method stores the SAML Ticket.
2494          * @param $sa The SAML Ticket.
2495          * @private
2496          */
2497         function setSA($sa)
2498                 { $this->_sa = $sa; }
2499         
2500         /**
2501          * This method tells if a SAML Ticket was stored.
2502          * @return TRUE if a SAML Ticket has been stored.
2503          * @private
2504          */
2505         function hasSA()
2506                 { return !empty($this->_sa); }
2507         
2508         /** @} */
2509         // ########################################################################
2510         //  PT VALIDATION
2511         // ########################################################################
2512         /**
2513          * @addtogroup internalProxied
2514          * @{
2515          */  
2516         
2517         /**
2518          * This method is used to validate a ST or PT; halt on failure
2519          * Used for all CAS 2.0 validations
2520          * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError().
2521          *
2522          * @private
2523          */
2524         function validatePT(&$validate_url,&$text_response,&$tree_response)
2525                 {
2526                 phpCAS::traceBegin();
2527                 // build the URL to validate the ticket
2528                 $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
2529                 
2530                 if ( $this->isProxy() ) {
2531                         // pass the callback url for CAS proxies
2532                         $validate_url .= '&pgtUrl='.urlencode($this->getCallbackURL());
2533                 }
2534                 
2535                 // open and read the URL
2536                 if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
2537                         phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
2538                         $this->authError('PT not validated',
2539                                 $validate_url,
2540                                 TRUE/*$no_response*/);
2541                 }
2542                 
2543                 // read the response of the CAS server into a DOM object
2544                 if ( !($dom = domxml_open_mem($text_response))) {
2545                         // read failed
2546                         $this->authError('PT not validated',
2547                                 $validate_url,
2548                                 FALSE/*$no_response*/,
2549                                 TRUE/*$bad_response*/,
2550                                 $text_response);
2551                 }
2552                 // read the root node of the XML tree
2553                 if ( !($tree_response = $dom->document_element()) ) {
2554                         // read failed
2555                         $this->authError('PT not validated',
2556                                 $validate_url,
2557                                 FALSE/*$no_response*/,
2558                                 TRUE/*$bad_response*/,
2559                                 $text_response);
2560                 }
2561                 // insure that tag name is 'serviceResponse'
2562                 if ( $tree_response->node_name() != 'serviceResponse' ) {
2563                         // bad root node
2564                         $this->authError('PT not validated',
2565                                 $validate_url,
2566                                 FALSE/*$no_response*/,
2567                                 TRUE/*$bad_response*/,
2568                                 $text_response);
2569                 }
2570                 if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
2571                         // authentication succeded, extract the user name
2572                         if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
2573                                 // no user specified => error
2574                                 $this->authError('PT not validated',
2575                                         $validate_url,
2576                                         FALSE/*$no_response*/,
2577                                         TRUE/*$bad_response*/,
2578                                         $text_response);
2579                         }
2580                         $this->setUser(trim($arr[0]->get_content()));
2581                         
2582                 } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
2583                         // authentication succeded, extract the error code and message
2584                         $this->authError('PT not validated',
2585                                 $validate_url,
2586                                 FALSE/*$no_response*/,
2587                                 FALSE/*$bad_response*/,
2588                                 $text_response,
2589                                 $arr[0]->get_attribute('code')/*$err_code*/,
2590                                 trim($arr[0]->get_content())/*$err_msg*/);
2591                 } else {
2592                         $this->authError('PT not validated',
2593                                 $validate_url,  
2594                                 FALSE/*$no_response*/,
2595                                 TRUE/*$bad_response*/,
2596                                 $text_response);
2597                 }
2598                 
2599                 $this->renameSession($this->getPT());
2600                 // at this step, PT has been validated and $this->_user has been set,
2601                 
2602                 phpCAS::traceEnd(TRUE);
2603                 return TRUE;
2604                 }
2605         
2606         /** @} */
2607         
2608         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2609         // XX                                                                    XX
2610         // XX                               MISC                                 XX
2611         // XX                                                                    XX
2612         // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2613         
2614         /**
2615          * @addtogroup internalMisc
2616          * @{
2617          */  
2618         
2619         // ########################################################################
2620         //  URL
2621         // ########################################################################
2622         /**
2623          * the URL of the current request (without any ticket CGI parameter). Written 
2624          * and read by CASClient::getURL().
2625          *
2626          * @hideinitializer
2627          * @private
2628          */
2629         var $_url = '';
2630         
2631         /**
2632          * This method returns the URL of the current request (without any ticket
2633          * CGI parameter).
2634          *
2635          * @return The URL
2636          *
2637          * @private
2638          */
2639         function getURL()
2640                 {
2641                 phpCAS::traceBegin();
2642                 // the URL is built when needed only
2643                 if ( empty($this->_url) ) {
2644                         $final_uri = '';
2645                         // remove the ticket if present in the URL
2646                         $final_uri = ($this->isHttps()) ? 'https' : 'http';
2647                         $final_uri .= '://';
2648                         /* replaced by Julien Marchal - v0.4.6
2649                          * $this->_url .= $_SERVER['SERVER_NAME'];
2650                          */
2651                         if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
2652                                 /* replaced by teedog - v0.4.12
2653                                  * $this->_url .= $_SERVER['SERVER_NAME'];
2654                                  */
2655                                 if (empty($_SERVER['SERVER_NAME'])) {
2656                                         $server_name = $_SERVER['HTTP_HOST'];
2657                                 } else {
2658                                         $server_name = $_SERVER['SERVER_NAME'];
2659                                 }
2660                         } else {
2661                                 $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER'];
2662                         }
2663                         $final_uri .= $server_name;
2664                         if (!strpos($server_name, ':')) {
2665                                 if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443)
2666                                                 || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) {
2667                                         $final_uri .= ':';
2668                                         $final_uri .= $_SERVER['SERVER_PORT'];
2669                                 }
2670                         }
2671                         
2672                         $request_uri    = explode('?', $_SERVER['REQUEST_URI'], 2);
2673                         $final_uri              .= $request_uri[0];
2674                         
2675                         if (isset($request_uri[1]) && $request_uri[1])
2676                         {
2677                                 $query_string   = $this->removeParameterFromQueryString('ticket', $request_uri[1]);
2678                                 
2679                                 // If the query string still has anything left, append it to the final URI
2680                                 if ($query_string !== '')
2681                                         $final_uri      .= "?$query_string";
2682                                 
2683                         }
2684                         
2685                         phpCAS::trace("Final URI: $final_uri");
2686                         $this->setURL($final_uri);
2687                 }
2688                 phpCAS::traceEnd($this->_url);
2689                 return $this->_url;
2690         }
2691         
2692
2693                 
2694         /**
2695          * Removes a parameter from a query string
2696          * 
2697          * @param string $parameterName 
2698          * @param string $queryString
2699          * @return string
2700          *
2701          * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
2702          */
2703         function removeParameterFromQueryString($parameterName, $queryString)
2704         {
2705                 $parameterName  = preg_quote($parameterName);
2706                 return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
2707         }
2708
2709         
2710         /**
2711          * This method sets the URL of the current request 
2712          *
2713          * @param $url url to set for service
2714          *
2715          * @private
2716          */
2717         function setURL($url)
2718                 {
2719                 $this->_url = $url;
2720                 }
2721         
2722         // ########################################################################
2723         //  AUTHENTICATION ERROR HANDLING
2724         // ########################################################################
2725         /**
2726          * This method is used to print the HTML output when the user was not authenticated.
2727          *
2728          * @param $failure the failure that occured
2729          * @param $cas_url the URL the CAS server was asked for
2730          * @param $no_response the response from the CAS server (other 
2731          * parameters are ignored if TRUE)
2732          * @param $bad_response bad response from the CAS server ($err_code
2733          * and $err_msg ignored if TRUE)
2734          * @param $cas_response the response of the CAS server
2735          * @param $err_code the error code given by the CAS server
2736          * @param $err_msg the error message given by the CAS server
2737          *
2738          * @private
2739          */
2740         function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
2741                 {
2742                 phpCAS::traceBegin();
2743                 
2744                 $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
2745                 printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),htmlentities($this->getURL()),$_SERVER['SERVER_ADMIN']);
2746                 phpCAS::trace('CAS URL: '.$cas_url);
2747                 phpCAS::trace('Authentication failure: '.$failure);
2748                 if ( $no_response ) {
2749                         phpCAS::trace('Reason: no response from the CAS server');
2750                 } else {
2751                         if ( $bad_response ) {
2752                                 phpCAS::trace('Reason: bad response from the CAS server');
2753                         } else {
2754                                 switch ($this->getServerVersion()) {
2755                                         case CAS_VERSION_1_0:
2756                                                 phpCAS::trace('Reason: CAS error');
2757                                                 break;
2758                                         case CAS_VERSION_2_0:
2759                                                 if ( empty($err_code) )
2760                                                         phpCAS::trace('Reason: no CAS error');
2761                                                 else
2762                                                         phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
2763                                                 break;
2764                                 }
2765                         }
2766                         phpCAS::trace('CAS response: '.$cas_response);
2767                 }
2768                 $this->printHTMLFooter();
2769                 phpCAS::traceExit();
2770                 exit();
2771                 }
2772         
2773         /** @} */
2774 }
2775
2776 ?>