ILIAS  Release_3_10_x_branch Revision 61812
 All Data Structures Namespaces Files Functions Variables Groups Pages
client.php
Go to the documentation of this file.
1 <?php
2 
8 // include internationalization stuff
9 include_once(dirname(__FILE__).'/languages/languages.php');
10 
11 // include PGT storage classes
12 include_once(dirname(__FILE__).'/PGTStorage/pgt-main.php');
13 
22 class CASClient
23 {
24 
25  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
26  // XX XX
27  // XX CONFIGURATION XX
28  // XX XX
29  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
30 
31  // ########################################################################
32  // HTML OUTPUT
33  // ########################################################################
52  function HTMLFilterOutput($str)
53  {
54  $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
55  $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
56  $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
57  echo $str;
58  }
59 
68  var $_output_header = '';
69 
80  {
81  $this->HTMLFilterOutput(str_replace('__TITLE__',
82  $title,
83  (empty($this->_output_header)
84  ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
85  : $this->output_header)
86  )
87  );
88  }
89 
98  var $_output_footer = '';
99 
107  function printHTMLFooter()
108  {
109  $this->HTMLFilterOutput(empty($this->_output_footer)
110  ?('<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>')
111  :$this->_output_footer);
112  }
113 
121  function setHTMLHeader($header)
122  {
123  $this->_output_header = $header;
124  }
125 
133  function setHTMLFooter($footer)
134  {
135  $this->_output_footer = $footer;
136  }
137 
139  // ########################################################################
140  // INTERNATIONALIZATION
141  // ########################################################################
156  var $_lang = '';
157 
165  function getLang()
166  {
167  if ( empty($this->_lang) )
169  return $this->_lang;
170  }
171 
182 
192  function getString($str)
193  {
194  // call CASclient::getLang() to be sure the language is initialized
195  $this->getLang();
196 
197  if ( !isset($this->_strings[$str]) ) {
198  trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
199  }
200  return $this->_strings[$str];
201  }
202 
212  function setLang($lang)
213  {
214  // include the corresponding language file
215  include_once(dirname(__FILE__).'/languages/'.$lang.'.php');
216 
217  if ( !is_array($this->_strings) ) {
218  trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
219  }
220  $this->_lang = $lang;
221  }
222 
224  // ########################################################################
225  // CAS SERVER CONFIG
226  // ########################################################################
256  var $_server = array(
257  'version' => -1,
258  'hostname' => 'none',
259  'port' => -1,
260  'uri' => 'none'
261  );
262 
268  function getServerVersion()
269  {
270  return $this->_server['version'];
271  }
272 
278  function getServerHostname()
279  { return $this->_server['hostname']; }
280 
286  function getServerPort()
287  { return $this->_server['port']; }
288 
294  function getServerURI()
295  { return $this->_server['uri']; }
296 
302  function getServerBaseURL()
303  {
304  // the URL is build only when needed
305  if ( empty($this->_server['base_url']) ) {
306 
307  // to do: undo this
308  //$this->_server['base_url'] = 'https://'
309  $this->_server['base_url'] = 'https://'
310  .$this->getServerHostname()
311  .':'
312  .$this->getServerPort()
313  .$this->getServerURI();
314  }
315 //echo "-".$this->_server['base_url']."-";
316  return $this->_server['base_url'];
317  }
318 
325  function getServerLoginURL($gateway)
326  {
327  phpCAS::traceBegin();
328  // the URL is build only when needed
329  if ( empty($this->_server['login_url']) ) {
330  $this->_server['login_url'] = $this->getServerBaseURL();
331  $this->_server['login_url'] .= 'login?service=';
332  $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL());
333  if ($gateway) {
334  $this->_server['login_url'] .= '&gateway=true';
335  }
336  }
337  phpCAS::traceEnd($this->_server['login_url']);
338  return $this->_server['login_url'];
339  }
340 
347  {
348  // the URL is build only when needed
349  if ( empty($this->_server['service_validate_url']) ) {
350  switch ($this->getServerVersion()) {
351  case CAS_VERSION_1_0:
352  $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
353  break;
354  case CAS_VERSION_2_0:
355  $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
356  break;
357  }
358  }
359  return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
360  }
361 
368  {
369  // the URL is build only when needed
370  if ( empty($this->_server['proxy_validate_url']) ) {
371  switch ($this->getServerVersion()) {
372  case CAS_VERSION_1_0:
373  $this->_server['proxy_validate_url'] = '';
374  break;
375  case CAS_VERSION_2_0:
376  $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
377  break;
378  }
379  }
380  return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
381  }
382 
388  function getServerProxyURL()
389  {
390  // the URL is build only when needed
391  if ( empty($this->_server['proxy_url']) ) {
392  switch ($this->getServerVersion()) {
393  case CAS_VERSION_1_0:
394  $this->_server['proxy_url'] = '';
395  break;
396  case CAS_VERSION_2_0:
397  $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
398  break;
399  }
400  }
401  return $this->_server['proxy_url'];
402  }
403 
410  {
411  // the URL is build only when needed
412  if ( empty($this->_server['logout_url']) ) {
413  $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
414  }
415  return $this->_server['logout_url'];
416  }
417 
418  // ########################################################################
419  // CONSTRUCTOR
420  // ########################################################################
435  function CASClient($server_version,
436  $proxy,
437  $server_hostname,
438  $server_port,
439  $server_uri,
440  $start_session = true)
441  {
442  phpCAS::traceBegin();
443 
444  // activate session mechanism if desired
445  if ($start_session) {
446  session_start();
447  }
448 
449  $this->_proxy = $proxy;
450 
451  // check version
452  switch ($server_version) {
453  case CAS_VERSION_1_0:
454  if ( $this->isProxy() )
455  phpCAS::error('CAS proxies are not supported in CAS '
456  .$server_version);
457  break;
458  case CAS_VERSION_2_0:
459  break;
460  default:
461  phpCAS::error('this version of CAS (`'
462  .$server_version
463  .'\') is not supported by phpCAS '
464  .phpCAS::getVersion());
465  }
466  $this->_server['version'] = $server_version;
467 
468  // check hostname
469  if ( empty($server_hostname)
470  || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
471  phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
472  }
473  $this->_server['hostname'] = $server_hostname;
474 
475  // check port
476  if ( $server_port == 0
477  || !is_int($server_port) ) {
478  phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
479  }
480  $this->_server['port'] = $server_port;
481 
482  // check URI
483  if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
484  phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
485  }
486  // add leading and trailing `/' and remove doubles
487  $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
488  $this->_server['uri'] = $server_uri;
489 
490  // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
491  if ( $this->isProxy() ) {
492  $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
493  }
494 
495  if ( $this->isCallbackMode() ) {
496  // callback mode: check that phpCAS is secured
497  if ( $_SERVER['HTTPS'] != 'on' ) {
498  phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
499  }
500  } else {
501  // normal mode: get ticket and remove it from CGI parameters for developpers
502  $ticket = $_GET['ticket'];
503  // at first check for a Service Ticket
504  if( preg_match('/^ST-/',$ticket)) {
505  phpCAS::trace('ST \''.$ticket.'\' found');
506  // ST present
507  $this->setST($ticket);
508  }
509  // in a second time check for a Proxy Ticket (CAS >= 2.0)
510  else if( ($this->getServerVersion()!=CAS_VERSION_1_0) && preg_match('/^PT-/',$ticket) ) {
511  phpCAS::trace('PT \''.$ticket.'\' found');
512  $this->setPT($ticket);
513  }
514  // ill-formed ticket, halt
515  else if ( !empty($ticket) ) {
516  phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
517  }
518  // ticket has been taken into account, unset it to hide it to applications
519  unset($_GET['ticket']);
520  }
521  phpCAS::traceEnd();
522  }
523 
526  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
527  // XX XX
528  // XX AUTHENTICATION XX
529  // XX XX
530  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
531 
544  var $_user = '';
545 
553  function setUser($user)
554  {
555  $this->_user = $user;
556  }
557 
565  function getUser()
566  {
567  if ( empty($this->_user) ) {
568  phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
569  }
570  return $this->_user;
571  }
572 
580  {
581  phpCAS::traceBegin();
582 
583  if ( $this->isAuthenticated() ) {
584  // the user is authenticated, nothing to be done.
585  phpCAS::trace('no need to authenticate');
586  $res = TRUE;
587  } else {
588  // the user is not authenticated, redirect to the CAS server
589  unset($_SESSION['phpCAS']['auth_checked']);
590  $this->redirectToCas(FALSE/* no gateway */);
591  // never reached
592  $res = FALSE;
593  }
594  phpCAS::traceEnd($res);
595  return $res;
596  }
597 
604  {
605  phpCAS::traceBegin();
606 
607  if ( $this->isAuthenticated() ) {
608  phpCAS::trace('user is authenticated');
609  $res = TRUE;
610  } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
611  // the previous request has redirected the client to the CAS server with gateway=true
612  unset($_SESSION['phpCAS']['auth_checked']);
613  $res = FALSE;
614  } else {
615  $_SESSION['phpCAS']['auth_checked'] = true;
616  $this->redirectToCas(TRUE/* gateway */);
617  // never reached
618  $res = FALSE;
619  }
620  phpCAS::traceEnd($res);
621  return $res;
622  }
623 
632  function isAuthenticated()
633  {
634  phpCAS::traceBegin();
635  $res = FALSE;
636  $validate_url = '';
637 
638  if ( $this->wasPreviouslyAuthenticated() ) {
639  // the user has already (previously during the session) been
640  // authenticated, nothing to be done.
641  phpCAS::trace('user was already authenticated, no need to look for tickets');
642  $res = TRUE;
643  } elseif ( $this->hasST() ) {
644  // if a Service Ticket was given, validate it
645  phpCAS::trace('ST `'.$this->getST().'\' is present');
646  $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
647  phpCAS::trace('ST `'.$this->getST().'\' was validated');
648  if ( $this->isProxy() ) {
649  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
650  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
651  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
652  }
653  $_SESSION['phpCAS']['user'] = $this->getUser();
654  $res = TRUE;
655  } elseif ( $this->hasPT() ) {
656  // if a Proxy Ticket was given, validate it
657  phpCAS::trace('PT `'.$this->getPT().'\' is present');
658  $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
659  phpCAS::trace('PT `'.$this->getPT().'\' was validated');
660  if ( $this->isProxy() ) {
661  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
662  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
663  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
664  }
665  $_SESSION['phpCAS']['user'] = $this->getUser();
666  $res = TRUE;
667  } else {
668  // no ticket given, not authenticated
669  phpCAS::trace('no ticket found');
670  }
671 
672  phpCAS::traceEnd($res);
673  return $res;
674  }
675 
687  {
688  phpCAS::traceBegin();
689 
690  if ( $this->isCallbackMode() ) {
691  $this->callback();
692  }
693 
694  $auth = FALSE;
695 
696  if ( $this->isProxy() ) {
697  // CAS proxy: username and PGT must be present
698  if ( !empty($_SESSION['phpCAS']['user']) && !empty($_SESSION['phpCAS']['pgt']) ) {
699  // authentication already done
700  $this->setUser($_SESSION['phpCAS']['user']);
701  $this->setPGT($_SESSION['phpCAS']['pgt']);
702  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
703  $auth = TRUE;
704  } elseif ( !empty($_SESSION['phpCAS']['user']) && empty($_SESSION['phpCAS']['pgt']) ) {
705  // these two variables should be empty or not empty at the same time
706  phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
707  // unset all tickets to enforce authentication
708  unset($_SESSION['phpCAS']);
709  $this->setST('');
710  $this->setPT('');
711  } elseif ( empty($_SESSION['phpCAS']['user']) && !empty($_SESSION['phpCAS']['pgt']) ) {
712  // these two variables should be empty or not empty at the same time
713  phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
714  // unset all tickets to enforce authentication
715  unset($_SESSION['phpCAS']);
716  $this->setST('');
717  $this->setPT('');
718  } else {
719  phpCAS::trace('neither user not PGT found');
720  }
721  } else {
722  // `simple' CAS client (not a proxy): username must be present
723  if ( !empty($_SESSION['phpCAS']['user']) ) {
724  // authentication already done
725  $this->setUser($_SESSION['phpCAS']['user']);
726  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
727  $auth = TRUE;
728  } else {
729  phpCAS::trace('no user found');
730  }
731  }
732 
733  phpCAS::traceEnd($auth);
734  return $auth;
735  }
736 
743  function redirectToCas($gateway)
744  {
745  phpCAS::traceBegin();
746  $cas_url = $this->getServerLoginURL($gateway);
747  header('Location: '.$cas_url);
749  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
750  $this->printHTMLFooter();
751  phpCAS::traceExit();
752  exit();
753  }
754 
760  function logout($url = "")
761  {
762  phpCAS::traceBegin();
763  $cas_url = $this->getServerLogoutURL();
764  // v0.4.14 sebastien.gougeon at univ-rennes1.fr
765  // header('Location: '.$cas_url);
766  if ( $url != "" ) {
767  $url = '?service=' . $url;
768  }
769  header('Location: '.$cas_url . $url);
770  session_unset();
771  session_destroy();
772  $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
773  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
774  $this->printHTMLFooter();
775  phpCAS::traceExit();
776  exit();
777  }
778 
781  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
782  // XX XX
783  // XX BASIC CLIENT FEATURES (CAS 1.0) XX
784  // XX XX
785  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
786 
787  // ########################################################################
788  // ST
789  // ########################################################################
803  var $_st = '';
804 
810  function getST()
811  { return $this->_st; }
812 
818  function setST($st)
819  { $this->_st = $st; }
820 
826  function hasST()
827  { return !empty($this->_st); }
828 
831  // ########################################################################
832  // ST VALIDATION
833  // ########################################################################
852  function validateST($validate_url,&$text_response,&$tree_response)
853  {
854  phpCAS::traceBegin();
855  // build the URL to validate the ticket
856  $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
857  if ( $this->isProxy() ) {
858  // pass the callback url for CAS proxies
859  $validate_url .= '&pgtUrl='.$this->getCallbackURL();
860  }
861 
862  // open and read the URL
863  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
864  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
865  $this->authError('ST not validated',
866  $validate_url,
867  TRUE/*$no_response*/);
868  }
869 
870  // analyze the result depending on the version
871  switch ($this->getServerVersion()) {
872  case CAS_VERSION_1_0:
873  if (preg_match('/^no\n/',$text_response)) {
874  phpCAS::trace('ST has not been validated');
875  $this->authError('ST not validated',
876  $validate_url,
877  FALSE/*$no_response*/,
878  FALSE/*$bad_response*/,
879  $text_response);
880  }
881  if (!preg_match('/^yes\n/',$text_response)) {
882  phpCAS::trace('ill-formed response');
883  $this->authError('ST not validated',
884  $validate_url,
885  FALSE/*$no_response*/,
886  TRUE/*$bad_response*/,
887  $text_response);
888  }
889  // ST has been validated, extract the user name
890  $arr = preg_split('/\n/',$text_response);
891  $this->setUser(trim($arr[1]));
892  break;
893  case CAS_VERSION_2_0:
894  // read the response of the CAS server into a DOM object
895  if ( !($dom = domxml_open_mem($text_response))) {
896  phpCAS::trace('domxml_open_mem() failed');
897  $this->authError('ST not validated',
898  $validate_url,
899  FALSE/*$no_response*/,
900  TRUE/*$bad_response*/,
901  $text_response);
902  }
903  // read the root node of the XML tree
904  if ( !($tree_response = $dom->document_element()) ) {
905  phpCAS::trace('document_element() failed');
906  $this->authError('ST not validated',
907  $validate_url,
908  FALSE/*$no_response*/,
909  TRUE/*$bad_response*/,
910  $text_response);
911  }
912  // insure that tag name is 'serviceResponse'
913  if ( $tree_response->node_name(true) != 'serviceResponse' ) {
914  phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name(true).'\'');
915  $this->authError('ST not validated',
916  $validate_url,
917  FALSE/*$no_response*/,
918  TRUE/*$bad_response*/,
919  $text_response);
920  }
921  if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
922  // authentication succeded, extract the user name
923  if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
924  phpCAS::trace('<authenticationSuccess> found, but no <user>');
925  $this->authError('ST not validated',
926  $validate_url,
927  FALSE/*$no_response*/,
928  TRUE/*$bad_response*/,
929  $text_response);
930  }
931  $user = trim($user_elements[0]->get_content());
932  phpCAS::trace('user = `'.$user);
933  $this->setUser($user);
934 
935  } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
936  phpCAS::trace('<authenticationFailure> found');
937  // authentication failed, extract the error code and message
938  $this->authError('ST not validated',
939  $validate_url,
940  FALSE/*$no_response*/,
941  FALSE/*$bad_response*/,
942  $text_response,
943  $failure_elements[0]->get_attribute('code')/*$err_code*/,
944  trim($failure_elements[0]->get_content())/*$err_msg*/);
945  } else {
946  phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
947  $this->authError('ST not validated',
948  $validate_url,
949  FALSE/*$no_response*/,
950  TRUE/*$bad_response*/,
951  $text_response);
952  }
953  break;
954  }
955 
956  // at this step, ST has been validated and $this->_user has been set,
957  phpCAS::traceEnd(TRUE);
958  return TRUE;
959  }
960 
963  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
964  // XX XX
965  // XX PROXY FEATURES (CAS 2.0) XX
966  // XX XX
967  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
968 
969  // ########################################################################
970  // PROXYING
971  // ########################################################################
983  var $_proxy;
984 
992  function isProxy()
993  {
994  return $this->_proxy;
995  }
996 
998  // ########################################################################
999  // PGT
1000  // ########################################################################
1013  var $_pgt = '';
1014 
1020  function getPGT()
1021  { return $this->_pgt; }
1022 
1028  function setPGT($pgt)
1029  { $this->_pgt = $pgt; }
1030 
1036  function hasPGT()
1037  { return !empty($this->_pgt); }
1038 
1041  // ########################################################################
1042  // CALLBACK MODE
1043  // ########################################################################
1061  var $_callback_mode = FALSE;
1062 
1070  function setCallbackMode($callback_mode)
1071  {
1072  $this->_callback_mode = $callback_mode;
1073  }
1074 
1083  function isCallbackMode()
1084  {
1085  return $this->_callback_mode;
1086  }
1087 
1096  var $_callback_url = '';
1097 
1107  function getCallbackURL()
1108  {
1109  // the URL is built when needed only
1110  if ( empty($this->_callback_url) ) {
1111  $final_uri = '';
1112  // remove the ticket if present in the URL
1113  $final_uri = 'https://';
1114 
1115  /* replaced by Julien Marchal - v0.4.6
1116  * $this->uri .= $_SERVER['SERVER_NAME'];
1117  */
1118  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1119  /* replaced by teedog - v0.4.12
1120  * $final_uri .= $_SERVER['SERVER_NAME'];
1121  */
1122  if (empty($_SERVER['SERVER_NAME'])) {
1123  $final_uri .= $_SERVER['HTTP_HOST'];
1124  } else {
1125  $final_uri .= $_SERVER['SERVER_NAME'];
1126  }
1127  } else {
1128  $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1129  }
1130  if ( ($_SERVER['HTTPS']=='on' && $_SERVER['SERVER_PORT']!=443)
1131  || ($_SERVER['HTTPS']!='on' && $_SERVER['SERVER_PORT']!=80) ) {
1132  $final_uri .= ':';
1133  $final_uri .= $_SERVER['SERVER_PORT'];
1134  }
1135  $request_uri = $_SERVER['REQUEST_URI'];
1136  $request_uri = preg_replace('/\?.*$/','',$request_uri);
1137  $final_uri .= $request_uri;
1138  $this->setCallbackURL($final_uri);
1139  }
1140  return $this->_callback_url;
1141  }
1142 
1150  function setCallbackURL($url)
1151  {
1152  return $this->_callback_url = $url;
1153  }
1154 
1161  function callback()
1162  {
1163  phpCAS::traceBegin();
1164  $this->printHTMLHeader('phpCAS callback');
1165  $pgt_iou = $_GET['pgtIou'];
1166  $pgt = $_GET['pgtId'];
1167  phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
1168  echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
1169  $this->storePGT($pgt,$pgt_iou);
1170  $this->printHTMLFooter();
1171  phpCAS::traceExit();
1172  }
1173 
1176  // ########################################################################
1177  // PGT STORAGE
1178  // ########################################################################
1192  var $_pgt_storage = null;
1193 
1200  function initPGTStorage()
1201  {
1202  // if no SetPGTStorageXxx() has been used, default to file
1203  if ( !is_object($this->_pgt_storage) ) {
1204  $this->setPGTStorageFile();
1205  }
1206 
1207  // initializes the storage
1208  $this->_pgt_storage->init();
1209  }
1210 
1219  function storePGT($pgt,$pgt_iou)
1220  {
1221  // ensure that storage is initialized
1222  $this->initPGTStorage();
1223  // writes the PGT
1224  $this->_pgt_storage->write($pgt,$pgt_iou);
1225  }
1226 
1236  function loadPGT($pgt_iou)
1237  {
1238  // ensure that storage is initialized
1239  $this->initPGTStorage();
1240  // read the PGT
1241  return $this->_pgt_storage->read($pgt_iou);
1242  }
1243 
1253  function setPGTStorageFile($format='',
1254  $path='')
1255  {
1256  // check that the storage has not already been set
1257  if ( is_object($this->_pgt_storage) ) {
1258  phpCAS::error('PGT storage already defined');
1259  }
1260 
1261  // create the storage object
1262  $this->_pgt_storage = &new PGTStorageFile($this,$format,$path);
1263  }
1264 
1282  function setPGTStorageDB($user,
1283  $password,
1284  $database_type,
1285  $hostname,
1286  $port,
1287  $database,
1288  $table)
1289  {
1290  // check that the storage has not already been set
1291  if ( is_object($this->_pgt_storage) ) {
1292  phpCAS::error('PGT storage already defined');
1293  }
1294 
1295  // warn the user that he should use file storage...
1296  trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING);
1297 
1298  // create the storage object
1299  $this->_pgt_storage = & new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table);
1300  }
1301 
1302  // ########################################################################
1303  // PGT VALIDATION
1304  // ########################################################################
1318  function validatePGT(&$validate_url,$text_response,$tree_response)
1319  {
1320  phpCAS::traceBegin();
1321  if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
1322  phpCAS::trace('<proxyGrantingTicket> not found');
1323  // authentication succeded, but no PGT Iou was transmitted
1324  $this->authError('Ticket validated but no PGT Iou transmitted',
1325  $validate_url,
1326  FALSE/*$no_response*/,
1327  FALSE/*$bad_response*/,
1328  $text_response);
1329  } else {
1330  // PGT Iou transmitted, extract it
1331  $pgt_iou = trim($arr[0]->get_content());
1332  $pgt = $this->loadPGT($pgt_iou);
1333  if ( $pgt == FALSE ) {
1334  phpCAS::trace('could not load PGT');
1335  $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
1336  $validate_url,
1337  FALSE/*$no_response*/,
1338  FALSE/*$bad_response*/,
1339  $text_response);
1340  }
1341  $this->setPGT($pgt);
1342  }
1343  phpCAS::traceEnd(TRUE);
1344  return TRUE;
1345  }
1346 
1347  // ########################################################################
1348  // PGT VALIDATION
1349  // ########################################################################
1350 
1362  function retrievePT($target_service,&$err_code,&$err_msg)
1363  {
1364  phpCAS::traceBegin();
1365 
1366  // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
1367  // set to false and $err_msg to an error message. At the end, if $pt is FALSE
1368  // and $error_msg is still empty, it is set to 'invalid response' (the most
1369  // commonly encountered error).
1370  $err_msg = '';
1371 
1372  // build the URL to retrieve the PT
1373  $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
1374 
1375  // open and read the URL
1376  if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) {
1377  phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
1378  $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
1379  $err_msg = 'could not retrieve PT (no response from the CAS server)';
1380  phpCAS::traceEnd(FALSE);
1381  return FALSE;
1382  }
1383 
1384  $bad_response = FALSE;
1385 
1386  if ( !$bad_response ) {
1387  // read the response of the CAS server into a DOM object
1388  if ( !($dom = @domxml_open_mem($cas_response))) {
1389  phpCAS::trace('domxml_open_mem() failed');
1390  // read failed
1391  $bad_response = TRUE;
1392  }
1393  }
1394 
1395  if ( !$bad_response ) {
1396  // read the root node of the XML tree
1397  if ( !($root = $dom->document_element()) ) {
1398  phpCAS::trace('document_element() failed');
1399  // read failed
1400  $bad_response = TRUE;
1401  }
1402  }
1403 
1404  if ( !$bad_response ) {
1405  // insure that tag name is 'serviceResponse'
1406  if ( $root->node_name(true) != 'serviceResponse' ) {
1407  phpCAS::trace('node_name() failed');
1408  // bad root node
1409  $bad_response = TRUE;
1410  }
1411  }
1412 
1413  if ( !$bad_response ) {
1414  // look for a proxySuccess tag
1415  if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
1416  // authentication succeded, look for a proxyTicket tag
1417  if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
1418  $err_code = PHPCAS_SERVICE_OK;
1419  $err_msg = '';
1420  $pt = trim($arr[0]->get_content());
1421  phpCAS::traceEnd($pt);
1422  return $pt;
1423  } else {
1424  phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
1425  }
1426  }
1427  // look for a proxyFailure tag
1428  else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
1429  // authentication failed, extract the error
1430  $err_code = PHPCAS_SERVICE_PT_FAILURE;
1431  $err_msg = 'PT retrieving failed (code=`'
1432  .$arr[0]->get_attribute('code')
1433  .'\', message=`'
1434  .trim($arr[0]->get_content())
1435  .'\')';
1436  phpCAS::traceEnd(FALSE);
1437  return FALSE;
1438  } else {
1439  phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
1440  }
1441  }
1442 
1443  // at this step, we are sure that the response of the CAS server was ill-formed
1444  $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
1445  $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
1446 
1447  phpCAS::traceEnd(FALSE);
1448  return FALSE;
1449  }
1450 
1451  // ########################################################################
1452  // ACCESS TO EXTERNAL SERVICES
1453  // ########################################################################
1454 
1470  function readURL($url,$cookies,&$headers,&$body,&$err_msg)
1471  {
1472  phpCAS::traceBegin();
1473  $headers = '';
1474  $body = '';
1475  $err_msg = '';
1476 
1477  $res = TRUE;
1478 
1479  // initialize the CURL session
1480  $ch = curl_init($url);
1481 
1482  // verify the the server's certificate corresponds to its name
1483  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
1484  // but do not verify the certificate itself
1485  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
1486 
1487  // return the CURL output into a variable
1488  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1489  // include the HTTP header with the body
1490  curl_setopt($ch, CURLOPT_HEADER, 1);
1491  // add cookies headers
1492  if ( is_array($cookies) ) {
1493  curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
1494  }
1495  // perform the query
1496  $buf = curl_exec ($ch);
1497  if ( $buf === FALSE ) {
1498  phpCAS::trace('cur_exec() failed');
1499  $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
1500  // close the CURL session
1501  curl_close ($ch);
1502  $res = FALSE;
1503  } else {
1504  // close the CURL session
1505  curl_close ($ch);
1506 
1507  // find the end of the headers
1508  // note: strpos($str,"\n\r\n\r") does not work (?)
1509  $pos = FALSE;
1510  for ($i=0; $i<strlen($buf); $i++) {
1511  if ( $buf[$i] == chr(13) )
1512  if ( $buf[$i+1] == chr(10) )
1513  if ( $buf[$i+2] == chr(13) )
1514  if ( $buf[$i+3] == chr(10) ) {
1515  // header found
1516  $pos = $i;
1517  break;
1518  }
1519  }
1520 
1521  if ( $pos === FALSE ) {
1522  // end of header not found
1523  $err_msg = 'no header found';
1524  phpCAS::trace($err_msg);
1525  $res = FALSE;
1526  } else {
1527  // extract headers into an array
1528  $headers = preg_split ("/[\n\r]+/",substr($buf,0,$pos));
1529  // extract body into a string
1530  $body = substr($buf,$pos+4);
1531  }
1532  }
1533 
1534  phpCAS::traceEnd($res);
1535  return $res;
1536  }
1537 
1553  function serviceWeb($url,&$err_code,&$output)
1554  {
1555  phpCAS::traceBegin();
1556  // at first retrieve a PT
1557  $pt = $this->retrievePT($url,$err_code,$output);
1558 
1559  $res = TRUE;
1560 
1561  // test if PT was retrieved correctly
1562  if ( !$pt ) {
1563  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
1564  phpCAS::trace('PT was not retrieved correctly');
1565  $res = FALSE;
1566  } else {
1567  // add cookies if necessary
1568  if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
1569  foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
1570  $cookies[] = $name.'='.$val;
1571  }
1572  }
1573 
1574  // build the URL including the PT
1575  if ( strstr($url,'?') === FALSE ) {
1576  $service_url = $url.'?ticket='.$pt;
1577  } else {
1578  $service_url = $url.'&ticket='.$pt;
1579  }
1580 
1581  phpCAS::trace('reading URL`'.$service_url.'\'');
1582  if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) {
1583  phpCAS::trace('could not read URL`'.$service_url.'\'');
1584  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
1585  // give an error message
1586  $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
1587  $service_url,
1588  $err_msg);
1589  $res = FALSE;
1590  } else {
1591  // URL has been fetched, extract the cookies
1592  phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:');
1593  foreach ( $headers as $header ) {
1594  // test if the header is a cookie
1595  if ( preg_match('/^Set-Cookie:/',$header) ) {
1596  // the header is a cookie, remove the beginning
1597  $header_val = preg_replace('/^Set-Cookie: */','',$header);
1598  // extract interesting information
1599  $name_val = strtok($header_val,'; ');
1600  // extract the name and the value of the cookie
1601  $cookie_name = strtok($name_val,'=');
1602  $cookie_val = strtok('=');
1603  // store the cookie
1604  $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
1605  phpCAS::trace($cookie_name.' -> '.$cookie_val);
1606  }
1607  }
1608  }
1609  }
1610 
1611  phpCAS::traceEnd($res);
1612  return $res;
1613  }
1614 
1633  function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt)
1634  {
1635  phpCAS::traceBegin();
1636  // at first retrieve a PT
1637  $pt = $this->retrievePT($target_service,$err_code,$output);
1638 
1639  $stream = FALSE;
1640 
1641  // test if PT was retrieved correctly
1642  if ( !$pt ) {
1643  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
1644  phpCAS::trace('PT was not retrieved correctly');
1645  } else {
1646  phpCAS::trace('opening IMAP URL `'.$url.'\'...');
1647  $stream = @imap_open($url,$this->getUser(),$pt,$flags);
1648  if ( !$stream ) {
1649  phpCAS::trace('could not open URL');
1650  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
1651  // give an error message
1652  $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
1653  $service_url,
1654  var_export(imap_errors(),TRUE));
1655  $pt = FALSE;
1656  $stream = FALSE;
1657  } else {
1658  phpCAS::trace('ok');
1659  }
1660  }
1661 
1662  phpCAS::traceEnd($stream);
1663  return $stream;
1664  }
1665 
1668  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1669  // XX XX
1670  // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
1671  // XX XX
1672  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1673 
1674  // ########################################################################
1675  // PT
1676  // ########################################################################
1690  var $_pt = '';
1691 
1697  function getPT()
1698  { return $this->_pt; }
1699 
1705  function setPT($pt)
1706  { $this->_pt = $pt; }
1707 
1713  function hasPT()
1714  { return !empty($this->_pt); }
1715 
1717  // ########################################################################
1718  // PT VALIDATION
1719  // ########################################################################
1732  function validatePT(&$validate_url,&$text_response,&$tree_response)
1733  {
1734  phpCAS::traceBegin();
1735  // build the URL to validate the ticket
1736  $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
1737 
1738  if ( $this->isProxy() ) {
1739  // pass the callback url for CAS proxies
1740  $validate_url .= '&pgtUrl='.$this->getCallbackURL();
1741  }
1742 
1743  // open and read the URL
1744  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1745  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1746  $this->authError('PT not validated',
1747  $validate_url,
1748  TRUE/*$no_response*/);
1749  }
1750 
1751  // read the response of the CAS server into a DOM object
1752  if ( !($dom = domxml_open_mem($text_response))) {
1753  // read failed
1754  $this->authError('PT not validated',
1755  $alidate_url,
1756  FALSE/*$no_response*/,
1757  TRUE/*$bad_response*/,
1758  $text_response);
1759  }
1760  // read the root node of the XML tree
1761  if ( !($tree_response = $dom->document_element()) ) {
1762  // read failed
1763  $this->authError('PT not validated',
1764  $validate_url,
1765  FALSE/*$no_response*/,
1766  TRUE/*$bad_response*/,
1767  $text_response);
1768  }
1769  // insure that tag name is 'serviceResponse'
1770  if ( $tree_response->node_name(true) != 'serviceResponse' ) {
1771  // bad root node
1772  $this->authError('PT not validated',
1773  $validate_url,
1774  FALSE/*$no_response*/,
1775  TRUE/*$bad_response*/,
1776  $text_response);
1777  }
1778  if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
1779  // authentication succeded, extract the user name
1780  if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
1781  // no user specified => error
1782  $this->authError('PT not validated',
1783  $validate_url,
1784  FALSE/*$no_response*/,
1785  TRUE/*$bad_response*/,
1786  $text_response);
1787  }
1788  $this->setUser(trim($arr[0]->get_content()));
1789 
1790  } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
1791  // authentication succeded, extract the error code and message
1792  $this->authError('PT not validated',
1793  $validate_url,
1794  FALSE/*$no_response*/,
1795  FALSE/*$bad_response*/,
1796  $text_response,
1797  $arr[0]->get_attribute('code')/*$err_code*/,
1798  trim($arr[0]->get_content())/*$err_msg*/);
1799  } else {
1800  $this->authError('PT not validated',
1801  $validate_url,
1802  FALSE/*$no_response*/,
1803  TRUE/*$bad_response*/,
1804  $text_response);
1805  }
1806 
1807  // at this step, PT has been validated and $this->_user has been set,
1808 
1809  phpCAS::traceEnd(TRUE);
1810  return TRUE;
1811  }
1812 
1815  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1816  // XX XX
1817  // XX MISC XX
1818  // XX XX
1819  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1820 
1826  // ########################################################################
1827  // URL
1828  // ########################################################################
1836  var $_url = '';
1837 
1846  function getURL()
1847  {
1848  phpCAS::traceBegin();
1849  // the URL is built when needed only
1850  if ( empty($this->_url) ) {
1851  $final_uri = '';
1852  // remove the ticket if present in the URL
1853  $final_uri = ($_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
1854  $final_uri .= '://';
1855  /* replaced by Julien Marchal - v0.4.6
1856  * $this->_url .= $_SERVER['SERVER_NAME'];
1857  */
1858  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1859  /* replaced by teedog - v0.4.12
1860  * $this->_url .= $_SERVER['SERVER_NAME'];
1861  */
1862  if (empty($_SERVER['SERVER_NAME'])) {
1863  $final_uri .= $_SERVER['HTTP_HOST'];
1864  } else {
1865  $final_uri .= $_SERVER['SERVER_NAME'];
1866  }
1867  } else {
1868  $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1869  }
1870  if ( ($_SERVER['HTTPS']=='on' && $_SERVER['SERVER_PORT']!=443)
1871  || ($_SERVER['HTTPS']!='on' && $_SERVER['SERVER_PORT']!=80) ) {
1872  $final_uri .= ':';
1873  $final_uri .= $_SERVER['SERVER_PORT'];
1874  }
1875 
1876  $final_uri .= strtok($_SERVER['REQUEST_URI'],"?");
1877  $cgi_params = '?'.strtok("?");
1878  // remove the ticket if present in the CGI parameters
1879  $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params);
1880  $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params);
1881  $cgi_params = preg_replace('/\?$/','',$cgi_params);
1882  $final_uri .= $cgi_params;
1883  $this->setURL($final_uri);
1884  }
1885  phpCAS::traceEnd($this->_url);
1886  return $this->_url;
1887  }
1888 
1896  function setURL($url)
1897  {
1898  $this->_url = $url;
1899  }
1900 
1901  // ########################################################################
1902  // AUTHENTICATION ERROR HANDLING
1903  // ########################################################################
1919  function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
1920  {
1921  phpCAS::traceBegin();
1922 
1923  $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
1924  printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']);
1925  phpCAS::trace('CAS URL: '.$cas_url);
1926  phpCAS::trace('Authentication failure: '.$failure);
1927  if ( $no_response ) {
1928  phpCAS::trace('Reason: no response from the CAS server');
1929  } else {
1930  if ( $bad_response ) {
1931  phpCAS::trace('Reason: bad response from the CAS server');
1932  } else {
1933  switch ($this->getServerVersion()) {
1934  case CAS_VERSION_1_0:
1935  phpCAS::trace('Reason: CAS error');
1936  break;
1937  case CAS_VERSION_2_0:
1938  if ( empty($err_code) )
1939  phpCAS::trace('Reason: no CAS error');
1940  else
1941  phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
1942  break;
1943  }
1944  }
1945  phpCAS::trace('CAS response: '.$cas_response);
1946  }
1947  $this->printHTMLFooter();
1948  phpCAS::traceExit();
1949  exit();
1950  }
1951 
1953 }
1954 
1955 ?>