ILIAS  Release_4_4_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
client4.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 
14 // handle node name
15 function hnodename($name)
16 {
17  if ($i = is_int(strpos($name, ":")))
18  {
19  return substr($name, $i);
20  }
21  else
22  {
23  return $name;
24  }
25 }
26 
35 class CASClient
36 {
37 
38  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
39  // XX XX
40  // XX CONFIGURATION XX
41  // XX XX
42  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
43 
44  // ########################################################################
45  // HTML OUTPUT
46  // ########################################################################
65  function HTMLFilterOutput($str)
66  {
67  $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str);
68  $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str);
69  $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str);
70  echo $str;
71  }
72 
81  var $_output_header = '';
82 
92  function printHTMLHeader($title)
93  {
94  $this->HTMLFilterOutput(str_replace('__TITLE__',
95  $title,
96  (empty($this->_output_header)
97  ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
98  : $this->output_header)
99  )
100  );
101  }
102 
111  var $_output_footer = '';
112 
120  function printHTMLFooter()
121  {
122  $this->HTMLFilterOutput(empty($this->_output_footer)
123  ?('<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>')
124  :$this->_output_footer);
125  }
126 
134  function setHTMLHeader($header)
135  {
136  $this->_output_header = $header;
137  }
138 
146  function setHTMLFooter($footer)
147  {
148  $this->_output_footer = $footer;
149  }
150 
152  // ########################################################################
153  // INTERNATIONALIZATION
154  // ########################################################################
169  var $_lang = '';
170 
178  function getLang()
179  {
180  if ( empty($this->_lang) )
182  return $this->_lang;
183  }
184 
194  var $_strings;
195 
205  function getString($str)
206  {
207  // call CASclient::getLang() to be sure the language is initialized
208  $this->getLang();
209 
210  if ( !isset($this->_strings[$str]) ) {
211  trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR);
212  }
213  return $this->_strings[$str];
214  }
215 
225  function setLang($lang)
226  {
227  // include the corresponding language file
228  include_once(dirname(__FILE__).'/languages/'.$lang.'.php');
229 
230  if ( !is_array($this->_strings) ) {
231  trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR);
232  }
233  $this->_lang = $lang;
234  }
235 
237  // ########################################################################
238  // CAS SERVER CONFIG
239  // ########################################################################
269  var $_server = array(
270  'version' => -1,
271  'hostname' => 'none',
272  'port' => -1,
273  'uri' => 'none'
274  );
275 
281  function getServerVersion()
282  {
283  return $this->_server['version'];
284  }
285 
291  function getServerHostname()
292  { return $this->_server['hostname']; }
293 
299  function getServerPort()
300  { return $this->_server['port']; }
301 
307  function getServerURI()
308  { return $this->_server['uri']; }
309 
315  function getServerBaseURL()
316  {
317  // the URL is build only when needed
318  if ( empty($this->_server['base_url']) ) {
319  $this->_server['base_url'] = 'https://'
320  .$this->getServerHostname()
321  .':'
322  .$this->getServerPort()
323  .$this->getServerURI();
324  }
325  return $this->_server['base_url'];
326  }
327 
334  function getServerLoginURL($gateway)
335  {
337  // the URL is build only when needed
338  if ( empty($this->_server['login_url']) ) {
339  $this->_server['login_url'] = $this->getServerBaseURL();
340  $this->_server['login_url'] .= 'login?service=';
341  $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL());
342  if ($gateway) {
343  $this->_server['login_url'] .= '&gateway=true';
344  }
345  }
346  phpCAS::traceEnd($this->_server['login_url']);
347  return $this->_server['login_url'];
348  }
349 
356  {
357  // the URL is build only when needed
358  if ( empty($this->_server['service_validate_url']) ) {
359  switch ($this->getServerVersion()) {
360  case CAS_VERSION_1_0:
361  $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate';
362  break;
363  case CAS_VERSION_2_0:
364  $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate';
365  break;
366  }
367  }
368  return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
369  }
370 
377  {
378  // the URL is build only when needed
379  if ( empty($this->_server['proxy_validate_url']) ) {
380  switch ($this->getServerVersion()) {
381  case CAS_VERSION_1_0:
382  $this->_server['proxy_validate_url'] = '';
383  break;
384  case CAS_VERSION_2_0:
385  $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate';
386  break;
387  }
388  }
389  return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL());
390  }
391 
397  function getServerProxyURL()
398  {
399  // the URL is build only when needed
400  if ( empty($this->_server['proxy_url']) ) {
401  switch ($this->getServerVersion()) {
402  case CAS_VERSION_1_0:
403  $this->_server['proxy_url'] = '';
404  break;
405  case CAS_VERSION_2_0:
406  $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy';
407  break;
408  }
409  }
410  return $this->_server['proxy_url'];
411  }
412 
419  {
420  // the URL is build only when needed
421  if ( empty($this->_server['logout_url']) ) {
422  $this->_server['logout_url'] = $this->getServerBaseURL().'logout';
423  }
424  return $this->_server['logout_url'];
425  }
426 
427  // ########################################################################
428  // CONSTRUCTOR
429  // ########################################################################
444  function CASClient($server_version,
445  $proxy,
446  $server_hostname,
447  $server_port,
448  $server_uri,
449  $start_session = true)
450  {
452 
453  // activate session mechanism if desired
454  if ($start_session) {
455  session_start();
456  }
457 
458  $this->_proxy = $proxy;
459 
460  // check version
461  switch ($server_version) {
462  case CAS_VERSION_1_0:
463  if ( $this->isProxy() )
464  phpCAS::error('CAS proxies are not supported in CAS '
465  .$server_version);
466  break;
467  case CAS_VERSION_2_0:
468  break;
469  default:
470  phpCAS::error('this version of CAS (`'
471  .$server_version
472  .'\') is not supported by phpCAS '
473  .phpCAS::getVersion());
474  }
475  $this->_server['version'] = $server_version;
476 
477  // check hostname
478  if ( empty($server_hostname)
479  || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) {
480  phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
481  }
482  $this->_server['hostname'] = $server_hostname;
483 
484  // check port
485  if ( $server_port == 0
486  || !is_int($server_port) ) {
487  phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
488  }
489  $this->_server['port'] = $server_port;
490 
491  // check URI
492  if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) {
493  phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
494  }
495  // add leading and trailing `/' and remove doubles
496  $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/');
497  $this->_server['uri'] = $server_uri;
498 
499  // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
500  if ( $this->isProxy() ) {
501  $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId']));
502  }
503 
504  if ( $this->isCallbackMode() ) {
505  // callback mode: check that phpCAS is secured
506  if ( $_SERVER['HTTPS'] != 'on' ) {
507  phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server');
508  }
509  } else {
510  // normal mode: get ticket and remove it from CGI parameters for developpers
511  $ticket = $_GET['ticket'];
512  // at first check for a Service Ticket
513  if( preg_match('/^ST-/',$ticket)) {
514  phpCAS::trace('ST \''.$ticket.'\' found');
515  // ST present
516  $this->setST($ticket);
517  }
518  // in a second time check for a Proxy Ticket (CAS >= 2.0)
519  else if( ($this->getServerVersion()!=CAS_VERSION_1_0) && preg_match('/^PT-/',$ticket) ) {
520  phpCAS::trace('PT \''.$ticket.'\' found');
521  $this->setPT($ticket);
522  }
523  // ill-formed ticket, halt
524  else if ( !empty($ticket) ) {
525  phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
526  }
527  // ticket has been taken into account, unset it to hide it to applications
528  unset($_GET['ticket']);
529  }
531  }
532 
535  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
536  // XX XX
537  // XX AUTHENTICATION XX
538  // XX XX
539  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
540 
553  var $_user = '';
554 
562  function setUser($user)
563  {
564  $this->_user = $user;
565  }
566 
574  function getUser()
575  {
576  if ( empty($this->_user) ) {
577  phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()');
578  }
579  return $this->_user;
580  }
581 
589  {
591 
592  if ( $this->isAuthenticated() ) {
593  // the user is authenticated, nothing to be done.
594  phpCAS::trace('no need to authenticate');
595  $res = TRUE;
596  } else {
597  // the user is not authenticated, redirect to the CAS server
598  unset($_SESSION['phpCAS']['auth_checked']);
599  $this->redirectToCas(FALSE/* no gateway */);
600  // never reached
601  $res = FALSE;
602  }
604  return $res;
605  }
606 
613  {
615 
616  if ( $this->isAuthenticated() ) {
617  phpCAS::trace('user is authenticated');
618  $res = TRUE;
619  } else if (isset($_SESSION['phpCAS']['auth_checked'])) {
620  // the previous request has redirected the client to the CAS server with gateway=true
621  unset($_SESSION['phpCAS']['auth_checked']);
622  $res = FALSE;
623  } else {
624  $_SESSION['phpCAS']['auth_checked'] = true;
625  $this->redirectToCas(TRUE/* gateway */);
626  // never reached
627  $res = FALSE;
628  }
630  return $res;
631  }
632 
641  function isAuthenticated()
642  {
644  $res = FALSE;
645  $validate_url = '';
646 
647  if ( $this->wasPreviouslyAuthenticated() ) {
648  // the user has already (previously during the session) been
649  // authenticated, nothing to be done.
650  phpCAS::trace('user was already authenticated, no need to look for tickets');
651  $res = TRUE;
652  } elseif ( $this->hasST() ) {
653  // if a Service Ticket was given, validate it
654  phpCAS::trace('ST `'.$this->getST().'\' is present');
655  $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts
656  phpCAS::trace('ST `'.$this->getST().'\' was validated');
657  if ( $this->isProxy() ) {
658  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
659  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
660  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
661  }
662  $_SESSION['phpCAS']['user'] = $this->getUser();
663  $res = TRUE;
664  } elseif ( $this->hasPT() ) {
665  // if a Proxy Ticket was given, validate it
666  phpCAS::trace('PT `'.$this->getPT().'\' is present');
667  $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts
668  phpCAS::trace('PT `'.$this->getPT().'\' was validated');
669  if ( $this->isProxy() ) {
670  $this->validatePGT($validate_url,$text_response,$tree_response); // idem
671  phpCAS::trace('PGT `'.$this->getPGT().'\' was validated');
672  $_SESSION['phpCAS']['pgt'] = $this->getPGT();
673  }
674  $_SESSION['phpCAS']['user'] = $this->getUser();
675  $res = TRUE;
676  } else {
677  // no ticket given, not authenticated
678  phpCAS::trace('no ticket found');
679  }
680 
682  return $res;
683  }
684 
696  {
698 
699  if ( $this->isCallbackMode() ) {
700  $this->callback();
701  }
702 
703  $auth = FALSE;
704 
705  if ( $this->isProxy() ) {
706  // CAS proxy: username and PGT must be present
707  if ( !empty($_SESSION['phpCAS']['user']) && !empty($_SESSION['phpCAS']['pgt']) ) {
708  // authentication already done
709  $this->setUser($_SESSION['phpCAS']['user']);
710  $this->setPGT($_SESSION['phpCAS']['pgt']);
711  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
712  $auth = TRUE;
713  } elseif ( !empty($_SESSION['phpCAS']['user']) && empty($_SESSION['phpCAS']['pgt']) ) {
714  // these two variables should be empty or not empty at the same time
715  phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty');
716  // unset all tickets to enforce authentication
717  unset($_SESSION['phpCAS']);
718  $this->setST('');
719  $this->setPT('');
720  } elseif ( empty($_SESSION['phpCAS']['user']) && !empty($_SESSION['phpCAS']['pgt']) ) {
721  // these two variables should be empty or not empty at the same time
722  phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty');
723  // unset all tickets to enforce authentication
724  unset($_SESSION['phpCAS']);
725  $this->setST('');
726  $this->setPT('');
727  } else {
728  phpCAS::trace('neither user not PGT found');
729  }
730  } else {
731  // `simple' CAS client (not a proxy): username must be present
732  if ( !empty($_SESSION['phpCAS']['user']) ) {
733  // authentication already done
734  $this->setUser($_SESSION['phpCAS']['user']);
735  phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\'');
736  $auth = TRUE;
737  } else {
738  phpCAS::trace('no user found');
739  }
740  }
741 
743  return $auth;
744  }
745 
752  function redirectToCas($gateway)
753  {
755  $cas_url = $this->getServerLoginURL($gateway);
756  header('Location: '.$cas_url);
758  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
759  $this->printHTMLFooter();
761  exit();
762  }
763 
769  function logout($url = "")
770  {
772  $cas_url = $this->getServerLogoutURL();
773  // v0.4.14 sebastien.gougeon at univ-rennes1.fr
774  // header('Location: '.$cas_url);
775  if ( $url != "" ) {
776  $url = '?service=' . $url;
777  }
778  header('Location: '.$cas_url . $url);
779  session_unset();
780  session_destroy();
781  $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT));
782  printf('<p>'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'</p>',$cas_url);
783  $this->printHTMLFooter();
785  exit();
786  }
787 
790  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
791  // XX XX
792  // XX BASIC CLIENT FEATURES (CAS 1.0) XX
793  // XX XX
794  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
795 
796  // ########################################################################
797  // ST
798  // ########################################################################
812  var $_st = '';
813 
819  function getST()
820  { return $this->_st; }
821 
827  function setST($st)
828  { $this->_st = $st; }
829 
835  function hasST()
836  { return !empty($this->_st); }
837 
840  // ########################################################################
841  // ST VALIDATION
842  // ########################################################################
861  function validateST($validate_url,&$text_response,&$tree_response)
862  {
864  // build the URL to validate the ticket
865  $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST();
866  if ( $this->isProxy() ) {
867  // pass the callback url for CAS proxies
868  $validate_url .= '&pgtUrl='.$this->getCallbackURL();
869  }
870 
871  // open and read the URL
872  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
873  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
874  $this->authError('ST not validated (1)',
875  $validate_url,
876  TRUE/*$no_response*/);
877  }
878 
879  // analyze the result depending on the version
880  switch ($this->getServerVersion()) {
881  case CAS_VERSION_1_0:
882  if (preg_match('/^no\n/',$text_response)) {
883  phpCAS::trace('ST has not been validated');
884  $this->authError('ST not validated (2)',
885  $validate_url,
886  FALSE/*$no_response*/,
887  FALSE/*$bad_response*/,
888  $text_response);
889  }
890  if (!preg_match('/^yes\n/',$text_response)) {
891  phpCAS::trace('ill-formed response');
892  $this->authError('ST not validated (3)',
893  $validate_url,
894  FALSE/*$no_response*/,
895  TRUE/*$bad_response*/,
896  $text_response);
897  }
898  // ST has been validated, extract the user name
899  $arr = preg_split('/\n/',$text_response);
900  $this->setUser(trim($arr[1]));
901  break;
902  case CAS_VERSION_2_0:
903  // read the response of the CAS server into a DOM object
904  if ( !($dom = domxml_open_mem($text_response))) {
905  phpCAS::trace('domxml_open_mem() failed');
906  $this->authError('ST not validated (4)',
907  $validate_url,
908  FALSE/*$no_response*/,
909  TRUE/*$bad_response*/,
910  $text_response);
911  }
912  // read the root node of the XML tree
913  if ( !($tree_response = $dom->document_element()) ) {
914  phpCAS::trace('document_element() failed');
915  $this->authError('ST not validated (5)',
916  $validate_url,
917  FALSE/*$no_response*/,
918  TRUE/*$bad_response*/,
919  $text_response);
920  }
921  // insure that tag name is 'serviceResponse'
922  if ( hnodename($tree_response->node_name()) != 'serviceResponse' ) {
923  phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.hnodename($tree_response->node_name()).'\'');
924  $this->authError('ST not validated (6)',
925  $validate_url,
926  FALSE/*$no_response*/,
927  TRUE/*$bad_response*/,
928  $text_response);
929  }
930  if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
931  // authentication succeded, extract the user name
932  if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) {
933  phpCAS::trace('<authenticationSuccess> found, but no <user>');
934  $this->authError('ST not validated (7)',
935  $validate_url,
936  FALSE/*$no_response*/,
937  TRUE/*$bad_response*/,
938  $text_response);
939  }
940  $user = trim($user_elements[0]->get_content());
941  phpCAS::trace('user = `'.$user);
942  $this->setUser($user);
943 
944  } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
945  phpCAS::trace('<authenticationFailure> found');
946  // authentication failed, extract the error code and message
947  $this->authError('ST not validated (8)',
948  $validate_url,
949  FALSE/*$no_response*/,
950  FALSE/*$bad_response*/,
951  $text_response,
952  $failure_elements[0]->get_attribute('code')/*$err_code*/,
953  trim($failure_elements[0]->get_content())/*$err_msg*/);
954  } else {
955  phpCAS::trace('neither <authenticationSuccess> nor <authenticationFailure> found');
956  $this->authError('ST not validated (9)',
957  $validate_url,
958  FALSE/*$no_response*/,
959  TRUE/*$bad_response*/,
960  $text_response);
961  }
962  break;
963  }
964 
965  // at this step, ST has been validated and $this->_user has been set,
966  phpCAS::traceEnd(TRUE);
967  return TRUE;
968  }
969 
972  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
973  // XX XX
974  // XX PROXY FEATURES (CAS 2.0) XX
975  // XX XX
976  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
977 
978  // ########################################################################
979  // PROXYING
980  // ########################################################################
992  var $_proxy;
993 
1001  function isProxy()
1002  {
1003  return $this->_proxy;
1004  }
1005 
1007  // ########################################################################
1008  // PGT
1009  // ########################################################################
1022  var $_pgt = '';
1023 
1029  function getPGT()
1030  { return $this->_pgt; }
1031 
1037  function setPGT($pgt)
1038  { $this->_pgt = $pgt; }
1039 
1045  function hasPGT()
1046  { return !empty($this->_pgt); }
1047 
1050  // ########################################################################
1051  // CALLBACK MODE
1052  // ########################################################################
1070  var $_callback_mode = FALSE;
1071 
1079  function setCallbackMode($callback_mode)
1080  {
1081  $this->_callback_mode = $callback_mode;
1082  }
1083 
1092  function isCallbackMode()
1093  {
1094  return $this->_callback_mode;
1095  }
1096 
1105  var $_callback_url = '';
1106 
1116  function getCallbackURL()
1117  {
1118  // the URL is built when needed only
1119  if ( empty($this->_callback_url) ) {
1120  $final_uri = '';
1121  // remove the ticket if present in the URL
1122  $final_uri = 'https://';
1123  /* replaced by Julien Marchal - v0.4.6
1124  * $this->uri .= $_SERVER['SERVER_NAME'];
1125  */
1126  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1127  /* replaced by teedog - v0.4.12
1128  * $final_uri .= $_SERVER['SERVER_NAME'];
1129  */
1130  if (empty($_SERVER['SERVER_NAME'])) {
1131  $final_uri .= $_SERVER['HTTP_HOST'];
1132  } else {
1133  $final_uri .= $_SERVER['SERVER_NAME'];
1134  }
1135  } else {
1136  $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1137  }
1138  if ( ($_SERVER['HTTPS']=='on' && $_SERVER['SERVER_PORT']!=443)
1139  || ($_SERVER['HTTPS']!='on' && $_SERVER['SERVER_PORT']!=80) ) {
1140  $final_uri .= ':';
1141  $final_uri .= $_SERVER['SERVER_PORT'];
1142  }
1143  $request_uri = $_SERVER['REQUEST_URI'];
1144  $request_uri = preg_replace('/\?.*$/','',$request_uri);
1145  $final_uri .= $request_uri;
1146  $this->setCallbackURL($final_uri);
1147  }
1148  return $this->_callback_url;
1149  }
1150 
1158  function setCallbackURL($url)
1159  {
1160  return $this->_callback_url = $url;
1161  }
1162 
1169  function callback()
1170  {
1172  $this->printHTMLHeader('phpCAS callback');
1173  $pgt_iou = $_GET['pgtIou'];
1174  $pgt = $_GET['pgtId'];
1175  phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')');
1176  echo '<p>Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').</p>';
1177  $this->storePGT($pgt,$pgt_iou);
1178  $this->printHTMLFooter();
1180  }
1181 
1184  // ########################################################################
1185  // PGT STORAGE
1186  // ########################################################################
1200  var $_pgt_storage = null;
1201 
1208  function initPGTStorage()
1209  {
1210  // if no SetPGTStorageXxx() has been used, default to file
1211  if ( !is_object($this->_pgt_storage) ) {
1212  $this->setPGTStorageFile();
1213  }
1214 
1215  // initializes the storage
1216  $this->_pgt_storage->init();
1217  }
1218 
1227  function storePGT($pgt,$pgt_iou)
1228  {
1229  // ensure that storage is initialized
1230  $this->initPGTStorage();
1231  // writes the PGT
1232  $this->_pgt_storage->write($pgt,$pgt_iou);
1233  }
1234 
1244  function loadPGT($pgt_iou)
1245  {
1246  // ensure that storage is initialized
1247  $this->initPGTStorage();
1248  // read the PGT
1249  return $this->_pgt_storage->read($pgt_iou);
1250  }
1251 
1261  function setPGTStorageFile($format='',
1262  $path='')
1263  {
1264  // check that the storage has not already been set
1265  if ( is_object($this->_pgt_storage) ) {
1266  phpCAS::error('PGT storage already defined');
1267  }
1268 
1269  // create the storage object
1270  $this->_pgt_storage = &new PGTStorageFile($this,$format,$path);
1271  }
1272 
1290  function setPGTStorageDB($user,
1291  $password,
1292  $database_type,
1293  $hostname,
1294  $port,
1295  $database,
1296  $table)
1297  {
1298  // check that the storage has not already been set
1299  if ( is_object($this->_pgt_storage) ) {
1300  phpCAS::error('PGT storage already defined');
1301  }
1302 
1303  // warn the user that he should use file storage...
1304  trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING);
1305 
1306  // create the storage object
1307  $this->_pgt_storage = & new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table);
1308  }
1309 
1310  // ########################################################################
1311  // PGT VALIDATION
1312  // ########################################################################
1326  function validatePGT(&$validate_url,$text_response,$tree_response)
1327  {
1329  if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) {
1330  phpCAS::trace('<proxyGrantingTicket> not found');
1331  // authentication succeded, but no PGT Iou was transmitted
1332  $this->authError('Ticket validated but no PGT Iou transmitted',
1333  $validate_url,
1334  FALSE/*$no_response*/,
1335  FALSE/*$bad_response*/,
1336  $text_response);
1337  } else {
1338  // PGT Iou transmitted, extract it
1339  $pgt_iou = trim($arr[0]->get_content());
1340  $pgt = $this->loadPGT($pgt_iou);
1341  if ( $pgt == FALSE ) {
1342  phpCAS::trace('could not load PGT');
1343  $this->authError('PGT Iou was transmitted but PGT could not be retrieved',
1344  $validate_url,
1345  FALSE/*$no_response*/,
1346  FALSE/*$bad_response*/,
1347  $text_response);
1348  }
1349  $this->setPGT($pgt);
1350  }
1351  phpCAS::traceEnd(TRUE);
1352  return TRUE;
1353  }
1354 
1355  // ########################################################################
1356  // PGT VALIDATION
1357  // ########################################################################
1358 
1370  function retrievePT($target_service,&$err_code,&$err_msg)
1371  {
1373 
1374  // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is
1375  // set to false and $err_msg to an error message. At the end, if $pt is FALSE
1376  // and $error_msg is still empty, it is set to 'invalid response' (the most
1377  // commonly encountered error).
1378  $err_msg = '';
1379 
1380  // build the URL to retrieve the PT
1381  $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT();
1382 
1383  // open and read the URL
1384  if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) {
1385  phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')');
1386  $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
1387  $err_msg = 'could not retrieve PT (no response from the CAS server)';
1388  phpCAS::traceEnd(FALSE);
1389  return FALSE;
1390  }
1391 
1392  $bad_response = FALSE;
1393 
1394  if ( !$bad_response ) {
1395  // read the response of the CAS server into a DOM object
1396  if ( !($dom = @domxml_open_mem($cas_response))) {
1397  phpCAS::trace('domxml_open_mem() failed');
1398  // read failed
1399  $bad_response = TRUE;
1400  }
1401  }
1402 
1403  if ( !$bad_response ) {
1404  // read the root node of the XML tree
1405  if ( !($root = $dom->document_element()) ) {
1406  phpCAS::trace('document_element() failed');
1407  // read failed
1408  $bad_response = TRUE;
1409  }
1410  }
1411 
1412  if ( !$bad_response ) {
1413  // insure that tag name is 'serviceResponse'
1414  if ( hnodename($root->node_name()) != 'serviceResponse' ) {
1415  phpCAS::trace('node_name() failed');
1416  // bad root node
1417  $bad_response = TRUE;
1418  }
1419  }
1420 
1421  if ( !$bad_response ) {
1422  // look for a proxySuccess tag
1423  if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) {
1424  // authentication succeded, look for a proxyTicket tag
1425  if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) {
1426  $err_code = PHPCAS_SERVICE_OK;
1427  $err_msg = '';
1428  $pt = trim($arr[0]->get_content());
1429  phpCAS::traceEnd($pt);
1430  return $pt;
1431  } else {
1432  phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
1433  }
1434  }
1435  // look for a proxyFailure tag
1436  else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) {
1437  // authentication failed, extract the error
1438  $err_code = PHPCAS_SERVICE_PT_FAILURE;
1439  $err_msg = 'PT retrieving failed (code=`'
1440  .$arr[0]->get_attribute('code')
1441  .'\', message=`'
1442  .trim($arr[0]->get_content())
1443  .'\')';
1444  phpCAS::traceEnd(FALSE);
1445  return FALSE;
1446  } else {
1447  phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
1448  }
1449  }
1450 
1451  // at this step, we are sure that the response of the CAS server was ill-formed
1452  $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
1453  $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
1454 
1455  phpCAS::traceEnd(FALSE);
1456  return FALSE;
1457  }
1458 
1459  // ########################################################################
1460  // ACCESS TO EXTERNAL SERVICES
1461  // ########################################################################
1462 
1478  function readURL($url,$cookies,&$headers,&$body,&$err_msg)
1479  {
1481  $headers = '';
1482  $body = '';
1483  $err_msg = '';
1484 
1485  $res = TRUE;
1486 
1487  // initialize the CURL session
1488  $ch = curl_init($url);
1489 
1490  // verify the the server's certificate corresponds to its name
1491  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
1492  // but do not verify the certificate itself
1493  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
1494 
1495  // return the CURL output into a variable
1496  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1497  // include the HTTP header with the body
1498  curl_setopt($ch, CURLOPT_HEADER, 1);
1499  // add cookies headers
1500  if ( is_array($cookies) ) {
1501  curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies));
1502  }
1503  // perform the query
1504  $buf = curl_exec ($ch);
1505  if ( $buf === FALSE ) {
1506  phpCAS::trace('cur_exec() failed');
1507  $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch);
1508  // close the CURL session
1509  curl_close ($ch);
1510  $res = FALSE;
1511  } else {
1512  // close the CURL session
1513  curl_close ($ch);
1514 
1515  // find the end of the headers
1516  // note: strpos($str,"\n\r\n\r") does not work (?)
1517  $pos = FALSE;
1518  for ($i=0; $i<strlen($buf); $i++) {
1519  if ( $buf[$i] == chr(13) )
1520  if ( $buf[$i+1] == chr(10) )
1521  if ( $buf[$i+2] == chr(13) )
1522  if ( $buf[$i+3] == chr(10) ) {
1523  // header found
1524  $pos = $i;
1525  break;
1526  }
1527  }
1528 
1529  if ( $pos === FALSE ) {
1530  // end of header not found
1531  $err_msg = 'no header found';
1532  phpCAS::trace($err_msg);
1533  $res = FALSE;
1534  } else {
1535  // extract headers into an array
1536  $headers = preg_split ("/[\n\r]+/",substr($buf,0,$pos));
1537  // extract body into a string
1538  $body = substr($buf,$pos+4);
1539  }
1540  }
1541 
1543  return $res;
1544  }
1545 
1561  function serviceWeb($url,&$err_code,&$output)
1562  {
1564  // at first retrieve a PT
1565  $pt = $this->retrievePT($url,$err_code,$output);
1566 
1567  $res = TRUE;
1568 
1569  // test if PT was retrieved correctly
1570  if ( !$pt ) {
1571  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
1572  phpCAS::trace('PT was not retrieved correctly');
1573  $res = FALSE;
1574  } else {
1575  // add cookies if necessary
1576  if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) {
1577  foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) {
1578  $cookies[] = $name.'='.$val;
1579  }
1580  }
1581 
1582  // build the URL including the PT
1583  if ( strstr($url,'?') === FALSE ) {
1584  $service_url = $url.'?ticket='.$pt;
1585  } else {
1586  $service_url = $url.'&ticket='.$pt;
1587  }
1588 
1589  phpCAS::trace('reading URL`'.$service_url.'\'');
1590  if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) {
1591  phpCAS::trace('could not read URL`'.$service_url.'\'');
1592  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
1593  // give an error message
1594  $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
1595  $service_url,
1596  $err_msg);
1597  $res = FALSE;
1598  } else {
1599  // URL has been fetched, extract the cookies
1600  phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:');
1601  foreach ( $headers as $header ) {
1602  // test if the header is a cookie
1603  if ( preg_match('/^Set-Cookie:/',$header) ) {
1604  // the header is a cookie, remove the beginning
1605  $header_val = preg_replace('/^Set-Cookie: */','',$header);
1606  // extract interesting information
1607  $name_val = strtok($header_val,'; ');
1608  // extract the name and the value of the cookie
1609  $cookie_name = strtok($name_val,'=');
1610  $cookie_val = strtok('=');
1611  // store the cookie
1612  $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val;
1613  phpCAS::trace($cookie_name.' -> '.$cookie_val);
1614  }
1615  }
1616  }
1617  }
1618 
1619  phpCAS::traceEnd($res);
1620  return $res;
1621  }
1622 
1641  function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt)
1642  {
1643  phpCAS::traceBegin();
1644  // at first retrieve a PT
1645  $pt = $this->retrievePT($target_service,$err_code,$output);
1646 
1647  $stream = FALSE;
1648 
1649  // test if PT was retrieved correctly
1650  if ( !$pt ) {
1651  // note: $err_code and $err_msg are filled by CASClient::retrievePT()
1652  phpCAS::trace('PT was not retrieved correctly');
1653  } else {
1654  phpCAS::trace('opening IMAP URL `'.$url.'\'...');
1655  $stream = @imap_open($url,$this->getUser(),$pt,$flags);
1656  if ( !$stream ) {
1657  phpCAS::trace('could not open URL');
1658  $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
1659  // give an error message
1660  $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE),
1661  $service_url,
1662  var_export(imap_errors(),TRUE));
1663  $pt = FALSE;
1664  $stream = FALSE;
1665  } else {
1666  phpCAS::trace('ok');
1667  }
1668  }
1669 
1670  phpCAS::traceEnd($stream);
1671  return $stream;
1672  }
1673 
1676  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1677  // XX XX
1678  // XX PROXIED CLIENT FEATURES (CAS 2.0) XX
1679  // XX XX
1680  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1681 
1682  // ########################################################################
1683  // PT
1684  // ########################################################################
1698  var $_pt = '';
1699 
1705  function getPT()
1706  { return $this->_pt; }
1707 
1713  function setPT($pt)
1714  { $this->_pt = $pt; }
1715 
1721  function hasPT()
1722  { return !empty($this->_pt); }
1723 
1725  // ########################################################################
1726  // PT VALIDATION
1727  // ########################################################################
1740  function validatePT(&$validate_url,&$text_response,&$tree_response)
1741  {
1743  // build the URL to validate the ticket
1744  $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT();
1745 
1746  if ( $this->isProxy() ) {
1747  // pass the callback url for CAS proxies
1748  $validate_url .= '&pgtUrl='.$this->getCallbackURL();
1749  }
1750 
1751  // open and read the URL
1752  if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) {
1753  phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
1754  $this->authError('PT not validated',
1755  $validate_url,
1756  TRUE/*$no_response*/);
1757  }
1758 
1759  // read the response of the CAS server into a DOM object
1760  if ( !($dom = domxml_open_mem($text_response))) {
1761  // read failed
1762  $this->authError('PT not validated',
1763  $alidate_url,
1764  FALSE/*$no_response*/,
1765  TRUE/*$bad_response*/,
1766  $text_response);
1767  }
1768  // read the root node of the XML tree
1769  if ( !($tree_response = $dom->document_element()) ) {
1770  // read failed
1771  $this->authError('PT not validated',
1772  $validate_url,
1773  FALSE/*$no_response*/,
1774  TRUE/*$bad_response*/,
1775  $text_response);
1776  }
1777  // insure that tag name is 'serviceResponse'
1778  if ( hnodename($tree_response->node_name()) != 'serviceResponse' ) {
1779  // bad root node
1780  $this->authError('PT not validated',
1781  $validate_url,
1782  FALSE/*$no_response*/,
1783  TRUE/*$bad_response*/,
1784  $text_response);
1785  }
1786  if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) {
1787  // authentication succeded, extract the user name
1788  if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) {
1789  // no user specified => error
1790  $this->authError('PT not validated',
1791  $validate_url,
1792  FALSE/*$no_response*/,
1793  TRUE/*$bad_response*/,
1794  $text_response);
1795  }
1796  $this->setUser(trim($arr[0]->get_content()));
1797 
1798  } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) {
1799  // authentication succeded, extract the error code and message
1800  $this->authError('PT not validated',
1801  $validate_url,
1802  FALSE/*$no_response*/,
1803  FALSE/*$bad_response*/,
1804  $text_response,
1805  $arr[0]->get_attribute('code')/*$err_code*/,
1806  trim($arr[0]->get_content())/*$err_msg*/);
1807  } else {
1808  $this->authError('PT not validated',
1809  $validate_url,
1810  FALSE/*$no_response*/,
1811  TRUE/*$bad_response*/,
1812  $text_response);
1813  }
1814 
1815  // at this step, PT has been validated and $this->_user has been set,
1816 
1817  phpCAS::traceEnd(TRUE);
1818  return TRUE;
1819  }
1820 
1823  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1824  // XX XX
1825  // XX MISC XX
1826  // XX XX
1827  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1828 
1834  // ########################################################################
1835  // URL
1836  // ########################################################################
1844  var $_url = '';
1845 
1854  function getURL()
1855  {
1856  phpCAS::traceBegin();
1857  // the URL is built when needed only
1858  if ( empty($this->_url) ) {
1859  $final_uri = '';
1860  // remove the ticket if present in the URL
1861  $final_uri = ($_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
1862  $final_uri .= '://';
1863  /* replaced by Julien Marchal - v0.4.6
1864  * $this->_url .= $_SERVER['SERVER_NAME'];
1865  */
1866  if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){
1867  /* replaced by teedog - v0.4.12
1868  * $this->_url .= $_SERVER['SERVER_NAME'];
1869  */
1870  if (empty($_SERVER['SERVER_NAME'])) {
1871  $final_uri .= $_SERVER['HTTP_HOST'];
1872  } else {
1873  $final_uri .= $_SERVER['SERVER_NAME'];
1874  }
1875  } else {
1876  $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER'];
1877  }
1878  if ( ($_SERVER['HTTPS']=='on' && $_SERVER['SERVER_PORT']!=443)
1879  || ($_SERVER['HTTPS']!='on' && $_SERVER['SERVER_PORT']!=80) ) {
1880  $final_uri .= ':';
1881  $final_uri .= $_SERVER['SERVER_PORT'];
1882  }
1883 
1884  $final_uri .= strtok($_SERVER['REQUEST_URI'],"?");
1885  $cgi_params = '?'.strtok("?");
1886  // remove the ticket if present in the CGI parameters
1887  $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params);
1888  $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params);
1889  $cgi_params = preg_replace('/\?$/','',$cgi_params);
1890  $final_uri .= $cgi_params;
1891  $this->setURL($final_uri);
1892  }
1893  phpCAS::traceEnd($this->_url);
1894  return $this->_url;
1895  }
1896 
1904  function setURL($url)
1905  {
1906  $this->_url = $url;
1907  }
1908 
1909  // ########################################################################
1910  // AUTHENTICATION ERROR HANDLING
1911  // ########################################################################
1927  function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='')
1928  {
1930 
1931  $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED));
1932  printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']);
1933  phpCAS::trace('CAS URL: '.$cas_url);
1934  phpCAS::trace('Authentication failure: '.$failure);
1935  if ( $no_response ) {
1936  phpCAS::trace('Reason: no response from the CAS server');
1937  } else {
1938  if ( $bad_response ) {
1939  phpCAS::trace('Reason: bad response from the CAS server');
1940  } else {
1941  switch ($this->getServerVersion()) {
1942  case CAS_VERSION_1_0:
1943  phpCAS::trace('Reason: CAS error');
1944  break;
1945  case CAS_VERSION_2_0:
1946  if ( empty($err_code) )
1947  phpCAS::trace('Reason: no CAS error');
1948  else
1949  phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
1950  break;
1951  }
1952  }
1953  phpCAS::trace('CAS response: '.$cas_response);
1954  }
1955  $this->printHTMLFooter();
1957  exit();
1958  }
1959 
1961 }
1962 
1963 ?>