Add openid security
Add openid security

--- a/admin/agency2portfolio.php
+++ /dev/null
@@ -1,42 +1,1 @@
-<?php
 
-// Returns portfolio scraped live from directory.gov.au
-// or null if can't find a portfolio
-function agency2portfolio ($agency) {
-	static $cache = array();
-	if (isset($cache[$agency])) { return $cache[$agency]; }
-	$c = curl_init('http://www.directory.gov.au/searchres.php');
-	curl_setopt($c, CURLOPT_POST, true);
-	curl_setopt($c, CURLOPT_HEADER, false);
-	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
-	curl_setopt($c, CURLOPT_REFERER, 'http://www.directory.gov.au/adsearch.php');
-	curl_setopt($c, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3');
-	curl_setopt($c, CURLOPT_POSTFIELDS, array(
-		'advkeywordfield' => '',
-		'advorgunitfield' => $agency,
-		'advrolefield' => '',
-		'advsection' => 'All',
-		'advsurnamefield' => '',
-		'search' => 'Submit Query'
-	));
-	$results = curl_exec($c);
-	
-	if (preg_match('#<span\s+class="standardlinks"><a\s+href="([^"]+)">#smi', $results, $m)) {
-		$nextURL = $m[1];
-	} else {
-		$cache[$agency] = false; return false;
-	}
-	
-	curl_setopt($c, CURLOPT_URL, 'http://www.directory.gov.au' . $nextURL);
-	curl_setopt($c, CURLOPT_HTTPGET, true);
-	curl_setopt($c, CURLOPT_REFERER, 'http://www.directory.gov.au/searchres.php');
-	$results = curl_exec($c);
-	if (preg_match('#portfolios:\s+([^<]+)#ims', $results, $m)) {
-		$cache[$agency] = $m[1]; return $m[1];
-	} else {
-		$cache[$agency] = false; return false;
-	}
-}
-
-?>
-

--- a/admin/fixoldamend.php
+++ b/admin/fixoldamend.php
@@ -1,7 +1,8 @@
 <?php
-
-include_once("../lib/common.inc.php");
-
+if (php_sapi_name() != "cli") {
+    
+include_once ("../lib/common.inc.php");
+auth();
 $query = 'update contractnotice set "parentCN" = null where "parentCN" = \'0\'';
 $result = $conn->prepare($query);
 $result->execute();
@@ -37,4 +38,5 @@
         echo "parent CN unexpected - $oldCN doesn't look like child of {$record['parentCN']}, rather the ID suggests child of $parentCN <br>\n";
     }
 }
+}
 

--- a/admin/import.php
+++ b/admin/import.php
@@ -1,7 +1,8 @@
 <?php
-
+if (php_sapi_name() != "cli") {
+    
 include_once ("../lib/common.inc.php");
-
+auth();
 $contractNoticeFields = array(
     "importFile",
     "agencyName",
@@ -194,5 +195,6 @@
 //include ("setAgencyStatus.php");
 //include ("setAgencyURLABN.php");
 }
+}
 ?>
 

--- a/admin/importUNSPSC.php
+++ b/admin/importUNSPSC.php
@@ -1,5 +1,8 @@
 <?php
-  include_once("../lib/common.inc.php");
+if (php_sapi_name() != "cli") {
+    
+include_once ("../lib/common.inc.php");
+auth();
 
   // display existing
 
@@ -49,5 +52,6 @@
       echo "<br> $success records successfully created";
       flush();
       fclose($handle);
+}
 ?>
 

--- a/admin/linkAmendments.php
+++ b/admin/linkAmendments.php
@@ -1,5 +1,8 @@
 <?php
+if (php_sapi_name() != "cli") {
+    
 include_once ("../lib/common.inc.php");
+auth();
 $query = 'update contractnotice set "parentCN" = null where "parentCN" = \'0\'';
 $query = $conn->prepare($query);
 	$query->execute();
@@ -58,5 +61,6 @@
   else print_r($errors);
 }
 }
+}
 ?>
 

--- a/admin/partialdata/import.php
+++ b/admin/partialdata/import.php
@@ -1,5 +1,6 @@
 <?php
-include_once ("../../lib/common.inc.php");
+if (php_sapi_name() == "cli") {
+    include_once ("../../lib/common.inc.php");
 function processFile($fpath, $tablename)
 {
 	global $conn;
@@ -149,5 +150,6 @@
 	$success+= processFile($path . $fname, "contractnotice");
 
 }
+}
 ?>
 

--- a/admin/partialdata/importamendments.php
+++ b/admin/partialdata/importamendments.php
@@ -1,5 +1,6 @@
 <?php
-include_once ("../../lib/common.inc.php");
+if (php_sapi_name() == "cli") {
+    include_once ("../../lib/common.inc.php");
 /*
 update contractnotice set "supplierABN" = a."supplierABN" 
 from contractnotice as cn inner join (select "supplierABN", 
@@ -322,6 +323,6 @@
 	$success+= processFile($path . $fname, "contractnotice");
 
 }
-
+}
 ?>
 

--- a/admin/partialdata/scraper.txt
+++ b/admin/partialdata/scraper.txt
@@ -1,4 +1,5 @@
 <?php
+if (php_sapi_name() == "cli") {
 date_default_timezone_set('Australia/Melbourne');
 $split = false;
 function format_bytes($size) {
@@ -70,5 +71,6 @@
 } else {
 	getFile($startDate, $days, "" , "");
 }
+}
 ?>
 

--- a/admin/setAgencyStatus.php
+++ /dev/null
@@ -1,26 +1,1 @@
-<?php
-include_once("../lib/common.inc.php");
 
-$active = Array('0EC8D805-C293-3ADD-E51A93667D977314|Administrative Appeals Tribunal','0ECAA17D-9862-5309-101D94A126495C7F|Aged Care Standards and Accreditation Agency Ltd','CE45735B-F9FC-04DA-F299C730D9BE613E|Airservices Australia','0EC8DF71-ED98-8593-2D776EE93D9BA248|Attorney-General\'s Department','0ECA6B4C-DE4F-A04A-DBFB5F39B050B6D7|AusAid','0ECA6F64-F6B8-F406-E490A1479A7A717B|Austrade','D2724D99-F230-A93A-93D62A8DCC187940|Australia Council for the Arts','D26C4D0A-FF6E-612A-C6FEDB2D5E71AE4A|Australian Antarctic Division','C8E854CD-E966-9A35-078FF6702EC1ECED|Australian Broadcasting Corporation','0ECB50CF-028A-FB9D-3E5EF0F9708E4BC7|Australian Bureau of Statistics','0ECA671F-EA1F-8560-1D7C90408694B565|Australian Centre for International Agricultural Research','A861A42C-B32C-85A8-0F719005E27F4E23|Australian Commission for Law Enforcement Integrity','C8F9DAF8-9763-EA22-FA44B6BBC12B0B94|Australian Communications and Media Authority (ACMA)','0ECB2BB9-B19C-4929-87EC63542388D079|Australian Competition and Consumer Commission','0EC8E42E-EB6A-15DA-3F001AFB9C2B8D34|Australian Crime Commission','0EC8E8CE-9BCF-0651-D4362CF53DB8E4DC|Australian Customs and Border Protection Service','CD51BCA4-9DC7-9494-B7D944029D03A1F9|Australian Electoral Commission','D248626B-0BDD-B201-1A1C7C978AD973D2|Australian Fair Pay Commission','0EC8EDF3-E025-2F80-AEB39B350641C81F|Australian Federal Police','0EC8B90B-CBF5-8EC4-2C4E261041A25E16|Australian Fisheries Management Authority','0ECABD48-0388-F8AB-00BD4EB12FA44067|Australian Hearing Services','0EC920D5-CEB1-81FC-5A107A206F4A6BF3|Australian Human Rights Commission','D257B064-D245-51CC-F69A50FF4620915B|Australian Industrial Registry','0EC8F7B1-03ED-C4C3-70FBDF105ADB2FB6|Australian Institute of Criminology','D32EC07E-E22E-5805-1240CF68033C5E15|Australian Institute of Family Studies','0ECA9D77-A0B8-448A-53945B4DFACEE685|Australian Institute of Health and Welfare','D31CF0E2-B1A4-DE74-5D7B7B1DC97EA1DC|Australian Institute of Marine Science','0EC8FC41-C1BE-587A-A19920F43BA616BF|Australian Law Reform Commission','CE5DCAA3-D793-2FEE-B3C263B84AF2676C|Australian Maritime Safety Authority','0ECAED96-0B3B-1D24-56412EC3384996B0|Australian National Audit Office (ANAO)','D281D726-938C-D76D-A346348BEFC509F9|Australian National Maritime Museum','D31E738F-F139-2BC2-EBF2764D7812EA10|Australian Nuclear Science and Technology Organisation (ANSTO)','0ECB3D20-C837-FBE2-B1C32937F87CBB8F|Australian Office of Financial Management','82C5CFA1-0088-7B9E-9A3EF95987B55045|Australian Organ and Tissue Donation and Transplantation Authority','0EC8BF2B-B23F-0A07-0659BB9760EFC94E|Australian Pesticides and Veterinary Medicines Authority','0ECB5AFF-F4DC-06E3-97B3118B978F33BD|Australian Prudential Regulation Authority (APRA)','0ECAF18D-044A-0F5E-AD942B0CD5BB1ACC|Australian Public Service Commission','0ECA88C8-B803-ECE2-00F37CE4797C74BC|Australian Radiation Protection and Nuclear Safety Agency (ARPANSA)','D320882B-ED70-8061-6A5B2F8E64D6F575|Australian Research Council','CD5A2434-9592-2217-49970AF89E5956F8|Australian Reward Investment Alliance (ARIA)','0ECB44F1-BC3B-58E0-96CD33517E10E38B|Australian Securities and Investments Commission','0EC9056F-C13F-ED84-07B410D54C4A1013|Australian Security Intelligence Organisation','D303A5DD-B83B-EC45-588E3680B4E842E4|Australian Sports Anti-Doping Authority (ASADA)','0ECB54CA-EE15-8B26-3C430607E8DB3D4E|Australian Taxation Office','0EC900DB-D645-6C40-DD8BEA03CC15D392|Australian Transaction Reports and Analysis Centre (AUSTRAC)','77068F86-0A15-723A-C2FC1601C44FFCD2|Australian Transport Safety Bureau','0EC9AC89-DEB3-1441-2441891A3772863E|Australian War Memorial','D28B4F07-C2F2-2D4F-1C9229D177088373|Bureau of Meteorology','0ECA90FE-A44B-2EAA-08039CC6C43E728D|Cancer Australia','0ECAB1CF-D480-0C8B-70670DFC4FFDAC56|Centrelink','CE608086-A4DC-45F6-1179DCC148CF91F7|Civil Aviation Safety Authority','D24AFCAC-A2B2-4766-17C9A3EC3AAE4205|Comcare','166B9AD2-ACC9-B56F-4D5B47BD325DEB25|Commonwealth Grants Commission','CD5BDE89-9805-D921-0DAAFDBD5312D0D5|Comsuper','0ECB5EF6-CC7B-7C8E-32798557B78F6D98|Corporations and Markets Advisory Committee','0EC909FF-D902-5FD3-6660ADB353470F95|Crimtrac','0ECAC700-BDE8-458D-6A4E91C8B151A270|CRS Australia','D3275762-D634-4459-D33A26E1BEA05D14|CSIRO','0EC99E6C-0375-2DAF-A8338121FCBD6F7A|Defence Housing Australia','0EC9A29C-E478-AD19-3F0F29F6BE4914BF|Defence Materiel Organisation','0EC892FD-E156-BCB0-ADF5D534E584BC18|Department of Agriculture, Fisheries and Forestry','FB2B0C69-0AAF-1B2B-8186EC38772F8FC1|Department of Broadband, Communications and the Digital Economy','69368049-A8EF-0896-EA9C603C856788D9|Department of Climate Change and Energy Efficiency','0EC98EF9-E020-5DA9-DCB10135BE81BD2B|Department of Defence','D23E9B50-DAE3-6701-D90A2582EE48A4DA|Department of Education, Employment and Workplace Relations','D2FBF89C-D43D-4B3E-527132AC0D41ABBB|Department of Families, Housing, Community Services and Indigenous Affairs','CD39DC0D-D4B3-DFEC-DDDCD52E651F06EA|Department of Finance and Deregulation','0ECA5FC9-A4AD-0A3A-97A8DD3FD9D44CB1|Department of Foreign Affairs and Trade','0ECA80C0-EC9F-B5B9-0D3E8A4537C39808|Department of Health and Ageing','0ECAADBC-B488-A53B-F4F6672EC813849A|Department of Human Services','0ECB6A9B-BA91-CB7D-36C62C5C9191D403|Department of Immigration & Citizenship','047091E8-CEE4-A9FF-BF552F538D279807|Department of Infrastructure and Transport','FADB0367-F000-E0B9-8F79FD109F613AB6|Department of Innovation, Industry, Science and Research','0ECB7ED8-BB19-D108-324A8A05B1966ABF|Department of Parliamentary Services','2F03BD8D-F73D-C35C-729559C6C70C6602|Department of Regional Australia, Regional Development and Local Government','FADFD268-9BF5-97E4-2F247B92F74E82C0|Department of Resources, Energy and Tourism','62304115-AA13-68D1-592110A8021DA68E|Department of Sustainability, Environment, Water, Population and Communities','0ECB8490-9B07-1898-98170CDD42A9DFF7|Department of the House of Representatives','0ECAE795-F858-203D-FD1C9FF933EF4A37|Department of the Prime Minister and Cabinet','0ECB8A6E-B354-715B-C9DEDE4A6DA3C393|Department of the Senate','0ECB25C0-02C3-6BAA-4738951514F489A9|Department of the Treasury','0EC9A85E-BA37-0A60-47A828CD70D831FC|Department of Veterans\' Affairs','D2E272D5-DDD0-178B-F1BE5A9181D3ACAE|Director of National Parks','D2F85A2B-E563-118E-0CB302E02C74397C|Equal Opportunity for Women in the Workplace Agency','0ECA73FA-FAAD-62B5-48581DF753E57CCA|Export Finance and Insurance Corporation (EFIC)','A535F827-E58E-89FB-7F58107CBBD0D79A|Fair Work Australia','0EC91301-C770-6470-480E879920826ABD|Family Court of Australia','0EC91799-A81B-262C-CE6ACAB2622F0566|Federal Court of Australia','0EC91C3C-BB6B-97BF-76BEF63DD0905AAF|Federal Magistrates Court','08355C10-AB46-67AA-421E334D1B45E125|Food Standards Australia New Zealand','CD5E362E-A615-1102-67814B74731025AD|Future Fund Management Agency','D33926C6-BB30-4A30-3A29A5821CFB7AE6|Geoscience Australia','0EC8CA07-AE70-7EB2-5A174487D4BD6236|Grains Research and Development Corporation','D2E5D48D-08A4-5CC9-5378FEA3CD8E649C|Great Barrier Reef Marine Park Authority','0EC925CD-F184-26AF-DE8A84DE3CBA3790|Insolvency and Trustee Service Australia (ITSA)','0ECB302F-AADD-2BB1-392F50E0E94CB076|Inspector-General of Taxation','D3174EBA-F3C0-28E6-7A669C23CF0B3040|IP Australia','0ECAB5DF-B75F-EC35-92EA7821EFF77C0D|Medicare Australia','0ECB74A7-BE11-FC3C-8696F7F93D7612C0|Migration Review Tribunal and Refugee Review Tribunal (MRT-RRT)','F8E42DAB-9570-A008-97294775650CCE6E|Murray-Darling Basin Authority','0A18A552-0B56-257B-70BE077385CE2EDC|National Archives of Australia','0ECA8CEA-08C9-6D55-C57A7A102DFFBC8A|National Blood Authority','D2347B3A-F330-0317-68AB0C4F7240B9A5|National Capital Authority','0ECB3416-FEB1-F5E0-FB4760831CA2F66E|National Competition Council','7DEE189B-0A00-07D1-2D1C778EDAEFEF6D|National Film and Sound Archive','D2E7830B-9692-3124-1C2E1D74AAC8FFFF|National Gallery of Australia','0ECA957E-E479-2758-528FA4E576A27A5D|National Health and Medical Research Council','D2EA1497-0BB3-AFBD-76559FA87101FF06|National Library of Australia','D2EC2CF2-FEEE-8FB2-46193EDF8EA2F503|National Museum of Australia','0EC92A40-B2DA-D4FF-16895BCD00F9E20F|National Native Title Tribunal','D33DB6EB-FEE1-F686-7385E65E48749DD2|National Offshore Petroleum Safety Authority','D2EF702D-EF9B-B363-BDA3A4950C5ED57F|National Water Commission','0ECB00AF-04FE-735D-82D0B84D1A19F95A|Office of National Assessments','0EC93653-9FCE-12DD-DAAC73E23DCAE8CA|Office of Parliamentary Counsel','0ECB48E3-9198-6F2C-9737A69F9D15177A|Office of the Auditing and Assurance Standards Board','0ECB4CD6-A6D6-4334-D550948337CE9F56|Office of the Australian Accounting Standards Board','D25ACD14-ABA3-F69F-E1ADA4503ED97C82|Office of the Australian Building and Construction Commissioner (ABCC)','D331236A-BDE0-5CD7-24EF78F052CB2B50|Office of the Australian Information Commissioner','0ECAF6CA-ACBE-E3E7-6DCBF175E012F817|Office of the Commonwealth Ombudsman','0EC93B4D-DC77-6FA0-7E15530A7D7344CB|Office of the Director of Public Prosecutions','39DCC587-FF78-8759-E91BF5B9EC3D1904|Office of the Fair Work Ombudsman','0ECB049D-B7DC-1697-586AB4455B0DF251|Office of the Inspector-General of Intelligence and Security','0ECB0887-D30E-E490-3F16BCB7EF406D8E|Office of the Official Secretary to the Governor-General','E0EAE43E-B918-12DC-9F4E0E03811CACAD|Office of the Renewable Energy Regulator','D726F8EA-D648-B4DF-E25FF1EEE68C9700|Old Parliament House','87A575EB-E6CE-7E8B-1CB3D9B6580FAE5B|Private Health Insurance Ombudsman','0ECB380B-FC93-8003-9A1F04BD55A10F62|Productivity Commission','0ECA9978-D7C8-3F44-2D21C2827407BEF5|Professional Services Review','0ECB64C5-E6E4-3D23-B4409B18EB609B25|Reserve Bank of Australia','0ECB4106-02BC-64C6-1DF8032E549D88E4|Royal Australian Mint','4A350604-FD8E-C7BB-D6C6A14AEE6D8873|Safe Work Australia','D25D96A7-B9B7-21D2-57AA60EAC138107B|Seacare','D2FF2257-0E69-4B7C-FCF6AB672ECC341C|Social Security Appeals Tribunal','D2F16C5B-BCFB-E14D-9B62DEEDF992798B|Sydney Harbour Federation Trust','0ECA84C7-D6C3-3299-9F898B9C201D771B|Therapeutic Goods Administration','D340FD88-0AFE-B091-1F43D9EDBCE164D4|Tourism Australia','EB74EB82-AA02-F70B-DC648859ABFC28DA|Wheat Exports Australia');
-
-$suspended = Array('8B346DD0-D9A5-585C-1B3174A9B6292AD1|Aboriginal and Torres Strait Islander Services','0ECB16D3-9684-471C-3FFBCCCAB63728B4|Airservices Australia','0EC975AC-F94D-B8B5-FDF5CF053D78F7EA|Australia Council for the Arts','0ECA7934-9725-0D31-1BD08C6ADFD16040|Australia-Japan Foundation','0ECA043A-C02D-1838-E994DF3EC8B0A857|Australian Antarctic Division','0EC96B6B-9A30-F9E4-892DA6860B3985B2|Australian Broadcasting Corporation','0EC9521F-C38A-2FB1-54CEF555CE76807A|Australian Communications and Media Authority (ACMA)','0ECA48E7-B536-E530-CA1F4F8A4056517F|Australian Electoral Commission','0EC9D8CD-D6F8-A695-854B714011D9B2E8|Australian Fair Pay Commission','0EC9703D-9C88-C7FF-E15390103B51CE22|Australian Film Commission','D27DB409-E5C9-15AD-635A70CEF5A8E05C|Australian Film Commission','0EC96705-AED6-462D-E1579FE99DD57511|Australian Film Television and Radio School','D27F93E1-9898-8ED5-5DF9628A1CE78BBF|Australian Film Television and Radio School','0EC9DCED-ED0B-FF25-20323EF5E93EC88E|Australian Industrial Registry','0EC9BF9B-C83C-4EC5-DDE3C845EBF3CCC0|Australian Institute of Aboriginal and Torres Strait Islander Studies (AIATSIS)','D246AE91-0DD2-193E-84F9A3E13664E58E|Australian Institute of Aboriginal and Torres Strait Islander Studies (AIATSIS)','0ECA2851-AD46-9261-B3A77D28C5CC1351|Australian Institute of Family Studies','0EC9BB58-BBA8-3F3D-AD04CBFE4FABC85D|Australian Institute of Marine Science','0ECB1ACC-FAAC-3895-9EC179BC71C3FC54|Australian Maritime Safety Authority','0EC9611A-C5D2-43B5-D4039EED991272E3|Australian National Maritime Museum','0EC9C3D2-CE1C-8C81-085ADA97E2172420|Australian Nuclear Science and Technology Organisation (ANSTO)','0EC9B71F-EDBC-5E42-E44D5595BDF7B3F4|Australian Research Council','0ECA59B7-D4C6-360D-4829CB8A5E15B664|Australian Reward Investment Alliance (ARIA)','0EC95958-08E9-4A16-A79AC1CB828B9535|Australian Sports Anti-Doping Authority (ASADA)','0EC8C3DE-B6EB-88B8-0FD9460AC543DD0C|Biosecurity Australia','0ECA1921-EA6F-1BE2-3E85DBF7BBCDE734|Bureau of Meteorology','0ECAC142-B8B8-CCE0-A4B00E4B67ED0DCB|Child Support Agency','0ECB1FED-D2C6-6214-2CEB31CE9B107ED1|Civil Aviation Safety Authority','0EC9D499-F2DE-F8D9-0239ACA17C2ABFBB|Comcare','0ECA55A3-A0B2-0D36-D383E6A113AD5749|Commonwealth Grants Commission','0ECA4CF9-9312-2F3D-637C98CF2FEEB5A4|Comsuper','0EC9C7FE-F7AC-09BD-3C498B63A9DDDDA4|CSIRO','0EC8B354-F006-A824-A43120384BE56A98|Dairy Adjustment Authority','D3328CCE-DEA4-5516-AA23F77F557454FC|Department of Climate Change','0EC94B72-AD24-B695-3C5C388FBC0C23AA|Department of Communications, Information Technology and the Arts','0EC9B2BB-D5C8-8061-8CC999143D0D291C|Department of Education, Science and Training','0EC9CE2C-A998-C45A-90697489AC42B83B|Department of Employment and Workplace Relations','0ECA21F5-F5FE-7F08-52CD6FFB54E788FE|Department of Families, Community Services & Indigenous Affairs','0ECA32E7-E860-DFD4-0FBDCE1CC8E0FC84|Department of Finance and Administration','8B3539BA-EC97-7510-AF049F389DC0497A|Department of Immigration and Multicultural Affairs','8B361B4A-EAAE-D747-134D9FA97497C70F|Department of Immigration and Multicultural and Indigenous Affairs (DIMIA)','0ECACF65-D93B-D892-1862B65F15C92A6A|Department of Industry, Tourism and Resources','CE419954-CB32-1154-9E61EDB5805C222D|Department of Infrastructure, Transport, Regional Development and Local Government','0EC9F8FB-F224-1E70-F59834C1D5CE12D5|Department of the Environment and Water Resources','D2DFCF42-A87B-181C-2F48D7EEE8849AFD|Department of the Environment, Water, Heritage and the Arts','0ECB0EE4-9D78-A341-363B0D13161C5916|Department of Transport and Regional Services','0ECA0863-0F4E-5FA8-B7C522D16CCAC9BB|Director of National Parks','0EC90E81-DAF0-F67A-7FDC1AE664FF0BEE|Emergency Management Australia','0EC9E429-9FFF-7E05-41ADCC29FE17811C|Equal Opportunity for Women in the Workplace Agency','576FCE5F-E258-4A5C-92C11639C73A0442|Export Wheat Commission','0ECA518C-EF07-86F5-ADCAA84198009116|Future Fund Management Agency','0ECAE1C5-E249-022B-DCC7D944F398E1FD|Geoscience Australia','0ECA0CF1-AEF7-5C78-23878C6A5BF93C36|Great Barrier Reef Marine Park Authority','0ECADD52-F400-97E8-25F98B9FF96FFD08|IP Australia','0EC8D025-D94D-FF1D-97130B40320BAD9A|Land and Water Australia','0EC98433-EB5E-BEB1-1EEC7CADF83918A0|National Archives of Australia','CD603E31-CD1F-AAE5-F233F954A031553E|National Archives of Australia','0ECB12DC-BF41-FF5B-CB306CF040EC3218|National Capital Authority','0EC97A03-EA2E-5AE3-EC3C2C23487ECECA|National Gallery of Australia','0EC98880-F5F1-6560-605A0250EA58D139|National Library of Australia','0EC97FE7-E894-67B2-A8CE40DEF409AC23|National Museum of Australia','0ECAD957-B214-674F-3C94DEEF52BA3F46|National Offshore Petroleum Safety Authority','0ECAFC83-0B5D-045A-EE43EA12B9953CC0|National Water Commission','48499BD1-AFE0-1093-EF06293D78B3FD65|National Water Commission','0EC92FD3-F7B3-B0E0-9A8D8D2B611BB615|Office of Film and Literature Classification','0EC9E84A-A9F7-4EEF-5DDD2FC87C1F1B55|Office of the Australian Building and Construction Commissioner (ABCC)','0EC940DD-9A7E-1ECD-B44DDA0B72B2407F|Office of the Privacy Commissioner','0ECA10FE-AC03-80D8-C8DF1069C707351E|Office of the Renewable Energy Regulator','D3362537-FE00-1892-B408CABF5E0257A7|Office of the Renewable Energy Regulator','0EC9F2EC-C778-46D0-F15FDB1A8F1552F9|Office of Workplace Services','FB2A3EA0-D6FC-DB1D-19B0B72180A8A19B|Questacon','D329A60A-E914-FB37-FBA853A4CE8558EB|Questacon','0ECB788C-CC5C-2969-4B94EFB3913B0E65|Refugee Review Tribunal','A2A6EBF4-A8F5-E60B-5C25ACD3EA0679D8|Screen Australia','0EC9EC88-9B12-CB63-56A58BE69BDF3B6F|Seacare','0ECA2CE0-CBA9-9E13-AA0FB347CF4B5FD1|Social Security Appeals Tribunal','0ECA1510-B4A2-BBA4-D6FDE37DFAAC7C12|Sydney Harbour Federation Trust','0ECAA587-C851-4038-8CDC9CCE9A28F6E6|The National Institute of Clinical Studies Ltd','0ECAD567-E1E7-F5FC-72BA34FDF72F0FC0|Tourism Australia','7A592345-0410-DF1C-4B349EA35F314D3F|Workplace Authority','003B8DE1-D09A-D72F-28CEBC19DB84E866|Workplace Ombudsman','D260B27E-9744-EF72-C065072F24B4A62A|Workplace Ombudsman');
-
-foreach ($active as $agency ) {
-	$agencyParts = explode("|",$agency);
-	$agency = $agencyParts[1];
-	$agencyInsert = "INSERT INTO agency (agencyName) VALUES ('$agency')";
-	$result = mysql_query($agencyInsert);
-	$result = mysql_query("UPDATE agency SET status = 'active' where agencyName = '".mysql_real_escape_string($agency)."';") ;
-	if ($result) echo $agency. " set to active in ". mysql_affected_rows() . " divisions/branches <br>\n";
-	else echo "error".mysql_error();
-}
-foreach ($suspended as $agency) {
-	$agencyParts = explode("|",$agency);
-	$agency = $agencyParts[1];
-	$agencyInsert = "INSERT INTO agency (agencyName) VALUES ('$agency')";
-	$result = mysql_query($agencyInsert);
-	$result = mysql_query("UPDATE agency SET status = 'suspended' where agencyName = '".mysql_real_escape_string($agency)."';") ;
-	if ($result) echo $agency. " set to suspended in ". mysql_affected_rows() . " divisions/branches <br>\n";
-	else echo "error".mysql_error();
-}
-?>

--- a/admin/setAgencyURLABN.php
+++ /dev/null
@@ -1,38 +1,1 @@
-<?php
-include_once ("../lib/common.inc.php");
-// to reset: update agency set abn = 0, website = ''
-$ch = curl_init();
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
-curl_setopt($ch, CURLOPT_REFERER, "http://contractdashboard.lambdacomplex.org");
-$querySel = sprintf("SELECT * FROM `agency` where ABN = 0 OR website = ''");
-$resultSel = mysql_query($querySel);
-while ($row = mysql_fetch_array($resultSel, MYSQL_ASSOC)) {
-  $agency = $row['agencyName'];
-  //foreach $agency
-  $url = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&key=ABQIAAAA95XYXN0cki3Yj_Sb71CFvBSgIPe2k9-DRgEVQvbvQDV8xmTiXRTBpHxrD7bSh5rl2lswY56769CyQQ&rsz=small&filter=1&gl=au&q=" . urlencode($agency) . "%20ABN%20site:.gov.au%20-site:www.abr.business.gov.au";
-  curl_setopt($ch, CURLOPT_URL, $url);
-  $body = curl_exec($ch);
-  $json = json_decode($body);
-  echo "<pre>";
-  $result = $json->responseData->results[0];
-  print_r($result);
-  echo "</pre>";
-  echo "<b> Agency:</b> $agency";
-  echo "<b> URL:</b> http://" . $result->visibleUrl;
-  preg_match('/\d{2} \d{3} \d{3} \d{3}/i', $result->content, $abn);
-  $abn[0] = str_replace(" ","",$abn[0]);
-  echo "<b> ABN:</b> {$abn[0]}";
-  if ($abn[0] > 1000) {
-  $result = mysql_query("UPDATE agency SET website = 'http://" . $result->visibleUrl . "', abn = '{$abn[0]}' WHERE agencyName = '$agency';");
-  if ($result) echo $agency . " set in " . mysql_affected_rows() . " <br>\n";
-  } else {
-	
-	echo "invalid ABN";
-	$result = mysql_query("UPDATE agency SET website = 'http://" . $result->visibleUrl . "' WHERE agencyName = '$agency';");
-	if ($result) echo $agency . " set in " . mysql_affected_rows() . " <br>\n";
-  }
-  // fi
-  
-}
-curl_close($ch);
-?>
+

--- a/displayContract.php
+++ b/displayContract.php
@@ -34,7 +34,7 @@
 echo '<br><a href="https://www.tenders.gov.au/?event=public.advancedsearch.keyword&keyword=CN' . $_REQUEST['CNID'] . '"> View original record @ tenders.gov.au</a><br>';

 

 

-$query = 'SELECT * FROM `heuristic_results` where "CNID" = :CNID';

+$query = 'SELECT * FROM heuristic_results where "CNID" = :CNID';

 $query = $conn->prepare($query);

 $agencyName = $input . '%';

 $query->bindParam(":CNID", $_REQUEST['CNID']);


file:a/futureindex.php (deleted)
--- a/futureindex.php
+++ /dev/null
@@ -1,12 +1,1 @@
-<?php
-include_once ("./lib/common.inc.php");
-include_header("Home");
-echo '        <div class="hero-unit">
-          <h1>Hello, world!</h1>
-          <p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
-          <p><a class="btn primary large">Learn more &raquo;</a></p>
-        </div>';
-include_footer();
-?>
 
-

--- a/heuristics/metadataHeuristics.php
+++ b/heuristics/metadataHeuristics.php
@@ -1,7 +1,7 @@
  <?php
 /* all
    SELECT description, count(*) as count
-FROM `contractnotice` 
+FROM contractnotice 
 group by description having count > 1 order by count
 */
 /*- duplicated description
@@ -48,7 +48,7 @@
 {
 	$query = "select avg(count),STDDEV(count) from (
         SELECT description, count(*) as count
-FROM `contractnotice` 
+FROM contractnotice 
 group by description having count > 1 
         ) as a;";
     $result = $conn->query($query);

--- a/heuristics/runHeuristics.php
+++ b/heuristics/runHeuristics.php
@@ -1,4 +1,8 @@
 <?php
+if (php_sapi_name() != "cli") {
+    
+include_once ("../lib/common.inc.php");
+auth();
 include_once("heuristics.inc.php");
 
 $query = 'SELECT *, agency.abn as "agencyABN", case when "supplierABN" != 0 then "supplierABN"::text else "supplierName" end as "supplierID"
@@ -27,5 +31,6 @@
 foreach CN
 
 aggregate CN metrics */
+}
 ?>
 

--- a/heuristics/viewHeuristicsDistribution.php
+++ b/heuristics/viewHeuristicsDistribution.php
@@ -2,7 +2,7 @@
 
 /*// most interesting
 SELECT sum(heuristic_value) as sum, CNID
-FROM `heuristic_results` group by CNID order by sum DESC limit 30
+FROM heuristic_results group by CNID order by sum DESC limit 30
 
 // spread of values
 select floor(sum) as val,count(*) from (SELECT sum(heuristic_value) 

file:b/index.php (new)
--- /dev/null
+++ b/index.php
@@ -1,1 +1,12 @@
+<?php
+include_once ("./lib/common.inc.php");
+include_header("Home");
+echo '        <div class="hero-unit">
+          <h1>Hello, world!</h1>
+          <p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.</p>
+          <p><a class="btn primary large">Learn more &raquo;</a></p>
+        </div>';
+include_footer();
+?>
 
+

--- a/lib/common.inc.php
+++ b/lib/common.inc.php
@@ -8,6 +8,41 @@
 

 if (!$conn) {

     die("A database error occurred.\n");

+}

+

+define('ROOT' , pathinfo(__FILE__, PATHINFO_DIRNAME));

+if (strstr($_SERVER['PHP_SELF'], "labs/")) {

+    $basePath = "../";

+}

+

+require ROOT . DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'openid.php';

+$openid = new LightOpenID($_SERVER['HTTP_HOST']);

+

+function login() {

+    global $openid;

+    if (!$openid->mode) {

+        $openid->required = array('contact/email');

+        $openid->identity = 'https://www.google.com/accounts/o8/id';

+        header('Location: ' . $openid->authUrl());

+    }

+}

+

+function auth() {

+    global $openid;

+    if ($_SESSION['authed'] == true) {

+        return true;

+    }

+

+    if ($openid->mode) {

+        $attr = $openid->getAttributes();

+        if ($attr['contact/email'] != 'maxious@gmail.com') {

+            die('Access Denied');

+        } else {

+            $_SESSION['authed'] = true;

+        }

+    } else {

+        login();

+    }

 }

 

 //   $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

@@ -104,14 +139,14 @@
                 @import "media/css/demo_table.css";

             </style> 

             <script type="text/javascript" language="javascript" src="media/js/jquery.dataTables.js"></script> 

-           <script type="text/javascript" language="javascript" src="lib/bootstrap-dropdown.js"></script>

+            <script type="text/javascript" language="javascript" src="lib/bootstrap-dropdown.js"></script>

             <script type="text/javascript" charset="utf-8"> 

                 jQuery.fn.dataTableExt.aTypes.unshift(  

                 function ( sData )  

                 {  

                     var sValidChars = "0123456789.-,";  

                     var Char;  

-                      

+                          

                     /* Check the numeric part */  

                     for ( i=1 ; i<sData.length ; i++ )   

                     {   

@@ -121,7 +156,7 @@
                             return null;  

                         }  

                     }  

-                      

+                          

                     /* Check prefixed by currency */  

                     if ( sData.charAt(0) == '$' || sData.charAt(0) == '£' )  

                     {  

@@ -134,11 +169,11 @@
                     /* Remove any commas (assumes that if present all strings will have a fixed number of d.p) */

                     var x = a == "-" ? 0 : a.replace( /,/g, "" );

                     var y = b == "-" ? 0 : b.replace( /,/g, "" );

-            	

+                	

                     /* Remove the currency sign */

                     x = x.substring( 1 );

                     y = y.substring( 1 );

-            	

+                	

                     /* Parse and return */

                     x = parseFloat( x );

                     y = parseFloat( y );

@@ -149,11 +184,11 @@
                     /* Remove any commas (assumes that if present all strings will have a fixed number of d.p) */

                     var x = a == "-" ? 0 : a.replace( /,/g, "" );

                     var y = b == "-" ? 0 : b.replace( /,/g, "" );

-            	

+                	

                     /* Remove the currency sign */

                     x = x.substring( 1 );

                     y = y.substring( 1 );

-            	

+                	

                     /* Parse and return */

                     x = parseFloat( x );

                     y = parseFloat( y );

@@ -175,30 +210,30 @@
                             <li><a href="displaySupplier.php">suppliers</a></li>

                             <li><a href="displayCategory.php">categories</a></li>

                             <li><a href="displayCalendar.php">time periods</a></li>

-                          <!--  <li class="dropdown">

-                                <a href="#" class="dropdown-toggle">metrics</a>

-                                <ul class="dropdown-menu">-->

-                                    <li><a href="displayProcurementMethod.php">tenderm</a></li>

-                                    <li><a href="displayConfidentialities.php">confidentiality</a></li>

-                                    <li><a href="displayConsultancies.php">consultancies</a></li>

-                                    <li><a href="displayAmendments.php">amendments</a></li>

-                                    <li><a href="displayMap.php">geo</a></li>

-                              <!--  </ul>

-                            </li>-->

+                            <!--  <li class="dropdown">

+                                  <a href="#" class="dropdown-toggle">metrics</a>

+                                  <ul class="dropdown-menu">-->

+                            <li><a href="displayProcurementMethod.php">tenderm</a></li>

+                            <li><a href="displayConfidentialities.php">confidentiality</a></li>

+                            <li><a href="displayConsultancies.php">consultancies</a></li>

+                            <li><a href="displayAmendments.php">amendments</a></li>

+                            <li><a href="displayMap.php">geo</a></li>

+                            <!--  </ul>

+                          </li>-->

                         </ul>  

-                       

-

-                            <form method="post" action="search.php" class="pull-right">

-                                <input type="text" id="searchKeyword" name="searchKeyword"  value="" placeholder="Search" />

-                                <input type="hidden" id="searchID" name="searchID" value=""/>

-                            </form>

-                        

+

+

+                        <form method="post" action="search.php" class="pull-right">

+                            <input type="text" id="searchKeyword" name="searchKeyword"  value="" placeholder="Search" />

+                            <input type="hidden" id="searchID" name="searchID" value=""/>

+                        </form>

+

                     </div>

                 </div><!-- /topbar-inner -->

             </div><!-- /topbar -->

         </div><!-- /topbar-wrapper -->

         <script type="text/javascript">

-            	

+                	

             var options_xml = {

                 script: function (input) { return "search_autosuggest.php?input="+input; },

                 varname:"input",

@@ -210,25 +245,25 @@
             <div class="sidebar">

                 <div class="well">

                     Filter by:<li>

-                        <li>year

-                            <li><li>2008</li>

-                            </li>

-                        </li>

+                    <li>year

+                    <li><li>2008</li>

+                    </li>

+                    </li>

                     </li> <br>       

                 </div> </div>

-        <div class="content">

-            <?php

-            $start = (float) array_sum(explode(' ', microtime()));

-        }

-

-        function include_footer() {

-            global $start;

-            $end = (float) array_sum(explode(' ', microtime()));

-

-            echo '  <footer>' . "Processing time: " . sprintf("%.4f", ($end - $start)) . " seconds" . '  <footer>';

-            echo '</div> </div></body> </html>';

-        }

-

-        include ("graphs.inc.php");

-        ?>

+            <div class="content">

+                <?php

+                $start = (float) array_sum(explode(' ', microtime()));

+            }

+

+            function include_footer() {

+                global $start;

+                $end = (float) array_sum(explode(' ', microtime()));

+

+                echo '  <footer>' . "Processing time: " . sprintf("%.4f", ($end - $start)) . " seconds" . '  <footer>';

+                echo '</div> </div></body> </html>';

+            }

+

+            include ("graphs.inc.php");

+            ?>

 

--- /dev/null
+++ b/lib/openid-php/Auth/OpenID/Consumer.php
@@ -1,1 +1,2235 @@
-
+<?php
+
+/**
+ * This module documents the main interface with the OpenID consumer
+ * library.  The only part of the library which has to be used and
+ * isn't documented in full here is the store required to create an
+ * Auth_OpenID_Consumer instance.  More on the abstract store type and
+ * concrete implementations of it that are provided in the
+ * documentation for the Auth_OpenID_Consumer constructor.
+ *
+ * OVERVIEW
+ *
+ * The OpenID identity verification process most commonly uses the
+ * following steps, as visible to the user of this library:
+ *
+ *   1. The user enters their OpenID into a field on the consumer's
+ *      site, and hits a login button.
+ *   2. The consumer site discovers the user's OpenID server using the
+ *      YADIS protocol.
+ *   3. The consumer site sends the browser a redirect to the identity
+ *      server.  This is the authentication request as described in
+ *      the OpenID specification.
+ *   4. The identity server's site sends the browser a redirect back
+ *      to the consumer site.  This redirect contains the server's
+ *      response to the authentication request.
+ *
+ * The most important part of the flow to note is the consumer's site
+ * must handle two separate HTTP requests in order to perform the full
+ * identity check.
+ *
+ * LIBRARY DESIGN
+ * 
+ * This consumer library is designed with that flow in mind.  The goal
+ * is to make it as easy as possible to perform the above steps
+ * securely.
+ *
+ * At a high level, there are two important parts in the consumer
+ * library.  The first important part is this module, which contains
+ * the interface to actually use this library.  The second is the
+ * Auth_OpenID_Interface class, which describes the interface to use
+ * if you need to create a custom method for storing the state this
+ * library needs to maintain between requests.
+ *
+ * In general, the second part is less important for users of the
+ * library to know about, as several implementations are provided
+ * which cover a wide variety of situations in which consumers may use
+ * the library.
+ *
+ * This module contains a class, Auth_OpenID_Consumer, with methods
+ * corresponding to the actions necessary in each of steps 2, 3, and 4
+ * described in the overview.  Use of this library should be as easy
+ * as creating an Auth_OpenID_Consumer instance and calling the
+ * methods appropriate for the action the site wants to take.
+ *
+ * STORES AND DUMB MODE
+ *
+ * OpenID is a protocol that works best when the consumer site is able
+ * to store some state.  This is the normal mode of operation for the
+ * protocol, and is sometimes referred to as smart mode.  There is
+ * also a fallback mode, known as dumb mode, which is available when
+ * the consumer site is not able to store state.  This mode should be
+ * avoided when possible, as it leaves the implementation more
+ * vulnerable to replay attacks.
+ *
+ * The mode the library works in for normal operation is determined by
+ * the store that it is given.  The store is an abstraction that
+ * handles the data that the consumer needs to manage between http
+ * requests in order to operate efficiently and securely.
+ *
+ * Several store implementation are provided, and the interface is
+ * fully documented so that custom stores can be used as well.  See
+ * the documentation for the Auth_OpenID_Consumer class for more
+ * information on the interface for stores.  The implementations that
+ * are provided allow the consumer site to store the necessary data in
+ * several different ways, including several SQL databases and normal
+ * files on disk.
+ *
+ * There is an additional concrete store provided that puts the system
+ * in dumb mode.  This is not recommended, as it removes the library's
+ * ability to stop replay attacks reliably.  It still uses time-based
+ * checking to make replay attacks only possible within a small
+ * window, but they remain possible within that window.  This store
+ * should only be used if the consumer site has no way to retain data
+ * between requests at all.
+ *
+ * IMMEDIATE MODE
+ *
+ * In the flow described above, the user may need to confirm to the
+ * lidentity server that it's ok to authorize his or her identity.
+ * The server may draw pages asking for information from the user
+ * before it redirects the browser back to the consumer's site.  This
+ * is generally transparent to the consumer site, so it is typically
+ * ignored as an implementation detail.
+ *
+ * There can be times, however, where the consumer site wants to get a
+ * response immediately.  When this is the case, the consumer can put
+ * the library in immediate mode.  In immediate mode, there is an
+ * extra response possible from the server, which is essentially the
+ * server reporting that it doesn't have enough information to answer
+ * the question yet.
+ *
+ * USING THIS LIBRARY
+ *
+ * Integrating this library into an application is usually a
+ * relatively straightforward process.  The process should basically
+ * follow this plan:
+ *
+ * Add an OpenID login field somewhere on your site.  When an OpenID
+ * is entered in that field and the form is submitted, it should make
+ * a request to the your site which includes that OpenID URL.
+ *
+ * First, the application should instantiate the Auth_OpenID_Consumer
+ * class using the store of choice (Auth_OpenID_FileStore or one of
+ * the SQL-based stores).  If the application has a custom
+ * session-management implementation, an object implementing the
+ * {@link Auth_Yadis_PHPSession} interface should be passed as the
+ * second parameter.  Otherwise, the default uses $_SESSION.
+ *
+ * Next, the application should call the Auth_OpenID_Consumer object's
+ * 'begin' method.  This method takes the OpenID URL.  The 'begin'
+ * method returns an Auth_OpenID_AuthRequest object.
+ *
+ * Next, the application should call the 'redirectURL' method of the
+ * Auth_OpenID_AuthRequest object.  The 'return_to' URL parameter is
+ * the URL that the OpenID server will send the user back to after
+ * attempting to verify his or her identity.  The 'trust_root' is the
+ * URL (or URL pattern) that identifies your web site to the user when
+ * he or she is authorizing it.  Send a redirect to the resulting URL
+ * to the user's browser.
+ *
+ * That's the first half of the authentication process.  The second
+ * half of the process is done after the user's ID server sends the
+ * user's browser a redirect back to your site to complete their
+ * login.
+ *
+ * When that happens, the user will contact your site at the URL given
+ * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
+ * call made above.  The request will have several query parameters
+ * added to the URL by the identity server as the information
+ * necessary to finish the request.
+ *
+ * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
+ * call its 'complete' method, passing in all the received query
+ * arguments.
+ *
+ * There are multiple possible return types possible from that
+ * method. These indicate the whether or not the login was successful,
+ * and include any additional information appropriate for their type.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/HMAC.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/Nonce.php";
+require_once "Auth/OpenID/Discover.php";
+require_once "Auth/OpenID/URINorm.php";
+require_once "Auth/Yadis/Manager.php";
+require_once "Auth/Yadis/XRI.php";
+
+/**
+ * This is the status code returned when the complete method returns
+ * successfully.
+ */
+define('Auth_OpenID_SUCCESS', 'success');
+
+/**
+ * Status to indicate cancellation of OpenID authentication.
+ */
+define('Auth_OpenID_CANCEL', 'cancel');
+
+/**
+ * This is the status code completeAuth returns when the value it
+ * received indicated an invalid login.
+ */
+define('Auth_OpenID_FAILURE', 'failure');
+
+/**
+ * This is the status code completeAuth returns when the
+ * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
+ * identity server sends back a URL to send the user to to complete his
+ * or her login.
+ */
+define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
+
+/**
+ * This is the status code beginAuth returns when the page fetched
+ * from the entered OpenID URL doesn't contain the necessary link tags
+ * to function as an identity page.
+ */
+define('Auth_OpenID_PARSE_ERROR', 'parse error');
+
+/**
+ * An OpenID consumer implementation that performs discovery and does
+ * session management.  See the Consumer.php file documentation for
+ * more information.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Consumer {
+
+    /**
+     * @access private
+     */
+    var $discoverMethod = 'Auth_OpenID_discover';
+
+    /**
+     * @access private
+     */
+    var $session_key_prefix = "_openid_consumer_";
+
+    /**
+     * @access private
+     */
+    var $_token_suffix = "last_token";
+
+    /**
+     * Initialize a Consumer instance.
+     *
+     * You should create a new instance of the Consumer object with
+     * every HTTP request that handles OpenID transactions.
+     *
+     * @param Auth_OpenID_OpenIDStore $store This must be an object
+     * that implements the interface in {@link
+     * Auth_OpenID_OpenIDStore}.  Several concrete implementations are
+     * provided, to cover most common use cases.  For stores backed by
+     * MySQL, PostgreSQL, or SQLite, see the {@link
+     * Auth_OpenID_SQLStore} class and its sublcasses.  For a
+     * filesystem-backed store, see the {@link Auth_OpenID_FileStore}
+     * module.  As a last resort, if it isn't possible for the server
+     * to store state at all, an instance of {@link
+     * Auth_OpenID_DumbStore} can be used.
+     *
+     * @param mixed $session An object which implements the interface
+     * of the {@link Auth_Yadis_PHPSession} class.  Particularly, this
+     * object is expected to have these methods: get($key), set($key),
+     * $value), and del($key).  This defaults to a session object
+     * which wraps PHP's native session machinery.  You should only
+     * need to pass something here if you have your own sessioning
+     * implementation.
+     *
+     * @param str $consumer_cls The name of the class to instantiate
+     * when creating the internal consumer object.  This is used for
+     * testing.
+     */
+    function Auth_OpenID_Consumer($store, $session = null,
+                                  $consumer_cls = null)
+    {
+        if ($session === null) {
+            $session = new Auth_Yadis_PHPSession();
+        }
+
+        $this->session = $session;
+
+        if ($consumer_cls !== null) {
+            $this->consumer = new $consumer_cls($store);
+        } else {
+            $this->consumer = new Auth_OpenID_GenericConsumer($store);
+        }
+
+        $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
+    }
+
+    /**
+     * Used in testing to define the discovery mechanism.
+     *
+     * @access private
+     */
+    function getDiscoveryObject($session, $openid_url,
+                                $session_key_prefix)
+    {
+        return new Auth_Yadis_Discovery($session, $openid_url,
+                                        $session_key_prefix);
+    }
+
+    /**
+     * Start the OpenID authentication process. See steps 1-2 in the
+     * overview at the top of this file.
+     *
+     * @param string $user_url Identity URL given by the user. This
+     * method performs a textual transformation of the URL to try and
+     * make sure it is normalized. For example, a user_url of
+     * example.com will be normalized to http://example.com/
+     * normalizing and resolving any redirects the server might issue.
+     *
+     * @param bool $anonymous True if the OpenID request is to be sent
+     * to the server without any identifier information.  Use this
+     * when you want to transport data but don't want to do OpenID
+     * authentication with identifiers.
+     *
+     * @return Auth_OpenID_AuthRequest $auth_request An object
+     * containing the discovered information will be returned, with a
+     * method for building a redirect URL to the server, as described
+     * in step 3 of the overview. This object may also be used to add
+     * extension arguments to the request, using its 'addExtensionArg'
+     * method.
+     */
+    function begin($user_url, $anonymous=false)
+    {
+        $openid_url = $user_url;
+
+        $disco = $this->getDiscoveryObject($this->session,
+                                           $openid_url,
+                                           $this->session_key_prefix);
+
+        // Set the 'stale' attribute of the manager.  If discovery
+        // fails in a fatal way, the stale flag will cause the manager
+        // to be cleaned up next time discovery is attempted.
+
+        $m = $disco->getManager();
+        $loader = new Auth_Yadis_ManagerLoader();
+
+        if ($m) {
+            if ($m->stale) {
+                $disco->destroyManager();
+            } else {
+                $m->stale = true;
+                $disco->session->set($disco->session_key,
+                                     serialize($loader->toSession($m)));
+            }
+        }
+
+        $endpoint = $disco->getNextService($this->discoverMethod,
+                                           $this->consumer->fetcher);
+
+        // Reset the 'stale' attribute of the manager.
+        $m = $disco->getManager();
+        if ($m) {
+            $m->stale = false;
+            $disco->session->set($disco->session_key,
+                                 serialize($loader->toSession($m)));
+        }
+
+        if ($endpoint === null) {
+            return null;
+        } else {
+            return $this->beginWithoutDiscovery($endpoint,
+                                                $anonymous);
+        }
+    }
+
+    /**
+     * Start OpenID verification without doing OpenID server
+     * discovery. This method is used internally by Consumer.begin
+     * after discovery is performed, and exists to provide an
+     * interface for library users needing to perform their own
+     * discovery.
+     *
+     * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
+     * endpoint descriptor.
+     *
+     * @param bool anonymous Set to true if you want to perform OpenID
+     * without identifiers.
+     *
+     * @return Auth_OpenID_AuthRequest $auth_request An OpenID
+     * authentication request object.
+     */
+    function beginWithoutDiscovery($endpoint, $anonymous=false)
+    {
+        $loader = new Auth_OpenID_ServiceEndpointLoader();
+        $auth_req = $this->consumer->begin($endpoint);
+        $this->session->set($this->_token_key,
+              $loader->toSession($auth_req->endpoint));
+        if (!$auth_req->setAnonymous($anonymous)) {
+            return new Auth_OpenID_FailureResponse(null,
+              "OpenID 1 requests MUST include the identifier " .
+              "in the request.");
+        }
+        return $auth_req;
+    }
+
+    /**
+     * Called to interpret the server's response to an OpenID
+     * request. It is called in step 4 of the flow described in the
+     * consumer overview.
+     *
+     * @param string $current_url The URL used to invoke the application.
+     * Extract the URL from your application's web
+     * request framework and specify it here to have it checked
+     * against the openid.current_url value in the response.  If
+     * the current_url URL check fails, the status of the
+     * completion will be FAILURE.
+     *
+     * @param array $query An array of the query parameters (key =>
+     * value pairs) for this HTTP request.  Defaults to null.  If
+     * null, the GET or POST data are automatically gotten from the
+     * PHP environment.  It is only useful to override $query for
+     * testing.
+     *
+     * @return Auth_OpenID_ConsumerResponse $response A instance of an
+     * Auth_OpenID_ConsumerResponse subclass. The type of response is
+     * indicated by the status attribute, which will be one of
+     * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
+     */
+    function complete($current_url, $query=null)
+    {
+        if ($current_url && !is_string($current_url)) {
+            // This is ugly, but we need to complain loudly when
+            // someone uses the API incorrectly.
+            trigger_error("current_url must be a string; see NEWS file " .
+                          "for upgrading notes.",
+                          E_USER_ERROR);
+        }
+
+        if ($query === null) {
+            $query = Auth_OpenID::getQuery();
+        }
+
+        $loader = new Auth_OpenID_ServiceEndpointLoader();
+        $endpoint_data = $this->session->get($this->_token_key);
+        $endpoint =
+            $loader->fromSession($endpoint_data);
+
+        $message = Auth_OpenID_Message::fromPostArgs($query);
+        $response = $this->consumer->complete($message, $endpoint, 
+                                              $current_url);
+        $this->session->del($this->_token_key);
+
+        if (in_array($response->status, array(Auth_OpenID_SUCCESS,
+                                              Auth_OpenID_CANCEL))) {
+            if ($response->identity_url !== null) {
+                $disco = $this->getDiscoveryObject($this->session,
+                                                   $response->identity_url,
+                                                   $this->session_key_prefix);
+                $disco->cleanup(true);
+            }
+        }
+
+        return $response;
+    }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA1 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+    var $session_type = 'DH-SHA1';
+    var $hash_func = 'Auth_OpenID_SHA1';
+    var $secret_size = 20;
+    var $allowed_assoc_types = array('HMAC-SHA1');
+
+    function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
+    {
+        if ($dh === null) {
+            $dh = new Auth_OpenID_DiffieHellman();
+        }
+
+        $this->dh = $dh;
+    }
+
+    function getRequest()
+    {
+        $math = Auth_OpenID_getMathLib();
+
+        $cpub = $math->longToBase64($this->dh->public);
+
+        $args = array('dh_consumer_public' => $cpub);
+
+        if (!$this->dh->usingDefaultValues()) {
+            $args = array_merge($args, array(
+                'dh_modulus' =>
+                     $math->longToBase64($this->dh->mod),
+                'dh_gen' =>
+                     $math->longToBase64($this->dh->gen)));
+        }
+
+        return $args;
+    }
+
+    function extractSecret($response)
+    {
+        if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+                               'dh_server_public')) {
+            return null;
+        }
+
+        if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+                               'enc_mac_key')) {
+            return null;
+        }
+
+        $math = Auth_OpenID_getMathLib();
+
+        $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
+                                                      'dh_server_public'));
+        $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+                                                       'enc_mac_key'));
+
+        return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
+    }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA256 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
+      Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+    var $session_type = 'DH-SHA256';
+    var $hash_func = 'Auth_OpenID_SHA256';
+    var $secret_size = 32;
+    var $allowed_assoc_types = array('HMAC-SHA256');
+}
+
+/**
+ * A class implementing plaintext consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextConsumerSession {
+    var $session_type = 'no-encryption';
+    var $allowed_assoc_types =  array('HMAC-SHA1', 'HMAC-SHA256');
+
+    function getRequest()
+    {
+        return array();
+    }
+
+    function extractSecret($response)
+    {
+        if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) {
+            return null;
+        }
+
+        return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+                                               'mac_key'));
+    }
+}
+
+/**
+ * Returns available session types.
+ */
+function Auth_OpenID_getAvailableSessionTypes()
+{
+    $types = array(
+      'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
+      'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
+      'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
+
+    return $types;
+}
+
+/**
+ * This class is the interface to the OpenID consumer logic.
+ * Instances of it maintain no per-request state, so they can be
+ * reused (or even used by multiple threads concurrently) as needed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_GenericConsumer {
+    /**
+     * @access private
+     */
+    var $discoverMethod = 'Auth_OpenID_discover';
+
+    /**
+     * This consumer's store object.
+     */
+    var $store;
+
+    /**
+     * @access private
+     */
+    var $_use_assocs;
+
+    /**
+     * @access private
+     */
+    var $openid1_nonce_query_arg_name = 'janrain_nonce';
+
+    /**
+     * Another query parameter that gets added to the return_to for
+     * OpenID 1; if the user's session state is lost, use this claimed
+     * identifier to do discovery when verifying the response.
+     */
+    var $openid1_return_to_identifier_name = 'openid1_claimed_id';
+
+    /**
+     * This method initializes a new {@link Auth_OpenID_Consumer}
+     * instance to access the library.
+     *
+     * @param Auth_OpenID_OpenIDStore $store This must be an object
+     * that implements the interface in {@link Auth_OpenID_OpenIDStore}.
+     * Several concrete implementations are provided, to cover most common use
+     * cases.  For stores backed by MySQL, PostgreSQL, or SQLite, see
+     * the {@link Auth_OpenID_SQLStore} class and its sublcasses.  For a
+     * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
+     * As a last resort, if it isn't possible for the server to store
+     * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
+     *
+     * @param bool $immediate This is an optional boolean value.  It
+     * controls whether the library uses immediate mode, as explained
+     * in the module description.  The default value is False, which
+     * disables immediate mode.
+     */
+    function Auth_OpenID_GenericConsumer($store)
+    {
+        $this->store = $store;
+        $this->negotiator = Auth_OpenID_getDefaultNegotiator();
+        $this->_use_assocs = (is_null($this->store) ? false : true);
+
+        $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+        $this->session_types = Auth_OpenID_getAvailableSessionTypes();
+    }
+
+    /**
+     * Called to begin OpenID authentication using the specified
+     * {@link Auth_OpenID_ServiceEndpoint}.
+     *
+     * @access private
+     */
+    function begin($service_endpoint)
+    {
+        $assoc = $this->_getAssociation($service_endpoint);
+        $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
+        $r->return_to_args[$this->openid1_nonce_query_arg_name] =
+            Auth_OpenID_mkNonce();
+
+        if ($r->message->isOpenID1()) {
+            $r->return_to_args[$this->openid1_return_to_identifier_name] =
+                $r->endpoint->claimed_id;
+        }
+
+        return $r;
+    }
+
+    /**
+     * Given an {@link Auth_OpenID_Message}, {@link
+     * Auth_OpenID_ServiceEndpoint} and optional return_to URL,
+     * complete OpenID authentication.
+     *
+     * @access private
+     */
+    function complete($message, $endpoint, $return_to)
+    {
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+                                 '<no mode set>');
+
+        $mode_methods = array(
+                              'cancel' => '_complete_cancel',
+                              'error' => '_complete_error',
+                              'setup_needed' => '_complete_setup_needed',
+                              'id_res' => '_complete_id_res',
+                              );
+
+        $method = Auth_OpenID::arrayGet($mode_methods, $mode,
+                                        '_completeInvalid');
+
+        return call_user_func_array(array($this, $method),
+                                    array($message, &$endpoint, $return_to));
+    }
+
+    /**
+     * @access private
+     */
+    function _completeInvalid($message, $endpoint, $unused)
+    {
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+                                 '<No mode set>');
+
+        return new Auth_OpenID_FailureResponse($endpoint,
+                    sprintf("Invalid openid.mode '%s'", $mode));
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_cancel($message, $endpoint, $unused)
+    {
+        return new Auth_OpenID_CancelResponse($endpoint);
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_error($message, $endpoint, $unused)
+    {
+        $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
+        $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
+        $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
+
+        return new Auth_OpenID_FailureResponse($endpoint, $error,
+                                               $contact, $reference);
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_setup_needed($message, $endpoint, $unused)
+    {
+        if (!$message->isOpenID2()) {
+            return $this->_completeInvalid($message, $endpoint);
+        }
+
+        $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                           'user_setup_url');
+        return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_id_res($message, $endpoint, $return_to)
+    {
+        $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+                                           'user_setup_url');
+
+        if ($this->_checkSetupNeeded($message)) {
+            return new Auth_OpenID_SetupNeededResponse(
+                $endpoint, $user_setup_url);
+        } else {
+            return $this->_doIdRes($message, $endpoint, $return_to);
+        }
+    }
+
+    /**
+     * @access private
+     */
+    function _checkSetupNeeded($message)
+    {
+        // In OpenID 1, we check to see if this is a cancel from
+        // immediate mode by the presence of the user_setup_url
+        // parameter.
+        if ($message->isOpenID1()) {
+            $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+                                               'user_setup_url');
+            if ($user_setup_url !== null) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @access private
+     */
+    function _doIdRes($message, $endpoint, $return_to)
+    {
+        // Checks for presence of appropriate fields (and checks
+        // signed list fields)
+        $result = $this->_idResCheckForFields($message);
+
+        if (Auth_OpenID::isFailure($result)) {
+            return $result;
+        }
+
+        if (!$this->_checkReturnTo($message, $return_to)) {
+            return new Auth_OpenID_FailureResponse(null,
+            sprintf("return_to does not match return URL. Expected %s, got %s",
+                    $return_to,
+                    $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
+        }
+
+        // Verify discovery information:
+        $result = $this->_verifyDiscoveryResults($message, $endpoint);
+
+        if (Auth_OpenID::isFailure($result)) {
+            return $result;
+        }
+
+        $endpoint = $result;
+
+        $result = $this->_idResCheckSignature($message,
+                                              $endpoint->server_url);
+
+        if (Auth_OpenID::isFailure($result)) {
+            return $result;
+        }
+
+        $result = $this->_idResCheckNonce($message, $endpoint);
+
+        if (Auth_OpenID::isFailure($result)) {
+            return $result;
+        }
+
+        $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
+                                            Auth_OpenID_NO_DEFAULT);
+        if (Auth_OpenID::isFailure($signed_list_str)) {
+            return $signed_list_str;
+        }
+        $signed_list = explode(',', $signed_list_str);
+
+        $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
+
+        return new Auth_OpenID_SuccessResponse($endpoint, $message,
+                                               $signed_fields);
+
+    }
+
+    /**
+     * @access private
+     */
+    function _checkReturnTo($message, $return_to)
+    {
+        // Check an OpenID message and its openid.return_to value
+        // against a return_to URL from an application.  Return True
+        // on success, False on failure.
+
+        // Check the openid.return_to args against args in the
+        // original message.
+        $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
+                                           $message->toPostArgs());
+        if (Auth_OpenID::isFailure($result)) {
+            return false;
+        }
+
+        // Check the return_to base URL against the one in the
+        // message.
+        $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+                                          'return_to');
+        if (Auth_OpenID::isFailure($return_to)) {
+            // XXX log me
+            return false;
+        }
+
+        $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
+        $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
+
+        // If port is absent from both, add it so it's equal in the
+        // check below.
+        if ((!array_key_exists('port', $return_to_parts)) &&
+            (!array_key_exists('port', $msg_return_to_parts))) {
+            $return_to_parts['port'] = null;
+            $msg_return_to_parts['port'] = null;
+        }
+
+        // If path is absent from both, add it so it's equal in the
+        // check below.
+        if ((!array_key_exists('path', $return_to_parts)) &&
+            (!array_key_exists('path', $msg_return_to_parts))) {
+            $return_to_parts['path'] = null;
+            $msg_return_to_parts['path'] = null;
+        }
+
+        // The URL scheme, authority, and path MUST be the same
+        // between the two URLs.
+        foreach (array('scheme', 'host', 'port', 'path') as $component) {
+            // If the url component is absent in either URL, fail.
+            // There should always be a scheme, host, port, and path.
+            if (!array_key_exists($component, $return_to_parts)) {
+                return false;
+            }
+
+            if (!array_key_exists($component, $msg_return_to_parts)) {
+                return false;
+            }
+
+            if (Auth_OpenID::arrayGet($return_to_parts, $component) !==
+                Auth_OpenID::arrayGet($msg_return_to_parts, $component)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyReturnToArgs($query)
+    {
+        // Verify that the arguments in the return_to URL are present in this
+        // response.
+
+        $message = Auth_OpenID_Message::fromPostArgs($query);
+        $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
+
+        if (Auth_OpenID::isFailure($return_to)) {
+            return $return_to;
+        }
+        // XXX: this should be checked by _idResCheckForFields
+        if (!$return_to) {
+            return new Auth_OpenID_FailureResponse(null,
+                           "Response has no return_to");
+        }
+
+        $parsed_url = parse_url($return_to);
+
+        $q = array();
+        if (array_key_exists('query', $parsed_url)) {
+            $rt_query = $parsed_url['query'];
+            $q = Auth_OpenID::parse_str($rt_query);
+        }
+
+        foreach ($q as $rt_key => $rt_value) {
+            if (!array_key_exists($rt_key, $query)) {
+                return new Auth_OpenID_FailureResponse(null,
+                  sprintf("return_to parameter %s absent from query", $rt_key));
+            } else {
+                $value = $query[$rt_key];
+                if ($rt_value != $value) {
+                    return new Auth_OpenID_FailureResponse(null,
+                      sprintf("parameter %s value %s does not match " .
+                              "return_to value %s", $rt_key,
+                              $value, $rt_value));
+                }
+            }
+        }
+
+        // Make sure all non-OpenID arguments in the response are also
+        // in the signed return_to.
+        $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
+        foreach ($bare_args as $key => $value) {
+            if (Auth_OpenID::arrayGet($q, $key) != $value) {
+                return new Auth_OpenID_FailureResponse(null,
+                  sprintf("Parameter %s = %s not in return_to URL",
+                          $key, $value));
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @access private
+     */
+    function _idResCheckSignature($message, $server_url)
+    {
+        $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+                                         'assoc_handle');
+        if (Auth_OpenID::isFailure($assoc_handle)) {
+            return $assoc_handle;
+        }
+
+        $assoc = $this->store->getAssociation($server_url, $assoc_handle);
+
+        if ($assoc) {
+            if ($assoc->getExpiresIn() <= 0) {
+                // XXX: It might be a good idea sometimes to re-start
+                // the authentication with a new association. Doing it
+                // automatically opens the possibility for
+                // denial-of-service by a server that just returns
+                // expired associations (or really short-lived
+                // associations)
+                return new Auth_OpenID_FailureResponse(null,
+                             'Association with ' . $server_url . ' expired');
+            }
+
+            if (!$assoc->checkMessageSignature($message)) {
+                // If we get a "bad signature" here, it means that the association
+                // is unrecoverabley corrupted in some way. Any futher attempts
+                // to login with this association is likely to fail. Drop it.
+                $this->store->removeAssociation($server_url, $assoc_handle);
+                return new Auth_OpenID_FailureResponse(null,
+                                                       "Bad signature");
+            }
+        } else {
+            // It's not an association we know about.  Stateless mode
+            // is our only possible path for recovery.  XXX - async
+            // framework will not want to block on this call to
+            // _checkAuth.
+            if (!$this->_checkAuth($message, $server_url)) {
+                return new Auth_OpenID_FailureResponse(null,
+                             "Server denied check_authentication");
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoveryResults($message, $endpoint=null)
+    {
+        if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) {
+            return $this->_verifyDiscoveryResultsOpenID2($message,
+                                                         $endpoint);
+        } else {
+            return $this->_verifyDiscoveryResultsOpenID1($message,
+                                                         $endpoint);
+        }
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoveryResultsOpenID1($message, $endpoint)
+    {
+        $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
+                                $this->openid1_return_to_identifier_name);
+
+        if (($endpoint === null) && ($claimed_id === null)) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              'When using OpenID 1, the claimed ID must be supplied, ' .
+              'either by passing it through as a return_to parameter ' .
+              'or by using a session, and supplied to the GenericConsumer ' .
+              'as the argument to complete()');
+        } else if (($endpoint !== null) && ($claimed_id === null)) {
+            $claimed_id = $endpoint->claimed_id;
+        }
+
+        $to_match = new Auth_OpenID_ServiceEndpoint();
+        $to_match->type_uris = array(Auth_OpenID_TYPE_1_1);
+        $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS,
+                                               'identity');
+
+        // Restore delegate information from the initiation phase
+        $to_match->claimed_id = $claimed_id;
+
+        if ($to_match->local_id === null) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+                         "Missing required field openid.identity");
+        }
+
+        $to_match_1_0 = $to_match->copy();
+        $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
+
+        if ($endpoint !== null) {
+            $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+            if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
+                $result = $this->_verifyDiscoverySingle($endpoint,
+                                                        $to_match_1_0);
+            }
+
+            if (Auth_OpenID::isFailure($result)) {
+                // oidutil.log("Error attempting to use stored
+                //             discovery information: " + str(e))
+                //             oidutil.log("Attempting discovery to
+                //             verify endpoint")
+            } else {
+                return $endpoint;
+            }
+        }
+
+        // Endpoint is either bad (failed verification) or None
+        return $this->_discoverAndVerify($to_match->claimed_id,
+                                         array($to_match, $to_match_1_0));
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoverySingle($endpoint, $to_match)
+    {
+        // Every type URI that's in the to_match endpoint has to be
+        // present in the discovered endpoint.
+        foreach ($to_match->type_uris as $type_uri) {
+            if (!$endpoint->usesExtension($type_uri)) {
+                return new Auth_OpenID_TypeURIMismatch($endpoint,
+                             "Required type ".$type_uri." not present");
+            }
+        }
+
+        // Fragments do not influence discovery, so we can't compare a
+        // claimed identifier with a fragment to discovered
+        // information.
+        list($defragged_claimed_id, $_) =
+            Auth_OpenID::urldefrag($to_match->claimed_id);
+
+        if ($defragged_claimed_id != $endpoint->claimed_id) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              sprintf('Claimed ID does not match (different subjects!), ' .
+                      'Expected %s, got %s', $defragged_claimed_id,
+                      $endpoint->claimed_id));
+        }
+
+        if ($to_match->getLocalID() != $endpoint->getLocalID()) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              sprintf('local_id mismatch. Expected %s, got %s',
+                      $to_match->getLocalID(), $endpoint->getLocalID()));
+        }
+
+        // If the server URL is None, this must be an OpenID 1
+        // response, because op_endpoint is a required parameter in
+        // OpenID 2. In that case, we don't actually care what the
+        // discovered server_url is, because signature checking or
+        // check_auth should take care of that check for us.
+        if ($to_match->server_url === null) {
+            if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) {
+                return new Auth_OpenID_FailureResponse($endpoint,
+                             "Preferred namespace mismatch (bug)");
+            }
+        } else if ($to_match->server_url != $endpoint->server_url) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              sprintf('OP Endpoint mismatch. Expected %s, got %s',
+                      $to_match->server_url, $endpoint->server_url));
+        }
+
+        return null;
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoveryResultsOpenID2($message, $endpoint)
+    {
+        $to_match = new Auth_OpenID_ServiceEndpoint();
+        $to_match->type_uris = array(Auth_OpenID_TYPE_2_0);
+        $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                                 'claimed_id');
+
+        $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                                'identity');
+
+        $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                                 'op_endpoint');
+
+        if ($to_match->server_url === null) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+                         "OP Endpoint URL missing");
+        }
+
+        // claimed_id and identifier must both be present or both be
+        // absent
+        if (($to_match->claimed_id === null) &&
+            ($to_match->local_id !== null)) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              'openid.identity is present without openid.claimed_id');
+        }
+
+        if (($to_match->claimed_id !== null) &&
+            ($to_match->local_id === null)) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+              'openid.claimed_id is present without openid.identity');
+        }
+
+        if ($to_match->claimed_id === null) {
+            // This is a response without identifiers, so there's
+            // really no checking that we can do, so return an
+            // endpoint that's for the specified `openid.op_endpoint'
+            return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
+                                                $to_match->server_url);
+        }
+
+        if (!$endpoint) {
+            // The claimed ID doesn't match, so we have to do
+            // discovery again. This covers not using sessions, OP
+            // identifier endpoints and responses that didn't match
+            // the original request.
+            // oidutil.log('No pre-discovered information supplied.')
+            return $this->_discoverAndVerify($to_match->claimed_id,
+                                             array($to_match));
+        } else {
+
+            // The claimed ID matches, so we use the endpoint that we
+            // discovered in initiation. This should be the most
+            // common case.
+            $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+            if (Auth_OpenID::isFailure($result)) {
+                $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
+                                                      array($to_match));
+                if (Auth_OpenID::isFailure($endpoint)) {
+                    return $endpoint;
+                }
+            }
+        }
+
+        // The endpoint we return should have the claimed ID from the
+        // message we just verified, fragment and all.
+        if ($endpoint->claimed_id != $to_match->claimed_id) {
+            $endpoint->claimed_id = $to_match->claimed_id;
+        }
+
+        return $endpoint;
+    }
+
+    /**
+     * @access private
+     */
+    function _discoverAndVerify($claimed_id, $to_match_endpoints)
+    {
+        // oidutil.log('Performing discovery on %s' % (claimed_id,))
+        list($unused, $services) = call_user_func($this->discoverMethod,
+                                                  $claimed_id,
+                                                  $this->fetcher);
+
+        if (!$services) {
+            return new Auth_OpenID_FailureResponse(null,
+              sprintf("No OpenID information found at %s",
+                      $claimed_id));
+        }
+
+        return $this->_verifyDiscoveryServices($claimed_id, $services,
+                                               $to_match_endpoints);
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoveryServices($claimed_id, 
+                                      $services, $to_match_endpoints)
+    {
+        // Search the services resulting from discovery to find one
+        // that matches the information from the assertion
+
+        foreach ($services as $endpoint) {
+            foreach ($to_match_endpoints as $to_match_endpoint) {
+                $result = $this->_verifyDiscoverySingle($endpoint, 
+                                                        $to_match_endpoint);
+
+                if (!Auth_OpenID::isFailure($result)) {
+                    // It matches, so discover verification has
+                    // succeeded. Return this endpoint.
+                    return $endpoint;
+                }
+            }
+        }
+
+        return new Auth_OpenID_FailureResponse(null,
+          sprintf('No matching endpoint found after discovering %s: %s',
+                  $claimed_id, $result->message));
+    }
+
+    /**
+     * Extract the nonce from an OpenID 1 response.  Return the nonce
+     * from the BARE_NS since we independently check the return_to
+     * arguments are the same as those in the response message.
+     *
+     * See the openid1_nonce_query_arg_name class variable
+     *
+     * @returns $nonce The nonce as a string or null
+     *
+     * @access private
+     */
+    function _idResGetNonceOpenID1($message, $endpoint)
+    {
+        return $message->getArg(Auth_OpenID_BARE_NS,
+                                $this->openid1_nonce_query_arg_name);
+    }
+
+    /**
+     * @access private
+     */
+    function _idResCheckNonce($message, $endpoint)
+    {
+        if ($message->isOpenID1()) {
+            // This indicates that the nonce was generated by the consumer
+            $nonce = $this->_idResGetNonceOpenID1($message, $endpoint);
+            $server_url = '';
+        } else {
+            $nonce = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                      'response_nonce');
+
+            $server_url = $endpoint->server_url;
+        }
+
+        if ($nonce === null) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+                                     "Nonce missing from response");
+        }
+
+        $parts = Auth_OpenID_splitNonce($nonce);
+
+        if ($parts === null) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+                                     "Malformed nonce in response");
+        }
+
+        list($timestamp, $salt) = $parts;
+
+        if (!$this->store->useNonce($server_url, $timestamp, $salt)) {
+            return new Auth_OpenID_FailureResponse($endpoint,
+                         "Nonce already used or out of range");
+        }
+
+        return null;
+    }
+
+    /**
+     * @access private
+     */
+    function _idResCheckForFields($message)
+    {
+        $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
+        $basic_sig_fields = array('return_to', 'identity');
+
+        $require_fields = array(
+            Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
+                                                  array('op_endpoint')),
+
+            Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
+                                                  array('identity'))
+            );
+
+        $require_sigs = array(
+            Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
+                                                  array('response_nonce',
+                                                        'claimed_id',
+                                                        'assoc_handle',
+                                                        'op_endpoint')),
+            Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
+                                                  array('nonce'))
+            );
+
+        foreach ($require_fields[$message->getOpenIDNamespace()] as $field) {
+            if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
+                return new Auth_OpenID_FailureResponse(null,
+                             "Missing required field '".$field."'");
+            }
+        }
+
+        $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
+                                            'signed',
+                                            Auth_OpenID_NO_DEFAULT);
+        if (Auth_OpenID::isFailure($signed_list_str)) {
+            return $signed_list_str;
+        }
+        $signed_list = explode(',', $signed_list_str);
+
+        foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
+            // Field is present and not in signed list
+            if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
+                (!in_array($field, $signed_list))) {
+                return new Auth_OpenID_FailureResponse(null,
+                             "'".$field."' not signed");
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @access private
+     */
+    function _checkAuth($message, $server_url)
+    {
+        $request = $this->_createCheckAuthRequest($message);
+        if ($request === null) {
+            return false;
+        }
+
+        $resp_message = $this->_makeKVPost($request, $server_url);
+        if (($resp_message === null) ||
+            (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) {
+            return false;
+        }
+
+        return $this->_processCheckAuthResponse($resp_message, $server_url);
+    }
+
+    /**
+     * @access private
+     */
+    function _createCheckAuthRequest($message)
+    {
+        $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+        if ($signed) {
+            foreach (explode(',', $signed) as $k) {
+                $value = $message->getAliasedArg($k);
+                if ($value === null) {
+                    return null;
+                }
+            }
+        }
+        $ca_message = $message->copy();
+        $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode', 
+                            'check_authentication');
+        return $ca_message;
+    }
+
+    /**
+     * @access private
+     */
+    function _processCheckAuthResponse($response, $server_url)
+    {
+        $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid',
+                                      'false');
+
+        $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS,
+                                               'invalidate_handle');
+
+        if ($invalidate_handle !== null) {
+            $this->store->removeAssociation($server_url,
+                                            $invalidate_handle);
+        }
+
+        if ($is_valid == 'true') {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Adapt a POST response to a Message.
+     *
+     * @param $response Result of a POST to an OpenID endpoint.
+     *
+     * @access private
+     */
+    static function _httpResponseToMessage($response, $server_url)
+    {
+        // Should this function be named Message.fromHTTPResponse instead?
+        $response_message = Auth_OpenID_Message::fromKVForm($response->body);
+
+        if ($response->status == 400) {
+            return Auth_OpenID_ServerErrorContainer::fromMessage(
+                        $response_message);
+        } else if ($response->status != 200 and $response->status != 206) {
+            return null;
+        }
+
+        return $response_message;
+    }
+
+    /**
+     * @access private
+     */
+    function _makeKVPost($message, $server_url)
+    {
+        $body = $message->toURLEncoded();
+        $resp = $this->fetcher->post($server_url, $body);
+
+        if ($resp === null) {
+            return null;
+        }
+
+        return $this->_httpResponseToMessage($resp, $server_url);
+    }
+
+    /**
+     * @access private
+     */
+    function _getAssociation($endpoint)
+    {
+        if (!$this->_use_assocs) {
+            return null;
+        }
+
+        $assoc = $this->store->getAssociation($endpoint->server_url);
+
+        if (($assoc === null) ||
+            ($assoc->getExpiresIn() <= 0)) {
+
+            $assoc = $this->_negotiateAssociation($endpoint);
+
+            if ($assoc !== null) {
+                $this->store->storeAssociation($endpoint->server_url,
+                                               $assoc);
+            }
+        }
+
+        return $assoc;
+    }
+
+    /**
+     * Handle ServerErrors resulting from association requests.
+     *
+     * @return $result If server replied with an C{unsupported-type}
+     * error, return a tuple of supported C{association_type},
+     * C{session_type}.  Otherwise logs the error and returns null.
+     *
+     * @access private
+     */
+    function _extractSupportedAssociationType($server_error, $endpoint,
+                                              $assoc_type)
+    {
+        // Any error message whose code is not 'unsupported-type'
+        // should be considered a total failure.
+        if (($server_error->error_code != 'unsupported-type') ||
+            ($server_error->message->isOpenID1())) {
+            return null;
+        }
+
+        // The server didn't like the association/session type that we
+        // sent, and it sent us back a message that might tell us how
+        // to handle it.
+
+        // Extract the session_type and assoc_type from the error
+        // message
+        $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+                                                     'assoc_type');
+
+        $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+                                                       'session_type');
+
+        if (($assoc_type === null) || ($session_type === null)) {
+            return null;
+        } else if (!$this->negotiator->isAllowed($assoc_type,
+                                                 $session_type)) {
+            return null;
+        } else {
+          return array($assoc_type, $session_type);
+        }
+    }
+
+    /**
+     * @access private
+     */
+    function _negotiateAssociation($endpoint)
+    {
+        // Get our preferred session/association type from the negotiatior.
+        list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
+
+        $assoc = $this->_requestAssociation(
+                           $endpoint, $assoc_type, $session_type);
+
+        if (Auth_OpenID::isFailure($assoc)) {
+            return null;
+        }
+
+        if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+            $why = $assoc;
+
+            $supportedTypes = $this->_extractSupportedAssociationType(
+                                     $why, $endpoint, $assoc_type);
+
+            if ($supportedTypes !== null) {
+                list($assoc_type, $session_type) = $supportedTypes;
+
+                // Attempt to create an association from the assoc_type
+                // and session_type that the server told us it
+                // supported.
+                $assoc = $this->_requestAssociation(
+                                   $endpoint, $assoc_type, $session_type);
+
+                if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+                    // Do not keep trying, since it rejected the
+                    // association type that it told us to use.
+                    // oidutil.log('Server %s refused its suggested association
+                    //             'type: session_type=%s, assoc_type=%s'
+                    //             % (endpoint.server_url, session_type,
+                    //                assoc_type))
+                    return null;
+                } else {
+                    return $assoc;
+                }
+            } else {
+                return null;
+            }
+        } else {
+            return $assoc;
+        }
+    }
+
+    /**
+     * @access private
+     */
+    function _requestAssociation($endpoint, $assoc_type, $session_type)
+    {
+        list($assoc_session, $args) = $this->_createAssociateRequest(
+                                      $endpoint, $assoc_type, $session_type);
+
+        $response_message = $this->_makeKVPost($args, $endpoint->server_url);
+
+        if ($response_message === null) {
+            // oidutil.log('openid.associate request failed: %s' % (why[0],))
+            return null;
+        } else if (is_a($response_message,
+                        'Auth_OpenID_ServerErrorContainer')) {
+            return $response_message;
+        }
+
+        return $this->_extractAssociation($response_message, $assoc_session);
+    }
+
+    /**
+     * @access private
+     */
+    function _extractAssociation($assoc_response, $assoc_session)
+    {
+        // Extract the common fields from the response, raising an
+        // exception if they are not found
+        $assoc_type = $assoc_response->getArg(
+                         Auth_OpenID_OPENID_NS, 'assoc_type',
+                         Auth_OpenID_NO_DEFAULT);
+
+        if (Auth_OpenID::isFailure($assoc_type)) {
+            return $assoc_type;
+        }
+
+        $assoc_handle = $assoc_response->getArg(
+                           Auth_OpenID_OPENID_NS, 'assoc_handle',
+                           Auth_OpenID_NO_DEFAULT);
+
+        if (Auth_OpenID::isFailure($assoc_handle)) {
+            return $assoc_handle;
+        }
+
+        // expires_in is a base-10 string. The Python parsing will
+        // accept literals that have whitespace around them and will
+        // accept negative values. Neither of these are really in-spec,
+        // but we think it's OK to accept them.
+        $expires_in_str = $assoc_response->getArg(
+                             Auth_OpenID_OPENID_NS, 'expires_in',
+                             Auth_OpenID_NO_DEFAULT);
+
+        if (Auth_OpenID::isFailure($expires_in_str)) {
+            return $expires_in_str;
+        }
+
+        $expires_in = Auth_OpenID::intval($expires_in_str);
+        if ($expires_in === false) {
+            
+            $err = sprintf("Could not parse expires_in from association ".
+                           "response %s", print_r($assoc_response, true));
+            return new Auth_OpenID_FailureResponse(null, $err);
+        }
+
+        // OpenID 1 has funny association session behaviour.
+        if ($assoc_response->isOpenID1()) {
+            $session_type = $this->_getOpenID1SessionType($assoc_response);
+        } else {
+            $session_type = $assoc_response->getArg(
+                               Auth_OpenID_OPENID2_NS, 'session_type',
+                               Auth_OpenID_NO_DEFAULT);
+
+            if (Auth_OpenID::isFailure($session_type)) {
+                return $session_type;
+            }
+        }
+
+        // Session type mismatch
+        if ($assoc_session->session_type != $session_type) {
+            if ($assoc_response->isOpenID1() &&
+                ($session_type == 'no-encryption')) {
+                // In OpenID 1, any association request can result in
+                // a 'no-encryption' association response. Setting
+                // assoc_session to a new no-encryption session should
+                // make the rest of this function work properly for
+                // that case.
+                $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+            } else {
+                // Any other mismatch, regardless of protocol version
+                // results in the failure of the association session
+                // altogether.
+                return null;
+            }
+        }
+
+        // Make sure assoc_type is valid for session_type
+        if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
+            return null;
+        }
+
+        // Delegate to the association session to extract the secret
+        // from the response, however is appropriate for that session
+        // type.
+        $secret = $assoc_session->extractSecret($assoc_response);
+
+        if ($secret === null) {
+            return null;
+        }
+
+        return Auth_OpenID_Association::fromExpiresIn(
+                 $expires_in, $assoc_handle, $secret, $assoc_type);
+    }
+
+    /**
+     * @access private
+     */
+    function _createAssociateRequest($endpoint, $assoc_type, $session_type)
+    {
+        if (array_key_exists($session_type, $this->session_types)) {
+            $session_type_class = $this->session_types[$session_type];
+
+            if (is_callable($session_type_class)) {
+                $assoc_session = $session_type_class();
+            } else {
+                $assoc_session = new $session_type_class();
+            }
+        } else {
+            return null;
+        }
+
+        $args = array(
+            'mode' => 'associate',
+            'assoc_type' => $assoc_type);
+
+        if (!$endpoint->compatibilityMode()) {
+            $args['ns'] = Auth_OpenID_OPENID2_NS;
+        }
+
+        // Leave out the session type if we're in compatibility mode
+        // *and* it's no-encryption.
+        if ((!$endpoint->compatibilityMode()) ||
+            ($assoc_session->session_type != 'no-encryption')) {
+            $args['session_type'] = $assoc_session->session_type;
+        }
+
+        $args = array_merge($args, $assoc_session->getRequest());
+        $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+        return array($assoc_session, $message);
+    }
+
+    /**
+     * Given an association response message, extract the OpenID 1.X
+     * session type.
+     *
+     * This function mostly takes care of the 'no-encryption' default
+     * behavior in OpenID 1.
+     *
+     * If the association type is plain-text, this function will
+     * return 'no-encryption'
+     *
+     * @access private
+     * @return $typ The association type for this message
+     */
+    function _getOpenID1SessionType($assoc_response)
+    {
+        // If it's an OpenID 1 message, allow session_type to default
+        // to None (which signifies "no-encryption")
+        $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
+                                                'session_type');
+
+        // Handle the differences between no-encryption association
+        // respones in OpenID 1 and 2:
+
+        // no-encryption is not really a valid session type for OpenID
+        // 1, but we'll accept it anyway, while issuing a warning.
+        if ($session_type == 'no-encryption') {
+            // oidutil.log('WARNING: OpenID server sent "no-encryption"'
+            //             'for OpenID 1.X')
+        } else if (($session_type == '') || ($session_type === null)) {
+            // Missing or empty session type is the way to flag a
+            // 'no-encryption' response. Change the session type to
+            // 'no-encryption' so that it can be handled in the same
+            // way as OpenID 2 'no-encryption' respones.
+            $session_type = 'no-encryption';
+        }
+
+        return $session_type;
+    }
+}
+
+/**
+ * This class represents an authentication request from a consumer to
+ * an OpenID server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AuthRequest {
+
+    /**
+     * Initialize an authentication request with the specified token,
+     * association, and endpoint.
+     *
+     * Users of this library should not create instances of this
+     * class.  Instances of this class are created by the library when
+     * needed.
+     */
+    function Auth_OpenID_AuthRequest($endpoint, $assoc)
+    {
+        $this->assoc = $assoc;
+        $this->endpoint = $endpoint;
+        $this->return_to_args = array();
+        $this->message = new Auth_OpenID_Message(
+            $endpoint->preferredNamespace());
+        $this->_anonymous = false;
+    }
+
+    /**
+     * Add an extension to this checkid request.
+     *
+     * $extension_request: An object that implements the extension
+     * request interface for adding arguments to an OpenID message.
+     */
+    function addExtension($extension_request)
+    {
+        $extension_request->toMessage($this->message);
+    }
+
+    /**
+     * Add an extension argument to this OpenID authentication
+     * request.
+     *
+     * Use caution when adding arguments, because they will be
+     * URL-escaped and appended to the redirect URL, which can easily
+     * get quite long.
+     *
+     * @param string $namespace The namespace for the extension. For
+     * example, the simple registration extension uses the namespace
+     * 'sreg'.
+     *
+     * @param string $key The key within the extension namespace. For
+     * example, the nickname field in the simple registration
+     * extension's key is 'nickname'.
+     *
+     * @param string $value The value to provide to the server for
+     * this argument.
+     */
+    function addExtensionArg($namespace, $key, $value)
+    {
+        return $this->message->setArg($namespace, $key, $value);
+    }
+
+    /**
+     * Set whether this request should be made anonymously. If a
+     * request is anonymous, the identifier will not be sent in the
+     * request. This is only useful if you are making another kind of
+     * request with an extension in this request.
+     *
+     * Anonymous requests are not allowed when the request is made
+     * with OpenID 1.
+     */
+    function setAnonymous($is_anonymous)
+    {
+        if ($is_anonymous && $this->message->isOpenID1()) {
+            return false;
+        } else {
+            $this->_anonymous = $is_anonymous;
+            return true;
+        }
+    }
+
+    /**
+     * Produce a {@link Auth_OpenID_Message} representing this
+     * request.
+     *
+     * @param string $realm The URL (or URL pattern) that identifies
+     * your web site to the user when she is authorizing it.
+     *
+     * @param string $return_to The URL that the OpenID provider will
+     * send the user back to after attempting to verify her identity.
+     *
+     * Not specifying a return_to URL means that the user will not be
+     * returned to the site issuing the request upon its completion.
+     *
+     * @param bool $immediate If true, the OpenID provider is to send
+     * back a response immediately, useful for behind-the-scenes
+     * authentication attempts.  Otherwise the OpenID provider may
+     * engage the user before providing a response.  This is the
+     * default case, as the user may need to provide credentials or
+     * approve the request before a positive response can be sent.
+     */
+    function getMessage($realm, $return_to=null, $immediate=false)
+    {
+        if ($return_to) {
+            $return_to = Auth_OpenID::appendArgs($return_to,
+                                                 $this->return_to_args);
+        } else if ($immediate) {
+            // raise ValueError(
+            //     '"return_to" is mandatory when
+            //using "checkid_immediate"')
+            return new Auth_OpenID_FailureResponse(null,
+              "'return_to' is mandatory when using checkid_immediate");
+        } else if ($this->message->isOpenID1()) {
+            // raise ValueError('"return_to" is
+            // mandatory for OpenID 1 requests')
+            return new Auth_OpenID_FailureResponse(null,
+              "'return_to' is mandatory for OpenID 1 requests");
+        } else if ($this->return_to_args) {
+            // raise ValueError('extra "return_to" arguments
+            // were specified, but no return_to was specified')
+            return new Auth_OpenID_FailureResponse(null,
+              "extra 'return_to' arguments where specified, " .
+              "but no return_to was specified");
+        }
+
+        if ($immediate) {
+            $mode = 'checkid_immediate';
+        } else {
+            $mode = 'checkid_setup';
+        }
+
+        $message = $this->message->copy();
+        if ($message->isOpenID1()) {
+            $realm_key = 'trust_root';
+        } else {
+            $realm_key = 'realm';
+        }
+
+        $message->updateArgs(Auth_OpenID_OPENID_NS,
+                             array(
+                                   $realm_key => $realm,
+                                   'mode' => $mode,
+                                   'return_to' => $return_to));
+
+        if (!$this->_anonymous) {
+            if ($this->endpoint->isOPIdentifier()) {
+                // This will never happen when we're in compatibility
+                // mode, as long as isOPIdentifier() returns False
+                // whenever preferredNamespace() returns OPENID1_NS.
+                $claimed_id = $request_identity =
+                    Auth_OpenID_IDENTIFIER_SELECT;
+            } else {
+                $request_identity = $this->endpoint->getLocalID();
+                $claimed_id = $this->endpoint->claimed_id;
+            }
+
+            // This is true for both OpenID 1 and 2
+            $message->setArg(Auth_OpenID_OPENID_NS, 'identity',
+                             $request_identity);
+
+            if ($message->isOpenID2()) {
+                $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id',
+                                 $claimed_id);
+            }
+        }
+
+        if ($this->assoc) {
+            $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
+                             $this->assoc->handle);
+        }
+
+        return $message;
+    }
+
+    function redirectURL($realm, $return_to = null,
+                         $immediate = false)
+    {
+        $message = $this->getMessage($realm, $return_to, $immediate);
+
+        if (Auth_OpenID::isFailure($message)) {
+            return $message;
+        }
+
+        return $message->toURL($this->endpoint->server_url);
+    }
+
+    /**
+     * Get html for a form to submit this request to the IDP.
+     *
+     * form_tag_attrs: An array of attributes to be added to the form
+     * tag. 'accept-charset' and 'enctype' have defaults that can be
+     * overridden. If a value is supplied for 'action' or 'method', it
+     * will be replaced.
+     */
+    function formMarkup($realm, $return_to=null, $immediate=false,
+                        $form_tag_attrs=null)
+    {
+        $message = $this->getMessage($realm, $return_to, $immediate);
+
+        if (Auth_OpenID::isFailure($message)) {
+            return $message;
+        }
+
+        return $message->toFormMarkup($this->endpoint->server_url,
+                                      $form_tag_attrs);
+    }
+
+    /**
+     * Get a complete html document that will autosubmit the request
+     * to the IDP.
+     *
+     * Wraps formMarkup.  See the documentation for that function.
+     */
+    function htmlMarkup($realm, $return_to=null, $immediate=false,
+                        $form_tag_attrs=null)
+    {
+        $form = $this->formMarkup($realm, $return_to, $immediate, 
+                                  $form_tag_attrs);
+
+        if (Auth_OpenID::isFailure($form)) {
+            return $form;
+        }
+        return Auth_OpenID::autoSubmitHTML($form);
+    }
+
+    function shouldSendRedirect()
+    {
+        return $this->endpoint->compatibilityMode();
+    }
+}
+
+/**
+ * The base class for responses from the Auth_OpenID_Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ConsumerResponse {
+    var $status = null;
+
+    function setEndpoint($endpoint)
+    {
+        $this->endpoint = $endpoint;
+        if ($endpoint === null) {
+            $this->identity_url = null;
+        } else {
+            $this->identity_url = $endpoint->claimed_id;
+        }
+    }
+
+    /**
+     * Return the display identifier for this response.
+     *
+     * The display identifier is related to the Claimed Identifier, but the
+     * two are not always identical.  The display identifier is something the
+     * user should recognize as what they entered, whereas the response's
+     * claimed identifier (in the identity_url attribute) may have extra
+     * information for better persistence.
+     *
+     * URLs will be stripped of their fragments for display.  XRIs will
+     * display the human-readable identifier (i-name) instead of the
+     * persistent identifier (i-number).
+     *
+     * Use the display identifier in your user interface.  Use
+     * identity_url for querying your database or authorization server.
+     *
+     */
+    function getDisplayIdentifier()
+    {
+        if ($this->endpoint !== null) {
+            return $this->endpoint->getDisplayIdentifier();
+        }
+        return null;
+    }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SUCCESS. Indicates that
+ * this request is a successful acknowledgement from the OpenID server
+ * that the supplied URL is, indeed controlled by the requesting
+ * agent.  This has three relevant attributes:
+ *
+ * claimed_id - The identity URL that has been authenticated
+ *
+ * signed_args - The arguments in the server's response that were
+ * signed and verified.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse {
+    var $status = Auth_OpenID_SUCCESS;
+
+    /**
+     * @access private
+     */
+    function Auth_OpenID_SuccessResponse($endpoint, $message, $signed_args=null)
+    {
+        $this->endpoint = $endpoint;
+        $this->identity_url = $endpoint->claimed_id;
+        $this->signed_args = $signed_args;
+        $this->message = $message;
+
+        if ($this->signed_args === null) {
+            $this->signed_args = array();
+        }
+    }
+
+    /**
+     * Extract signed extension data from the server's response.
+     *
+     * @param string $prefix The extension namespace from which to
+     * extract the extension data.
+     */
+    function extensionResponse($namespace_uri, $require_signed)
+    {
+        if ($require_signed) {
+            return $this->getSignedNS($namespace_uri);
+        } else {
+            return $this->message->getArgs($namespace_uri);
+        }
+    }
+
+    function isOpenID1()
+    {
+        return $this->message->isOpenID1();
+    }
+
+    function isSigned($ns_uri, $ns_key)
+    {
+        // Return whether a particular key is signed, regardless of
+        // its namespace alias
+        return in_array($this->message->getKey($ns_uri, $ns_key),
+                        $this->signed_args);
+    }
+
+    function getSigned($ns_uri, $ns_key, $default = null)
+    {
+        // Return the specified signed field if available, otherwise
+        // return default
+        if ($this->isSigned($ns_uri, $ns_key)) {
+            return $this->message->getArg($ns_uri, $ns_key, $default);
+        } else {
+            return $default;
+        }
+    }
+
+    function getSignedNS($ns_uri)
+    {
+        $args = array();
+
+        $msg_args = $this->message->getArgs($ns_uri);
+        if (Auth_OpenID::isFailure($msg_args)) {
+            return null;
+        }
+
+        foreach ($msg_args as $key => $value) {
+            if (!$this->isSigned($ns_uri, $key)) {
+                unset($msg_args[$key]);
+            }
+        }
+
+        return $msg_args;
+    }
+
+    /**
+     * Get the openid.return_to argument from this response.
+     *
+     * This is useful for verifying that this request was initiated by
+     * this consumer.
+     *
+     * @return string $return_to The return_to URL supplied to the
+     * server on the initial request, or null if the response did not
+     * contain an 'openid.return_to' argument.
+    */
+    function getReturnTo()
+    {
+        return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to');
+    }
+}
+
+/**
+ * A response with a status of Auth_OpenID_FAILURE. Indicates that the
+ * OpenID protocol has failed. This could be locally or remotely
+ * triggered.  This has three relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined.  Otherwise, null.
+ *
+ * message - A message indicating why the request failed, if one is
+ * supplied.  Otherwise, null.
+ *
+ * status - Auth_OpenID_FAILURE.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse {
+    var $status = Auth_OpenID_FAILURE;
+
+    function Auth_OpenID_FailureResponse($endpoint, $message = null,
+                                         $contact = null, $reference = null)
+    {
+        $this->setEndpoint($endpoint);
+        $this->message = $message;
+        $this->contact = $contact;
+        $this->reference = $reference;
+    }
+}
+
+/**
+ * A specific, internal failure used to detect type URI mismatch.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse {
+}
+
+/**
+ * Exception that is raised when the server returns a 400 response
+ * code to a direct request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerErrorContainer {
+    function Auth_OpenID_ServerErrorContainer($error_text,
+                                              $error_code,
+                                              $message)
+    {
+        $this->error_text = $error_text;
+        $this->error_code = $error_code;
+        $this->message = $message;
+    }
+
+    /**
+     * @access private
+     */
+    static function fromMessage($message)
+    {
+        $error_text = $message->getArg(
+           Auth_OpenID_OPENID_NS, 'error', '<no error message supplied>');
+        $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code');
+        return new Auth_OpenID_ServerErrorContainer($error_text,
+                                                    $error_code,
+                                                    $message);
+    }
+}
+
+/**
+ * A response with a status of Auth_OpenID_CANCEL. Indicates that the
+ * user cancelled the OpenID authentication request.  This has two
+ * relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined.  Otherwise, null.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse {
+    var $status = Auth_OpenID_CANCEL;
+
+    function Auth_OpenID_CancelResponse($endpoint)
+    {
+        $this->setEndpoint($endpoint);
+    }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates
+ * that the request was in immediate mode, and the server is unable to
+ * authenticate the user without further interaction.
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted.
+ *
+ * setup_url - A URL that can be used to send the user to the server
+ * to set up for authentication. The user should be redirected in to
+ * the setup_url, either in the current window or in a new browser
+ * window.  Null in OpenID 2.
+ *
+ * status - Auth_OpenID_SETUP_NEEDED.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse {
+    var $status = Auth_OpenID_SETUP_NEEDED;
+
+    function Auth_OpenID_SetupNeededResponse($endpoint,
+                                             $setup_url = null)
+    {
+        $this->setEndpoint($endpoint);
+        $this->setup_url = $setup_url;
+    }
+}
+
+
+

--- /dev/null
+++ b/lib/openid-php/Auth/OpenID/Server.php
@@ -1,1 +1,1766 @@
-
+<?php
+
+/**
+ * OpenID server protocol and logic.
+ * 
+ * Overview
+ *
+ * An OpenID server must perform three tasks:
+ *
+ *  1. Examine the incoming request to determine its nature and validity.
+ *  2. Make a decision about how to respond to this request.
+ *  3. Format the response according to the protocol.
+ * 
+ * The first and last of these tasks may performed by the {@link
+ * Auth_OpenID_Server::decodeRequest()} and {@link
+ * Auth_OpenID_Server::encodeResponse} methods.  Who gets to do the
+ * intermediate task -- deciding how to respond to the request -- will
+ * depend on what type of request it is.
+ *
+ * If it's a request to authenticate a user (a 'checkid_setup' or
+ * 'checkid_immediate' request), you need to decide if you will assert
+ * that this user may claim the identity in question.  Exactly how you
+ * do that is a matter of application policy, but it generally
+ * involves making sure the user has an account with your system and
+ * is logged in, checking to see if that identity is hers to claim,
+ * and verifying with the user that she does consent to releasing that
+ * information to the party making the request.
+ *
+ * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
+ * object, and if and when you've come to a decision, form a response
+ * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
+ *
+ * Other types of requests relate to establishing associations between
+ * client and server and verifing the authenticity of previous
+ * communications.  {@link Auth_OpenID_Server} contains all the logic
+ * and data necessary to respond to such requests; just pass it to
+ * {@link Auth_OpenID_Server::handleRequest()}.
+ *
+ * OpenID Extensions
+ * 
+ * Do you want to provide other information for your users in addition
+ * to authentication?  Version 1.2 of the OpenID protocol allows
+ * consumers to add extensions to their requests.  For example, with
+ * sites using the Simple Registration
+ * Extension
+ * (http://openid.net/specs/openid-simple-registration-extension-1_0.html),
+ * a user can agree to have their nickname and e-mail address sent to
+ * a site when they sign up.
+ *
+ * Since extensions do not change the way OpenID authentication works,
+ * code to handle extension requests may be completely separate from
+ * the {@link Auth_OpenID_Request} class here.  But you'll likely want
+ * data sent back by your extension to be signed.  {@link
+ * Auth_OpenID_ServerResponse} provides methods with which you can add
+ * data to it which can be signed with the other data in the OpenID
+ * signature.
+ *
+ * For example:
+ *
+ * <pre>  // when request is a checkid_* request
+ *  $response = $request->answer(true);
+ *  // this will a signed 'openid.sreg.timezone' parameter to the response
+ *  response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
+ *
+ * Stores
+ *
+ * The OpenID server needs to maintain state between requests in order
+ * to function.  Its mechanism for doing this is called a store.  The
+ * store interface is defined in Interface.php.  Additionally, several
+ * concrete store implementations are provided, so that most sites
+ * won't need to implement a custom store.  For a store backed by flat
+ * files on disk, see {@link Auth_OpenID_FileStore}.  For stores based
+ * on MySQL, SQLite, or PostgreSQL, see the {@link
+ * Auth_OpenID_SQLStore} subclasses.
+ *
+ * Upgrading
+ *
+ * The keys by which a server looks up associations in its store have
+ * changed in version 1.2 of this library.  If your store has entries
+ * created from version 1.0 code, you should empty it.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Required imports
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/TrustRoot.php";
+require_once "Auth/OpenID/ServerRequest.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Nonce.php";
+
+define('AUTH_OPENID_HTTP_OK', 200);
+define('AUTH_OPENID_HTTP_REDIRECT', 302);
+define('AUTH_OPENID_HTTP_ERROR', 400);
+
+/**
+ * @access private
+ */
+global $_Auth_OpenID_Request_Modes;
+$_Auth_OpenID_Request_Modes = array('checkid_setup',
+                                    'checkid_immediate');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
+
+/**
+ * @access private
+ */
+function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
+{
+    return is_a($obj, $cls);
+}
+
+/**
+ * An error class which gets instantiated and returned whenever an
+ * OpenID protocol error occurs.  Be prepared to use this in place of
+ * an ordinary server response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerError {
+    /**
+     * @access private
+     */
+    function Auth_OpenID_ServerError($message = null, $text = null,
+                                     $reference = null, $contact = null)
+    {
+        $this->message = $message;
+        $this->text = $text;
+        $this->contact = $contact;
+        $this->reference = $reference;
+    }
+
+    function getReturnTo()
+    {
+        if ($this->message &&
+            $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
+            return $this->message->getArg(Auth_OpenID_OPENID_NS,
+                                          'return_to');
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the return_to URL for the request which caused this
+     * error.
+     */
+    function hasReturnTo()
+    {
+        return $this->getReturnTo() !== null;
+    }
+
+    /**
+     * Encodes this error's response as a URL suitable for
+     * redirection.  If the response has no return_to, another
+     * Auth_OpenID_ServerError is returned.
+     */
+    function encodeToURL()
+    {
+        if (!$this->message) {
+            return null;
+        }
+
+        $msg = $this->toMessage();
+        return $msg->toURL($this->getReturnTo());
+    }
+
+    /**
+     * Encodes the response to key-value form.  This is a
+     * machine-readable format used to respond to messages which came
+     * directly from the consumer and not through the user-agent.  See
+     * the OpenID specification.
+     */
+    function encodeToKVForm()
+    {
+        return Auth_OpenID_KVForm::fromArray(
+                                      array('mode' => 'error',
+                                            'error' => $this->toString()));
+    }
+
+    function toFormMarkup($form_tag_attrs=null)
+    {
+        $msg = $this->toMessage();
+        return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
+    }
+
+    function toHTML($form_tag_attrs=null)
+    {
+        return Auth_OpenID::autoSubmitHTML(
+                      $this->toFormMarkup($form_tag_attrs));
+    }
+
+    function toMessage()
+    {
+        // Generate a Message object for sending to the relying party,
+        // after encoding.
+        $namespace = $this->message->getOpenIDNamespace();
+        $reply = new Auth_OpenID_Message($namespace);
+        $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
+        $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());
+
+        if ($this->contact !== null) {
+            $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
+        }
+
+        if ($this->reference !== null) {
+            $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
+                           $this->reference);
+        }
+
+        return $reply;
+    }
+
+    /**
+     * Returns one of Auth_OpenID_ENCODE_URL,
+     * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
+     * encoding expected for this error's payload.
+     */
+    function whichEncoding()
+    {
+        global $_Auth_OpenID_Request_Modes;
+
+        if ($this->hasReturnTo()) {
+            if ($this->message->isOpenID2() &&
+                (strlen($this->encodeToURL()) >
+                   Auth_OpenID_OPENID1_URL_LIMIT)) {
+                return Auth_OpenID_ENCODE_HTML_FORM;
+            } else {
+                return Auth_OpenID_ENCODE_URL;
+            }
+        }
+
+        if (!$this->message) {
+            return null;
+        }
+
+        $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
+                                       'mode');
+
+        if ($mode) {
+            if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
+                return Auth_OpenID_ENCODE_KVFORM;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns this error message.
+     */
+    function toString()
+    {
+        if ($this->text) {
+            return $this->text;
+        } else {
+            return get_class($this) . " error";
+        }
+    }
+}
+
+/**
+ * Error returned by the server code when a return_to is absent from a
+ * request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
+    function Auth_OpenID_NoReturnToError($message = null,
+                                         $text = "No return_to URL available")
+    {
+        parent::Auth_OpenID_ServerError($message, $text);
+    }
+
+    function toString()
+    {
+        return "No return_to available";
+    }
+}
+
+/**
+ * An error indicating that the return_to URL is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
+    function Auth_OpenID_MalformedReturnURL($message, $return_to)
+    {
+        $this->return_to = $return_to;
+        parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
+    }
+}
+
+/**
+ * This error is returned when the trust_root value is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
+    function Auth_OpenID_MalformedTrustRoot($message = null,
+                                            $text = "Malformed trust root")
+    {
+        parent::Auth_OpenID_ServerError($message, $text);
+    }
+
+    function toString()
+    {
+        return "Malformed trust root";
+    }
+}
+
+/**
+ * The base class for all server request classes.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Request {
+    var $mode = null;
+}
+
+/**
+ * A request to verify the validity of a previous response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
+    var $mode = "check_authentication";
+    var $invalidate_handle = null;
+
+    function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
+                                          $invalidate_handle = null)
+    {
+        $this->assoc_handle = $assoc_handle;
+        $this->signed = $signed;
+        if ($invalidate_handle !== null) {
+            $this->invalidate_handle = $invalidate_handle;
+        }
+        $this->namespace = Auth_OpenID_OPENID2_NS;
+        $this->message = null;
+    }
+
+    static function fromMessage($message, $server=null)
+    {
+        $required_keys = array('assoc_handle', 'sig', 'signed');
+
+        foreach ($required_keys as $k) {
+            if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
+                return new Auth_OpenID_ServerError($message,
+                    sprintf("%s request missing required parameter %s from \
+                            query", "check_authentication", $k));
+            }
+        }
+
+        $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
+        $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');
+
+        $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+        $signed_list = explode(",", $signed_list);
+
+        $signed = $message;
+        if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
+            $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
+        }
+
+        $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
+        $result->message = $message;
+        $result->sig = $sig;
+        $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+                                                      'invalidate_handle');
+        return $result;
+    }
+
+    function answer($signatory)
+    {
+        $is_valid = $signatory->verify($this->assoc_handle, $this->signed);
+
+        // Now invalidate that assoc_handle so it this checkAuth
+        // message cannot be replayed.
+        $signatory->invalidate($this->assoc_handle, true);
+        $response = new Auth_OpenID_ServerResponse($this);
+
+        $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                  'is_valid',
+                                  ($is_valid ? "true" : "false"));
+
+        if ($this->invalidate_handle) {
+            $assoc = $signatory->getAssociation($this->invalidate_handle,
+                                                false);
+            if (!$assoc) {
+                $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                          'invalidate_handle',
+                                          $this->invalidate_handle);
+            }
+        }
+        return $response;
+    }
+}
+
+/**
+ * A class implementing plaintext server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextServerSession {
+    /**
+     * An object that knows how to handle association requests with no
+     * session type.
+     */
+    var $session_type = 'no-encryption';
+    var $needs_math = false;
+    var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
+
+    static function fromMessage($unused_request)
+    {
+        return new Auth_OpenID_PlainTextServerSession();
+    }
+
+    function answer($secret)
+    {
+        return array('mac_key' => base64_encode($secret));
+    }
+}
+
+/**
+ * A class implementing DH-SHA1 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ServerSession {
+    /**
+     * An object that knows how to handle association requests with
+     * the Diffie-Hellman session type.
+     */
+
+    var $session_type = 'DH-SHA1';
+    var $needs_math = true;
+    var $allowed_assoc_types = array('HMAC-SHA1');
+    var $hash_func = 'Auth_OpenID_SHA1';
+
+    function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
+    {
+        $this->dh = $dh;
+        $this->consumer_pubkey = $consumer_pubkey;
+    }
+
+    static function getDH($message)
+    {
+        $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
+        $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');
+
+        if ((($dh_modulus === null) && ($dh_gen !== null)) ||
+            (($dh_gen === null) && ($dh_modulus !== null))) {
+
+            if ($dh_modulus === null) {
+                $missing = 'modulus';
+            } else {
+                $missing = 'generator';
+            }
+
+            return new Auth_OpenID_ServerError($message,
+                                'If non-default modulus or generator is '.
+                                'supplied, both must be supplied.  Missing '.
+                                $missing);
+        }
+
+        $lib = Auth_OpenID_getMathLib();
+
+        if ($dh_modulus || $dh_gen) {
+            $dh_modulus = $lib->base64ToLong($dh_modulus);
+            $dh_gen = $lib->base64ToLong($dh_gen);
+            if ($lib->cmp($dh_modulus, 0) == 0 ||
+                $lib->cmp($dh_gen, 0) == 0) {
+                return new Auth_OpenID_ServerError(
+                  $message, "Failed to parse dh_mod or dh_gen");
+            }
+            $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
+        } else {
+            $dh = new Auth_OpenID_DiffieHellman();
+        }
+
+        $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
+                                            'dh_consumer_public');
+        if ($consumer_pubkey === null) {
+            return new Auth_OpenID_ServerError($message,
+                                  'Public key for DH-SHA1 session '.
+                                  'not found in query');
+        }
+
+        $consumer_pubkey =
+            $lib->base64ToLong($consumer_pubkey);
+
+        if ($consumer_pubkey === false) {
+            return new Auth_OpenID_ServerError($message,
+                                       "dh_consumer_public is not base64");
+        }
+
+        return array($dh, $consumer_pubkey);
+    }
+
+    static function fromMessage($message)
+    {
+        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+        if (is_a($result, 'Auth_OpenID_ServerError')) {
+            return $result;
+        } else {
+            list($dh, $consumer_pubkey) = $result;
+            return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
+                                                    $consumer_pubkey);
+        }
+    }
+
+    function answer($secret)
+    {
+        $lib = Auth_OpenID_getMathLib();
+        $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
+                                        $this->hash_func);
+        return array(
+           'dh_server_public' =>
+                $lib->longToBase64($this->dh->public),
+           'enc_mac_key' => base64_encode($mac_key));
+    }
+}
+
+/**
+ * A class implementing DH-SHA256 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ServerSession
+      extends Auth_OpenID_DiffieHellmanSHA1ServerSession {
+
+    var $session_type = 'DH-SHA256';
+    var $hash_func = 'Auth_OpenID_SHA256';
+    var $allowed_assoc_types = array('HMAC-SHA256');
+
+    static function fromMessage($message)
+    {
+        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+        if (is_a($result, 'Auth_OpenID_ServerError')) {
+            return $result;
+        } else {
+            list($dh, $consumer_pubkey) = $result;
+            return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
+                                                      $consumer_pubkey);
+        }
+    }
+}
+
+/**
+ * A request to associate with the server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
+    var $mode = "associate";
+
+    static function getSessionClasses()
+    {
+        return array(
+          'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
+          'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
+          'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
+    }
+
+    function Auth_OpenID_AssociateRequest($session, $assoc_type)
+    {
+        $this->session = $session;
+        $this->namespace = Auth_OpenID_OPENID2_NS;
+        $this->assoc_type = $assoc_type;
+    }
+
+    static function fromMessage($message, $server=null)
+    {
+        if ($message->isOpenID1()) {
+            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+                                             'session_type');
+
+            if ($session_type == 'no-encryption') {
+                // oidutil.log('Received OpenID 1 request with a no-encryption '
+                //             'assocaition session type. Continuing anyway.')
+            } else if (!$session_type) {
+                $session_type = 'no-encryption';
+            }
+        } else {
+            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+                                             'session_type');
+            if ($session_type === null) {
+                return new Auth_OpenID_ServerError($message,
+                  "session_type missing from request");
+            }
+        }
+
+        $session_class = Auth_OpenID::arrayGet(
+           Auth_OpenID_AssociateRequest::getSessionClasses(),
+           $session_type);
+
+        if ($session_class === null) {
+            return new Auth_OpenID_ServerError($message,
+                                               "Unknown session type " .
+                                               $session_type);
+        }
+
+        $session = call_user_func(array($session_class, 'fromMessage'),
+                                  $message);
+        if (is_a($session, 'Auth_OpenID_ServerError')) {
+            return $session;
+        }
+
+        $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
+                                       'assoc_type', 'HMAC-SHA1');
+
+        if (!in_array($assoc_type, $session->allowed_assoc_types)) {
+            $fmt = "Session type %s does not support association type %s";
+            return new Auth_OpenID_ServerError($message,
+              sprintf($fmt, $session_type, $assoc_type));
+        }
+
+        $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
+        $obj->message = $message;
+        $obj->namespace = $message->getOpenIDNamespace();
+        return $obj;
+    }
+
+    function answer($assoc)
+    {
+        $response = new Auth_OpenID_ServerResponse($this);
+        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+           array(
+                 'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
+                 'assoc_type' => $this->assoc_type,
+                 'assoc_handle' => $assoc->handle));
+
+        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+           $this->session->answer($assoc->secret));
+
+        if (! ($this->session->session_type == 'no-encryption' 
+               && $this->message->isOpenID1())) {
+            $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                      'session_type',
+                                      $this->session->session_type);
+        }
+
+        return $response;
+    }
+
+    function answerUnsupported($text_message,
+                               $preferred_association_type=null,
+                               $preferred_session_type=null)
+    {
+        if ($this->message->isOpenID1()) {
+            return new Auth_OpenID_ServerError($this->message);
+        }
+
+        $response = new Auth_OpenID_ServerResponse($this);
+        $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                  'error_code', 'unsupported-type');
+        $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                  'error', $text_message);
+
+        if ($preferred_association_type) {
+            $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                      'assoc_type',
+                                      $preferred_association_type);
+        }
+
+        if ($preferred_session_type) {
+            $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                      'session_type',
+                                      $preferred_session_type);
+        }
+        $response->code = AUTH_OPENID_HTTP_ERROR;
+        return $response;
+    }
+}
+
+/**
+ * A request to confirm the identity of a user.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+    /**
+     * Return-to verification callback.  Default is
+     * Auth_OpenID_verifyReturnTo from TrustRoot.php.
+     */
+    var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
+
+    /**
+     * The mode of this request.
+     */
+    var $mode = "checkid_setup"; // or "checkid_immediate"
+
+    /**
+     * Whether this request is for immediate mode.
+     */
+    var $immediate = false;
+
+    /**
+     * The trust_root value for this request.
+     */
+    var $trust_root = null;
+
+    /**
+     * The OpenID namespace for this request.
+     * deprecated since version 2.0.2
+     */
+    var $namespace;
+    
+    static function make($message, $identity, $return_to, $trust_root = null,
+                  $immediate = false, $assoc_handle = null, $server = null)
+    {
+        if ($server === null) {
+            return new Auth_OpenID_ServerError($message,
+                                               "server must not be null");
+        }
+
+        if ($return_to &&
+            !Auth_OpenID_TrustRoot::_parse($return_to)) {
+            return new Auth_OpenID_MalformedReturnURL($message, $return_to);
+        }
+
+        $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
+                                            $trust_root, $immediate,
+                                            $assoc_handle, $server);
+
+        $r->namespace = $message->getOpenIDNamespace();
+        $r->message = $message;
+
+        if (!$r->trustRootValid()) {
+            return new Auth_OpenID_UntrustedReturnURL($message,
+                                                      $return_to,
+                                                      $trust_root);
+        } else {
+            return $r;
+        }
+    }
+
+    function Auth_OpenID_CheckIDRequest($identity, $return_to,
+                                        $trust_root = null, $immediate = false,
+                                        $assoc_handle = null, $server = null,
+                                        $claimed_id = null)
+    {
+        $this->namespace = Auth_OpenID_OPENID2_NS;
+        $this->assoc_handle = $assoc_handle;
+        $this->identity = $identity;
+        if ($claimed_id === null) {
+            $this->claimed_id = $identity;
+        } else {
+            $this->claimed_id = $claimed_id;
+        }
+        $this->return_to = $return_to;
+        $this->trust_root = $trust_root;
+        $this->server = $server;
+
+        if ($immediate) {
+            $this->immediate = true;
+            $this->mode = "checkid_immediate";
+        } else {
+            $this->immediate = false;
+            $this->mode = "checkid_setup";
+        }
+    }
+
+    function equals($other)
+    {
+        return (
+                (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
+                ($this->namespace == $other->namespace) &&
+                ($this->assoc_handle == $other->assoc_handle) &&
+                ($this->identity == $other->identity) &&
+                ($this->claimed_id == $other->claimed_id) &&
+                ($this->return_to == $other->return_to) &&
+                ($this->trust_root == $other->trust_root));
+    }
+
+    /*
+     * Does the relying party publish the return_to URL for this
+     * response under the realm? It is up to the provider to set a
+     * policy for what kinds of realms should be allowed. This
+     * return_to URL verification reduces vulnerability to data-theft
+     * attacks based on open proxies, corss-site-scripting, or open
+     * redirectors.
+     *
+     * This check should only be performed after making sure that the
+     * return_to URL matches the realm.
+     *
+     * @return true if the realm publishes a document with the
+     * return_to URL listed, false if not or if discovery fails
+     */
+    function returnToVerified()
+    {
+        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+        return call_user_func_array($this->verifyReturnTo,
+                                    array($this->trust_root, $this->return_to, $fetcher));
+    }
+
+    static function fromMessage($message, $server)
+    {
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+        $immediate = null;
+
+        if ($mode == "checkid_immediate") {
+            $immediate = true;
+            $mode = "checkid_immediate";
+        } else {
+            $immediate = false;
+            $mode = "checkid_setup";
+        }
+
+        $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+                                      'return_to');
+
+        if (($message->isOpenID1()) &&
+            (!$return_to)) {
+            $fmt = "Missing required field 'return_to' from checkid request";
+            return new Auth_OpenID_ServerError($message, $fmt);
+        }
+
+        $identity = $message->getArg(Auth_OpenID_OPENID_NS,
+                                     'identity');
+        $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
+        if ($message->isOpenID1()) {
+            if ($identity === null) {
+                $s = "OpenID 1 message did not contain openid.identity";
+                return new Auth_OpenID_ServerError($message, $s);
+            }
+        } else {
+            if ($identity && !$claimed_id) {
+                $s = "OpenID 2.0 message contained openid.identity but not " .
+                  "claimed_id";
+                return new Auth_OpenID_ServerError($message, $s);
+            } else if ($claimed_id && !$identity) {
+                $s = "OpenID 2.0 message contained openid.claimed_id " .
+                  "but not identity";
+                return new Auth_OpenID_ServerError($message, $s);
+            }
+        }
+
+        // There's a case for making self.trust_root be a TrustRoot
+        // here.  But if TrustRoot isn't currently part of the
+        // "public" API, I'm not sure it's worth doing.
+        if ($message->isOpenID1()) {
+            $trust_root_param = 'trust_root';
+        } else {
+            $trust_root_param = 'realm';
+        }
+        $trust_root = $message->getArg(Auth_OpenID_OPENID_NS, 
+                                       $trust_root_param);
+        if (! $trust_root) {
+            $trust_root = $return_to;
+        }
+
+        if (! $message->isOpenID1() && 
+            ($return_to === null) &&
+            ($trust_root === null)) {
+            return new Auth_OpenID_ServerError($message,
+              "openid.realm required when openid.return_to absent");
+        }
+
+        $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+                                         'assoc_handle');
+
+        $obj = Auth_OpenID_CheckIDRequest::make($message,
+                                                $identity,
+                                                $return_to,
+                                                $trust_root,
+                                                $immediate,
+                                                $assoc_handle,
+                                                $server);
+
+        if (is_a($obj, 'Auth_OpenID_ServerError')) {
+            return $obj;
+        }
+
+        $obj->claimed_id = $claimed_id;
+
+        return $obj;
+    }
+
+    function idSelect()
+    {
+        // Is the identifier to be selected by the IDP?
+        // So IDPs don't have to import the constant
+        return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
+    }
+
+    function trustRootValid()
+    {
+        if (!$this->trust_root) {
+            return true;
+        }
+
+        $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
+        if ($tr === false) {
+            return new Auth_OpenID_MalformedTrustRoot($this->message,
+                                                      $this->trust_root);
+        }
+
+        if ($this->return_to !== null) {
+            return Auth_OpenID_TrustRoot::match($this->trust_root,
+                                                $this->return_to);
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Respond to this request.  Return either an
+     * {@link Auth_OpenID_ServerResponse} or
+     * {@link Auth_OpenID_ServerError}.
+     *
+     * @param bool $allow Allow this user to claim this identity, and
+     * allow the consumer to have this information?
+     *
+     * @param string $server_url DEPRECATED.  Passing $op_endpoint to
+     * the {@link Auth_OpenID_Server} constructor makes this optional.
+     *
+     * When an OpenID 1.x immediate mode request does not succeed, it
+     * gets back a URL where the request may be carried out in a
+     * not-so-immediate fashion.  Pass my URL in here (the fully
+     * qualified address of this server's endpoint, i.e.
+     * http://example.com/server), and I will use it as a base for the
+     * URL for a new request.
+     *
+     * Optional for requests where {@link $immediate} is false or
+     * $allow is true.
+     *
+     * @param string $identity The OP-local identifier to answer with.
+     * Only for use when the relying party requested identifier
+     * selection.
+     *
+     * @param string $claimed_id The claimed identifier to answer
+     * with, for use with identifier selection in the case where the
+     * claimed identifier and the OP-local identifier differ,
+     * i.e. when the claimed_id uses delegation.
+     *
+     * If $identity is provided but this is not, $claimed_id will
+     * default to the value of $identity.  When answering requests
+     * that did not ask for identifier selection, the response
+     * $claimed_id will default to that of the request.
+     *
+     * This parameter is new in OpenID 2.0.
+     *
+     * @return mixed
+     */
+    function answer($allow, $server_url = null, $identity = null,
+                    $claimed_id = null)
+    {
+        if (!$this->return_to) {
+            return new Auth_OpenID_NoReturnToError();
+        }
+
+        if (!$server_url) {
+            if ((!$this->message->isOpenID1()) &&
+                (!$this->server->op_endpoint)) {
+                return new Auth_OpenID_ServerError(null,
+                  "server should be constructed with op_endpoint to " .
+                  "respond to OpenID 2.0 messages.");
+            }
+
+            $server_url = $this->server->op_endpoint;
+        }
+
+        if ($allow) {
+            $mode = 'id_res';
+        } else if ($this->message->isOpenID1()) {
+            if ($this->immediate) {
+                $mode = 'id_res';
+            } else {
+                $mode = 'cancel';
+            }
+        } else {
+            if ($this->immediate) {
+                $mode = 'setup_needed';
+            } else {
+                $mode = 'cancel';
+            }
+        }
+
+        if (!$this->trustRootValid()) {
+            return new Auth_OpenID_UntrustedReturnURL(null,
+                                                      $this->return_to,
+                                                      $this->trust_root);
+        }
+
+        $response = new Auth_OpenID_ServerResponse($this);
+
+        if ($claimed_id &&
+            ($this->message->isOpenID1())) {
+            return new Auth_OpenID_ServerError(null,
+              "claimed_id is new in OpenID 2.0 and not " .
+              "available for ".$this->namespace);
+        }
+
+        if ($identity && !$claimed_id) {
+            $claimed_id = $identity;
+        }
+
+        if ($allow) {
+
+            if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
+                if (!$identity) {
+                    return new Auth_OpenID_ServerError(null,
+                      "This request uses IdP-driven identifier selection.  " .
+                      "You must supply an identifier in the response.");
+                }
+
+                $response_identity = $identity;
+                $response_claimed_id = $claimed_id;
+
+            } else if ($this->identity) {
+                if ($identity &&
+                    ($this->identity != $identity)) {
+                    $fmt = "Request was for %s, cannot reply with identity %s";
+                    return new Auth_OpenID_ServerError(null,
+                      sprintf($fmt, $this->identity, $identity));
+                }
+
+                $response_identity = $this->identity;
+                $response_claimed_id = $this->claimed_id;
+            } else {
+                if ($identity) {
+                    return new Auth_OpenID_ServerError(null,
+                      "This request specified no identity and " .
+                      "you supplied ".$identity);
+                }
+
+                $response_identity = null;
+            }
+
+            if (($this->message->isOpenID1()) &&
+                ($response_identity === null)) {
+                return new Auth_OpenID_ServerError(null,
+                  "Request was an OpenID 1 request, so response must " .
+                  "include an identifier.");
+            }
+
+            $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+                   array('mode' => $mode,
+                         'return_to' => $this->return_to,
+                         'response_nonce' => Auth_OpenID_mkNonce()));
+
+            if (!$this->message->isOpenID1()) {
+                $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                          'op_endpoint', $server_url);
+            }
+
+            if ($response_identity !== null) {
+                $response->fields->setArg(
+                                          Auth_OpenID_OPENID_NS,
+                                          'identity',
+                                          $response_identity);
+                if ($this->message->isOpenID2()) {
+                    $response->fields->setArg(
+                                              Auth_OpenID_OPENID_NS,
+                                              'claimed_id',
+                                              $response_claimed_id);
+                }
+            }
+
+        } else {
+            $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                      'mode', $mode);
+
+            if ($this->immediate) {
+                if (($this->message->isOpenID1()) &&
+                    (!$server_url)) {
+                    return new Auth_OpenID_ServerError(null,
+                                 'setup_url is required for $allow=false \
+                                  in OpenID 1.x immediate mode.');
+                }
+
+                $setup_request = new Auth_OpenID_CheckIDRequest(
+                                                $this->identity,
+                                                $this->return_to,
+                                                $this->trust_root,
+                                                false,
+                                                $this->assoc_handle,
+                                                $this->server,
+                                                $this->claimed_id);
+                $setup_request->message = $this->message;
+
+                $setup_url = $setup_request->encodeToURL($server_url);
+
+                if ($setup_url === null) {
+                    return new Auth_OpenID_NoReturnToError();
+                }
+
+                $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                          'user_setup_url',
+                                          $setup_url);
+            }
+        }
+
+        return $response;
+    }
+
+    function encodeToURL($server_url)
+    {
+        if (!$this->return_to) {
+            return new Auth_OpenID_NoReturnToError();
+        }
+
+        // Imported from the alternate reality where these classes are
+        // used in both the client and server code, so Requests are
+        // Encodable too.  That's right, code imported from alternate
+        // realities all for the love of you, id_res/user_setup_url.
+
+        $q = array('mode' => $this->mode,
+                   'identity' => $this->identity,
+                   'claimed_id' => $this->claimed_id,
+                   'return_to' => $this->return_to);
+
+        if ($this->trust_root) {
+            if ($this->message->isOpenID1()) {
+                $q['trust_root'] = $this->trust_root;
+            } else {
+                $q['realm'] = $this->trust_root;
+            }
+        }
+
+        if ($this->assoc_handle) {
+            $q['assoc_handle'] = $this->assoc_handle;
+        }
+
+        $response = new Auth_OpenID_Message(
+            $this->message->getOpenIDNamespace());
+        $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
+        return $response->toURL($server_url);
+    }
+
+    function getCancelURL()
+    {
+        if (!$this->return_to) {
+            return new Auth_OpenID_NoReturnToError();
+        }
+
+        if ($this->immediate) {
+            return new Auth_OpenID_ServerError(null,
+                                               "Cancel is not an appropriate \
+                                               response to immediate mode \
+                                               requests.");
+        }
+
+        $response = new Auth_OpenID_Message(
+            $this->message->getOpenIDNamespace());
+        $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
+        return $response->toURL($this->return_to);
+    }
+}
+
+/**
+ * This class encapsulates the response to an OpenID server request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerResponse {
+
+    function Auth_OpenID_ServerResponse($request)
+    {
+        $this->request = $request;
+        $this->fields = new Auth_OpenID_Message($this->request->namespace);
+    }
+
+    function whichEncoding()
+    {
+      global $_Auth_OpenID_Request_Modes;
+
+        if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
+            if ($this->fields->isOpenID2() &&
+                (strlen($this->encodeToURL()) >
+                   Auth_OpenID_OPENID1_URL_LIMIT)) {
+                return Auth_OpenID_ENCODE_HTML_FORM;
+            } else {
+                return Auth_OpenID_ENCODE_URL;
+            }
+        } else {
+            return Auth_OpenID_ENCODE_KVFORM;
+        }
+    }
+
+    /*
+     * Returns the form markup for this response.
+     *
+     * @return str
+     */
+    function toFormMarkup($form_tag_attrs=null)
+    {
+        return $this->fields->toFormMarkup($this->request->return_to,
+                                           $form_tag_attrs);
+    }
+
+    /*
+     * Returns an HTML document containing the form markup for this
+     * response that autosubmits with javascript.
+     */
+    function toHTML()
+    {
+        return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
+    }
+
+    /*
+     * Returns True if this response's encoding is ENCODE_HTML_FORM.
+     * Convenience method for server authors.
+     *
+     * @return bool
+     */
+    function renderAsForm()
+    {
+        return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
+    }
+
+
+    function encodeToURL()
+    {
+        return $this->fields->toURL($this->request->return_to);
+    }
+
+    function addExtension($extension_response)
+    {
+        $extension_response->toMessage($this->fields);
+    }
+
+    function needsSigning()
+    {
+        return $this->fields->getArg(Auth_OpenID_OPENID_NS,
+                                     'mode') == 'id_res';
+    }
+
+    function encodeToKVForm()
+    {
+        return $this->fields->toKVForm();
+    }
+}
+
+/**
+ * A web-capable response object which you can use to generate a
+ * user-agent response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_WebResponse {
+    var $code = AUTH_OPENID_HTTP_OK;
+    var $body = "";
+
+    function Auth_OpenID_WebResponse($code = null, $headers = null,
+                                     $body = null)
+    {
+        if ($code) {
+            $this->code = $code;
+        }
+
+        if ($headers !== null) {
+            $this->headers = $headers;
+        } else {
+            $this->headers = array();
+        }
+
+        if ($body !== null) {
+            $this->body = $body;
+        }
+    }
+}
+
+/**
+ * Responsible for the signature of query data and the verification of
+ * OpenID signature values.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Signatory {
+
+    // = 14 * 24 * 60 * 60; # 14 days, in seconds
+    var $SECRET_LIFETIME = 1209600;
+
+    // keys have a bogus server URL in them because the filestore
+    // really does expect that key to be a URL.  This seems a little
+    // silly for the server store, since I expect there to be only one
+    // server URL.
+    var $normal_key = 'http://localhost/|normal';
+    var $dumb_key = 'http://localhost/|dumb';
+
+    /**
+     * Create a new signatory using a given store.
+     */
+    function Auth_OpenID_Signatory($store)
+    {
+        // assert store is not None
+        $this->store = $store;
+    }
+
+    /**
+     * Verify, using a given association handle, a signature with
+     * signed key-value pairs from an HTTP request.
+     */
+    function verify($assoc_handle, $message)
+    {
+        $assoc = $this->getAssociation($assoc_handle, true);
+        if (!$assoc) {
+            // oidutil.log("failed to get assoc with handle %r to verify sig %r"
+            //             % (assoc_handle, sig))
+            return false;
+        }
+
+        return $assoc->checkMessageSignature($message);
+    }
+
+    /**
+     * Given a response, sign the fields in the response's 'signed'
+     * list, and insert the signature into the response.
+     */
+    function sign($response)
+    {
+        $signed_response = $response;
+        $assoc_handle = $response->request->assoc_handle;
+
+        if ($assoc_handle) {
+            // normal mode
+            $assoc = $this->getAssociation($assoc_handle, false, false);
+            if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
+                // fall back to dumb mode
+                $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
+                             'invalidate_handle', $assoc_handle);
+                $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');
+
+                if ($assoc && ($assoc->getExpiresIn() <= 0)) {
+                    $this->invalidate($assoc_handle, false);
+                }
+
+                $assoc = $this->createAssociation(true, $assoc_type);
+            }
+        } else {
+            // dumb mode.
+            $assoc = $this->createAssociation(true);
+        }
+
+        $signed_response->fields = $assoc->signMessage(
+                                      $signed_response->fields);
+        return $signed_response;
+    }
+
+    /**
+     * Make a new association.
+     */
+    function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
+    {
+        $secret = Auth_OpenID_CryptUtil::getBytes(
+                    Auth_OpenID_getSecretSize($assoc_type));
+
+        $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
+        $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
+
+        $assoc = Auth_OpenID_Association::fromExpiresIn(
+                      $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
+
+        if ($dumb) {
+            $key = $this->dumb_key;
+        } else {
+            $key = $this->normal_key;
+        }
+
+        $this->store->storeAssociation($key, $assoc);
+        return $assoc;
+    }
+
+    /**
+     * Given an association handle, get the association from the
+     * store, or return a ServerError or null if something goes wrong.
+     */
+    function getAssociation($assoc_handle, $dumb, $check_expiration=true)
+    {
+        if ($assoc_handle === null) {
+            return new Auth_OpenID_ServerError(null,
+                                     "assoc_handle must not be null");
+        }
+
+        if ($dumb) {
+            $key = $this->dumb_key;
+        } else {
+            $key = $this->normal_key;
+        }
+
+        $assoc = $this->store->getAssociation($key, $assoc_handle);
+
+        if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
+            if ($check_expiration) {
+                $this->store->removeAssociation($key, $assoc_handle);
+                $assoc = null;
+            }
+        }
+
+        return $assoc;
+    }
+
+    /**
+     * Invalidate a given association handle.
+     */
+    function invalidate($assoc_handle, $dumb)
+    {
+        if ($dumb) {
+            $key = $this->dumb_key;
+        } else {
+            $key = $this->normal_key;
+        }
+        $this->store->removeAssociation($key, $assoc_handle);
+    }
+}
+
+/**
+ * Encode an {@link Auth_OpenID_ServerResponse} to an
+ * {@link Auth_OpenID_WebResponse}.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Encoder {
+
+    var $responseFactory = 'Auth_OpenID_WebResponse';
+
+    /**
+     * Encode an {@link Auth_OpenID_ServerResponse} and return an
+     * {@link Auth_OpenID_WebResponse}.
+     */
+    function encode($response)
+    {
+        $cls = $this->responseFactory;
+
+        $encode_as = $response->whichEncoding();
+        if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
+            $wr = new $cls(null, null, $response->encodeToKVForm());
+            if (is_a($response, 'Auth_OpenID_ServerError')) {
+                $wr->code = AUTH_OPENID_HTTP_ERROR;
+            }
+        } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
+            $location = $response->encodeToURL();
+            $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
+                           array('location' => $location));
+        } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
+          $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
+                         $response->toHTML());
+        } else {
+            return new Auth_OpenID_EncodingError($response);
+        }
+        /* Allow the response to carry a custom error code (ex: for Association errors) */
+        if(isset($response->code)) {
+            $wr->code = $response->code;
+        }
+        return $wr;
+    }
+}
+
+/**
+ * An encoder which also takes care of signing fields when required.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
+
+    function Auth_OpenID_SigningEncoder($signatory)
+    {
+        $this->signatory = $signatory;
+    }
+
+    /**
+     * Sign an {@link Auth_OpenID_ServerResponse} and return an
+     * {@link Auth_OpenID_WebResponse}.
+     */
+    function encode($response)
+    {
+        // the isinstance is a bit of a kludge... it means there isn't
+        // really an adapter to make the interfaces quite match.
+        if (!is_a($response, 'Auth_OpenID_ServerError') &&
+            $response->needsSigning()) {
+
+            if (!$this->signatory) {
+                return new Auth_OpenID_ServerError(null,
+                                       "Must have a store to sign request");
+            }
+
+            if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
+                return new Auth_OpenID_AlreadySigned($response);
+            }
+            $response = $this->signatory->sign($response);
+        }
+
+        return parent::encode($response);
+    }
+}
+
+/**
+ * Decode an incoming query into an Auth_OpenID_Request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Decoder {
+
+    function Auth_OpenID_Decoder($server)
+    {
+        $this->server = $server;
+
+        $this->handlers = array(
+            'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
+            'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
+            'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
+            'associate' => 'Auth_OpenID_AssociateRequest'
+            );
+    }
+
+    /**
+     * Given an HTTP query in an array (key-value pairs), decode it
+     * into an Auth_OpenID_Request object.
+     */
+    function decode($query)
+    {
+        if (!$query) {
+            return null;
+        }
+
+        $message = Auth_OpenID_Message::fromPostArgs($query);
+
+        if ($message === null) {
+            /*
+             * It's useful to have a Message attached to a
+             * ProtocolError, so we override the bad ns value to build
+             * a Message out of it.  Kinda kludgy, since it's made of
+             * lies, but the parts that aren't lies are more useful
+             * than a 'None'.
+             */
+            $old_ns = $query['openid.ns'];
+
+            $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
+            $message = Auth_OpenID_Message::fromPostArgs($query);
+            return new Auth_OpenID_ServerError(
+                  $message,
+                  sprintf("Invalid OpenID namespace URI: %s", $old_ns));
+        }
+
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+        if (!$mode) {
+            return new Auth_OpenID_ServerError($message,
+                                               "No mode value in message");
+        }
+
+        if (Auth_OpenID::isFailure($mode)) {
+            return new Auth_OpenID_ServerError($message,
+                                               $mode->message);
+        }
+
+        $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
+                                            $this->defaultDecoder($message));
+
+        if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
+            return call_user_func_array(array($handlerCls, 'fromMessage'),
+                                        array($message, $this->server));
+        } else {
+            return $handlerCls;
+        }
+    }
+
+    function defaultDecoder($message)
+    {
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+
+        if (Auth_OpenID::isFailure($mode)) {
+            return new Auth_OpenID_ServerError($message,
+                                               $mode->message);
+        }
+
+        return new Auth_OpenID_ServerError($message,
+                       sprintf("Unrecognized OpenID mode %s", $mode));
+    }
+}
+
+/**
+ * An error that indicates an encoding problem occurred.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_EncodingError {
+    function Auth_OpenID_EncodingError($response)
+    {
+        $this->response = $response;
+    }
+}
+
+/**
+ * An error that indicates that a response was already signed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
+    // This response is already signed.
+}
+
+/**
+ * An error that indicates that the given return_to is not under the
+ * given trust_root.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
+    function Auth_OpenID_UntrustedReturnURL($message, $return_to,
+                                            $trust_root)
+    {
+        parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
+        $this->return_to = $return_to;
+        $this->trust_root = $trust_root;
+    }
+
+    function toString()
+    {
+        return sprintf("return_to %s not under trust_root %s",
+                       $this->return_to, $this->trust_root);
+    }
+}
+
+/**
+ * I handle requests for an OpenID server.
+ *
+ * Some types of requests (those which are not checkid requests) may
+ * be handed to my {@link handleRequest} method, and I will take care
+ * of it and return a response.
+ *
+ * For your convenience, I also provide an interface to {@link
+ * Auth_OpenID_Decoder::decode()} and {@link
+ * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
+ * decodeRequest} and {@link encodeResponse}.
+ *
+ * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
+ *
+ * Example:
+ *
+ * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
+ *                                   "http://example.com/op");
+ * $request = $oserver->decodeRequest();
+ * if (in_array($request->mode, array('checkid_immediate',
+ *                                    'checkid_setup'))) {
+ *     if ($app->isAuthorized($request->identity, $request->trust_root)) {
+ *         $response = $request->answer(true);
+ *     } else if ($request->immediate) {
+ *         $response = $request->answer(false);
+ *     } else {
+ *         $app->showDecidePage($request);
+ *         return;
+ *     }
+ * } else {
+ *     $response = $oserver->handleRequest($request);
+ * }
+ *
+ * $webresponse = $oserver->encode($response);</pre>
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Server {
+    function Auth_OpenID_Server($store, $op_endpoint=null)
+    {
+        $this->store = $store;
+        $this->signatory = new Auth_OpenID_Signatory($this->store);
+        $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
+        $this->decoder = new Auth_OpenID_Decoder($this);
+        $this->op_endpoint = $op_endpoint;
+        $this->negotiator = Auth_OpenID_getDefaultNegotiator();
+    }
+
+    /**
+     * Handle a request.  Given an {@link Auth_OpenID_Request} object,
+     * call the appropriate {@link Auth_OpenID_Server} method to
+     * process the request and generate a response.
+     *
+     * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
+     * returned by {@link Auth_OpenID_Server::decodeRequest()}.
+     *
+     * @return Auth_OpenID_ServerResponse $response A response object
+     * capable of generating a user-agent reply.
+     */
+    function handleRequest($request)
+    {
+        if (method_exists($this, "openid_" . $request->mode)) {
+            $handler = array($this, "openid_" . $request->mode);
+            return call_user_func($handler, $request);
+        }
+        return null;
+    }
+
+    /**
+     * The callback for 'check_authentication' messages.
+     */
+    function openid_check_authentication($request)
+    {
+        return $request->answer($this->signatory);
+    }
+
+    /**
+     * The callback for 'associate' messages.
+     */
+    function openid_associate($request)
+    {
+        $assoc_type = $request->assoc_type;
+        $session_type = $request->session->session_type;
+        if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
+            $assoc = $this->signatory->createAssociation(false,
+                                                         $assoc_type);
+            return $request->answer($assoc);
+        } else {
+            $message = sprintf('Association type %s is not supported with '.
+                               'session type %s', $assoc_type, $session_type);
+            list($preferred_assoc_type, $preferred_session_type) =
+                $this->negotiator->getAllowedType();
+            return $request->answerUnsupported($message,
+                                               $preferred_assoc_type,
+                                               $preferred_session_type);
+        }
+    }
+
+    /**
+     * Encodes as response in the appropriate format suitable for
+     * sending to the user agent.
+     */
+    function encodeResponse($response)
+    {
+        return $this->encoder->encode($response);
+    }
+
+    /**
+     * Decodes a query args array into the appropriate
+     * {@link Auth_OpenID_Request} object.
+     */
+    function decodeRequest($query=null)
+    {
+        if ($query === null) {
+            $query = Auth_OpenID::getQuery();
+        }
+
+        return $this->decoder->decode($query);
+    }
+}
+
+
+

--- /dev/null
+++ b/lib/openid-php/Auth/OpenID/TrustRoot.php
@@ -1,1 +1,462 @@
-
+<?php
+/**
+ * Functions for dealing with OpenID trust roots
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID/Discover.php';
+
+/**
+ * A regular expression that matches a domain ending in a top-level domains.
+ * Used in checking trust roots for sanity.
+ *
+ * @access private
+ */
+define('Auth_OpenID___TLDs',
+       '/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
+       '|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
+       '|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
+       '|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
+       '|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
+       '|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
+       '|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
+       '|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
+       '|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
+       '|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
+       '|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
+       '|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
+       '|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
+       '|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
+       '|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
+       '|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
+       '|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
+       '|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');
+
+define('Auth_OpenID___HostSegmentRe',
+       "/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");
+
+/**
+ * A wrapper for trust-root related functions
+ */
+class Auth_OpenID_TrustRoot {
+    /*
+     * Return a discovery URL for this realm.
+     *
+     * Return null if the realm could not be parsed or was not valid.
+     *
+     * @param return_to The relying party return URL of the OpenID
+     * authentication request
+     *
+     * @return The URL upon which relying party discovery should be
+     * run in order to verify the return_to URL
+     */
+    static function buildDiscoveryURL($realm)
+    {
+        $parsed = Auth_OpenID_TrustRoot::_parse($realm);
+
+        if ($parsed === false) {
+            return false;
+        }
+
+        if ($parsed['wildcard']) {
+            // Use "www." in place of the star
+            if ($parsed['host'][0] != '.') {
+                return false;
+            }
+
+            $www_domain = 'www' . $parsed['host'];
+
+            return sprintf('%s://%s%s', $parsed['scheme'],
+                           $www_domain, $parsed['path']);
+        } else {
+            return $parsed['unparsed'];
+        }
+    }
+
+    /**
+     * Parse a URL into its trust_root parts.
+     *
+     * @static
+     *
+     * @access private
+     *
+     * @param string $trust_root The url to parse
+     *
+     * @return mixed $parsed Either an associative array of trust root
+     * parts or false if parsing failed.
+     */
+    static function _parse($trust_root)
+    {
+        $trust_root = Auth_OpenID_urinorm($trust_root);
+        if ($trust_root === null) {
+            return false;
+        }
+
+        if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+            return false;
+        }
+
+        $parts = @parse_url($trust_root);
+        if ($parts === false) {
+            return false;
+        }
+
+        $required_parts = array('scheme', 'host');
+        $forbidden_parts = array('user', 'pass', 'fragment');
+        $keys = array_keys($parts);
+        if (array_intersect($keys, $required_parts) != $required_parts) {
+            return false;
+        }
+
+        if (array_intersect($keys, $forbidden_parts) != array()) {
+            return false;
+        }
+
+        if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
+            return false;
+        }
+
+        $scheme = strtolower($parts['scheme']);
+        $allowed_schemes = array('http', 'https');
+        if (!in_array($scheme, $allowed_schemes)) {
+            return false;
+        }
+        $parts['scheme'] = $scheme;
+
+        $host = strtolower($parts['host']);
+        $hostparts = explode('*', $host);
+        switch (count($hostparts)) {
+        case 1:
+            $parts['wildcard'] = false;
+            break;
+        case 2:
+            if ($hostparts[0] ||
+                ($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
+                return false;
+            }
+            $host = $hostparts[1];
+            $parts['wildcard'] = true;
+            break;
+        default:
+            return false;
+        }
+        if (strpos($host, ':') !== false) {
+            return false;
+        }
+
+        $parts['host'] = $host;
+
+        if (isset($parts['path'])) {
+            $path = strtolower($parts['path']);
+            if (substr($path, 0, 1) != '/') {
+                return false;
+            }
+        } else {
+            $path = '/';
+        }
+
+        $parts['path'] = $path;
+        if (!isset($parts['port'])) {
+            $parts['port'] = false;
+        }
+
+
+        $parts['unparsed'] = $trust_root;
+
+        return $parts;
+    }
+
+    /**
+     * Is this trust root sane?
+     *
+     * A trust root is sane if it is syntactically valid and it has a
+     * reasonable domain name. Specifically, the domain name must be
+     * more than one level below a standard TLD or more than two
+     * levels below a two-letter tld.
+     *
+     * For example, '*.com' is not a sane trust root, but '*.foo.com'
+     * is.  '*.co.uk' is not sane, but '*.bbc.co.uk' is.
+     *
+     * This check is not always correct, but it attempts to err on the
+     * side of marking sane trust roots insane instead of marking
+     * insane trust roots sane. For example, 'kink.fm' is marked as
+     * insane even though it "should" (for some meaning of should) be
+     * marked sane.
+     *
+     * This function should be used when creating OpenID servers to
+     * alert the users of the server when a consumer attempts to get
+     * the user to accept a suspicious trust root.
+     *
+     * @static
+     * @param string $trust_root The trust root to check
+     * @return bool $sanity Whether the trust root looks OK
+     */
+    static function isSane($trust_root)
+    {
+        $parts = Auth_OpenID_TrustRoot::_parse($trust_root);
+        if ($parts === false) {
+            return false;
+        }
+
+        // Localhost is a special case
+        if ($parts['host'] == 'localhost') {
+            return true;
+        }
+        
+        $host_parts = explode('.', $parts['host']);
+        if ($parts['wildcard']) {
+            // Remove the empty string from the beginning of the array
+            array_shift($host_parts);
+        }
+
+        if ($host_parts && !$host_parts[count($host_parts) - 1]) {
+            array_pop($host_parts);
+        }
+
+        if (!$host_parts) {
+            return false;
+        }
+
+        // Don't allow adjacent dots
+        if (in_array('', $host_parts, true)) {
+            return false;
+        }
+
+        // Get the top-level domain of the host. If it is not a valid TLD,
+        // it's not sane.
+        preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
+        if (!$matches) {
+            return false;
+        }
+        $tld = $matches[1];
+
+        if (count($host_parts) == 1) {
+            return false;
+        }
+
+        if ($parts['wildcard']) {
+            // It's a 2-letter tld with a short second to last segment
+            // so there needs to be more than two segments specified
+            // (e.g. *.co.uk is insane)
+            $second_level = $host_parts[count($host_parts) - 2];
+            if (strlen($tld) == 2 && strlen($second_level) <= 3) {
+                return count($host_parts) > 2;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Does this URL match the given trust root?
+     *
+     * Return whether the URL falls under the given trust root. This
+     * does not check whether the trust root is sane. If the URL or
+     * trust root do not parse, this function will return false.
+     *
+     * @param string $trust_root The trust root to match against
+     *
+     * @param string $url The URL to check
+     *
+     * @return bool $matches Whether the URL matches against the
+     * trust root
+     */
+    static function match($trust_root, $url)
+    {
+        $trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
+        $url_parsed = Auth_OpenID_TrustRoot::_parse($url);
+        if (!$trust_root_parsed || !$url_parsed) {
+            return false;
+        }
+
+        // Check hosts matching
+        if ($url_parsed['wildcard']) {
+            return false;
+        }
+        if ($trust_root_parsed['wildcard']) {
+            $host_tail = $trust_root_parsed['host'];
+            $host = $url_parsed['host'];
+            if ($host_tail &&
+                substr($host, -(strlen($host_tail))) != $host_tail &&
+                substr($host_tail, 1) != $host) {
+                return false;
+            }
+        } else {
+            if ($trust_root_parsed['host'] != $url_parsed['host']) {
+                return false;
+            }
+        }
+
+        // Check path and query matching
+        $base_path = $trust_root_parsed['path'];
+        $path = $url_parsed['path'];
+        if (!isset($trust_root_parsed['query'])) {
+            if ($base_path != $path) {
+                if (substr($path, 0, strlen($base_path)) != $base_path) {
+                    return false;
+                }
+                if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
+                    substr($path, strlen($base_path), 1) != '/') {
+                    return false;
+                }
+            }
+        } else {
+            $base_query = $trust_root_parsed['query'];
+            $query = @$url_parsed['query'];
+            $qplus = substr($query, 0, strlen($base_query) + 1);
+            $bqplus = $base_query . '&';
+            if ($base_path != $path ||
+                ($base_query != $query && $qplus != $bqplus)) {
+                return false;
+            }
+        }
+
+        // The port and scheme need to match exactly
+        return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
+                $url_parsed['port'] === $trust_root_parsed['port']);
+    }
+}
+
+/*
+ * If the endpoint is a relying party OpenID return_to endpoint,
+ * return the endpoint URL. Otherwise, return None.
+ *
+ * This function is intended to be used as a filter for the Yadis
+ * filtering interface.
+ *
+ * @see: C{L{openid.yadis.services}}
+ * @see: C{L{openid.yadis.filters}}
+ *
+ * @param endpoint: An XRDS BasicServiceEndpoint, as returned by
+ * performing Yadis dicovery.
+ *
+ * @returns: The endpoint URL or None if the endpoint is not a
+ * relying party endpoint.
+ */
+function filter_extractReturnURL($endpoint)
+{
+    if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
+        return $endpoint;
+    } else {
+        return null;
+    }
+}
+
+function &Auth_OpenID_extractReturnURL(&$endpoint_list)
+{
+    $result = array();
+
+    foreach ($endpoint_list as $endpoint) {
+        if (filter_extractReturnURL($endpoint)) {
+            $result[] = $endpoint;
+        }
+    }
+
+    return $result;
+}
+
+/*
+ * Is the return_to URL under one of the supplied allowed return_to
+ * URLs?
+ */
+function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
+{
+    foreach ($allowed_return_to_urls as $allowed_return_to) {
+        // A return_to pattern works the same as a realm, except that
+        // it's not allowed to use a wildcard. We'll model this by
+        // parsing it as a realm, and not trying to match it if it has
+        // a wildcard.
+
+        $return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
+        if (// Parses as a trust root
+            ($return_realm !== false) &&
+            // Does not have a wildcard
+            (!$return_realm['wildcard']) &&
+            // Matches the return_to that we passed in with it
+            (Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
+            return true;
+        }
+    }
+
+    // No URL in the list matched
+    return false;
+}
+
+/*
+ * Given a relying party discovery URL return a list of return_to
+ * URLs.
+ */
+function Auth_OpenID_getAllowedReturnURLs($relying_party_url, $fetcher,
+              $discover_function=null)
+{
+    if ($discover_function === null) {
+        $discover_function = array('Auth_Yadis_Yadis', 'discover');
+    }
+
+    $xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'consumerFromXRDS');
+
+    list($rp_url_after_redirects, $endpoints) =
+        Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
+                                       $discover_function, $fetcher);
+
+    if ($rp_url_after_redirects != $relying_party_url) {
+        // Verification caused a redirect
+        return false;
+    }
+
+    call_user_func_array($discover_function,
+                         array($relying_party_url, $fetcher));
+
+    $return_to_urls = array();
+    $matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);
+
+    foreach ($matching_endpoints as $e) {
+        $return_to_urls[] = $e->server_url;
+    }
+
+    return $return_to_urls;
+}
+
+/*
+ * Verify that a return_to URL is valid for the given realm.
+ *
+ * This function builds a discovery URL, performs Yadis discovery on
+ * it, makes sure that the URL does not redirect, parses out the
+ * return_to URLs, and finally checks to see if the current return_to
+ * URL matches the return_to.
+ *
+ * @return true if the return_to URL is valid for the realm
+ */
+function Auth_OpenID_verifyReturnTo($realm_str, $return_to, $fetcher,
+              $_vrfy='Auth_OpenID_getAllowedReturnURLs')
+{
+    $disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);
+
+    if ($disco_url === false) {
+        return false;
+    }
+
+    $allowable_urls = call_user_func_array($_vrfy,
+                           array($disco_url, $fetcher));
+
+    // The realm_str could not be parsed.
+    if ($allowable_urls === false) {
+        return false;
+    }
+
+    if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+

--- /dev/null
+++ b/lib/openid-php/Auth/Yadis/Manager.php
@@ -1,1 +1,522 @@
-
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base session class used by the Auth_Yadis_Manager.  This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_PHPSession {
+    /**
+     * Set a session key/value pair.
+     *
+     * @param string $name The name of the session key to add.
+     * @param string $value The value to add to the session.
+     */
+    function set($name, $value)
+    {
+        $_SESSION[$name] = $value;
+    }
+
+    /**
+     * Get a key's value from the session.
+     *
+     * @param string $name The name of the key to retrieve.
+     * @param string $default The optional value to return if the key
+     * is not found in the session.
+     * @return string $result The key's value in the session or
+     * $default if it isn't found.
+     */
+    function get($name, $default=null)
+    {
+        if (array_key_exists($name, $_SESSION)) {
+            return $_SESSION[$name];
+        } else {
+            return $default;
+        }
+    }
+
+    /**
+     * Remove a key/value pair from the session.
+     *
+     * @param string $name The name of the key to remove.
+     */
+    function del($name)
+    {
+        unset($_SESSION[$name]);
+    }
+
+    /**
+     * Return the contents of the session in array form.
+     */
+    function contents()
+    {
+        return $_SESSION;
+    }
+}
+
+/**
+ * A session helper class designed to translate between arrays and
+ * objects.  Note that the class used must have a constructor that
+ * takes no parameters.  This is not a general solution, but it works
+ * for dumb objects that just need to have attributes set.  The idea
+ * is that you'll subclass this and override $this->check($data) ->
+ * bool to implement your own session data validation.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_SessionLoader {
+    /**
+     * Override this.
+     *
+     * @access private
+     */
+    function check($data)
+    {
+        return true;
+    }
+
+    /**
+     * Given a session data value (an array), this creates an object
+     * (returned by $this->newObject()) whose attributes and values
+     * are those in $data.  Returns null if $data lacks keys found in
+     * $this->requiredKeys().  Returns null if $this->check($data)
+     * evaluates to false.  Returns null if $this->newObject()
+     * evaluates to false.
+     *
+     * @access private
+     */
+    function fromSession($data)
+    {
+        if (!$data) {
+            return null;
+        }
+
+        $required = $this->requiredKeys();
+
+        foreach ($required as $k) {
+            if (!array_key_exists($k, $data)) {
+                return null;
+            }
+        }
+
+        if (!$this->check($data)) {
+            return null;
+        }
+
+        $data = array_merge($data, $this->prepareForLoad($data));
+        $obj = $this->newObject($data);
+
+        if (!$obj) {
+            return null;
+        }
+
+        foreach ($required as $k) {
+            $obj->$k = $data[$k];
+        }
+
+        return $obj;
+    }
+
+    /**
+     * Prepares the data array by making any necessary changes.
+     * Returns an array whose keys and values will be used to update
+     * the original data array before calling $this->newObject($data).
+     *
+     * @access private
+     */
+    function prepareForLoad($data)
+    {
+        return array();
+    }
+
+    /**
+     * Returns a new instance of this loader's class, using the
+     * session data to construct it if necessary.  The object need
+     * only be created; $this->fromSession() will take care of setting
+     * the object's attributes.
+     *
+     * @access private
+     */
+    function newObject($data)
+    {
+        return null;
+    }
+
+    /**
+     * Returns an array of keys and values built from the attributes
+     * of $obj.  If $this->prepareForSave($obj) returns an array, its keys
+     * and values are used to update the $data array of attributes
+     * from $obj.
+     *
+     * @access private
+     */
+    function toSession($obj)
+    {
+        $data = array();
+        foreach ($obj as $k => $v) {
+            $data[$k] = $v;
+        }
+
+        $extra = $this->prepareForSave($obj);
+
+        if ($extra && is_array($extra)) {
+            foreach ($extra as $k => $v) {
+                $data[$k] = $v;
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Override this.
+     *
+     * @access private
+     */
+    function prepareForSave($obj)
+    {
+        return array();
+    }
+}
+
+/**
+ * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
+    function newObject($data)
+    {
+        return new Auth_OpenID_ServiceEndpoint();
+    }
+
+    function requiredKeys()
+    {
+        $obj = new Auth_OpenID_ServiceEndpoint();
+        $data = array();
+        foreach ($obj as $k => $v) {
+            $data[] = $k;
+        }
+        return $data;
+    }
+
+    function check($data)
+    {
+        return is_array($data['type_uris']);
+    }
+}
+
+/**
+ * A concrete loader implementation for Auth_Yadis_Managers.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
+    function requiredKeys()
+    {
+        return array('starting_url',
+                     'yadis_url',
+                     'services',
+                     'session_key',
+                     '_current',
+                     'stale');
+    }
+
+    function newObject($data)
+    {
+        return new Auth_Yadis_Manager($data['starting_url'],
+                                          $data['yadis_url'],
+                                          $data['services'],
+                                          $data['session_key']);
+    }
+
+    function check($data)
+    {
+        return is_array($data['services']);
+    }
+
+    function prepareForLoad($data)
+    {
+        $loader = new Auth_OpenID_ServiceEndpointLoader();
+        $services = array();
+        foreach ($data['services'] as $s) {
+            $services[] = $loader->fromSession($s);
+        }
+        return array('services' => $services);
+    }
+
+    function prepareForSave($obj)
+    {
+        $loader = new Auth_OpenID_ServiceEndpointLoader();
+        $services = array();
+        foreach ($obj->services as $s) {
+            $services[] = $loader->toSession($s);
+        }
+        return array('services' => $services);
+    }
+}
+
+/**
+ * The Yadis service manager which stores state in a session and
+ * iterates over <Service> elements in a Yadis XRDS document and lets
+ * a caller attempt to use each one.  This is used by the Yadis
+ * library internally.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Manager {
+
+    /**
+     * Intialize a new yadis service manager.
+     *
+     * @access private
+     */
+    function Auth_Yadis_Manager($starting_url, $yadis_url,
+                                    $services, $session_key)
+    {
+        // The URL that was used to initiate the Yadis protocol
+        $this->starting_url = $starting_url;
+
+        // The URL after following redirects (the identifier)
+        $this->yadis_url = $yadis_url;
+
+        // List of service elements
+        $this->services = $services;
+
+        $this->session_key = $session_key;
+
+        // Reference to the current service object
+        $this->_current = null;
+
+        // Stale flag for cleanup if PHP lib has trouble.
+        $this->stale = false;
+    }
+
+    /**
+     * @access private
+     */
+    function length()
+    {
+        // How many untried services remain?
+        return count($this->services);
+    }
+
+    /**
+     * Return the next service
+     *
+     * $this->current() will continue to return that service until the
+     * next call to this method.
+     */
+    function nextService()
+    {
+
+        if ($this->services) {
+            $this->_current = array_shift($this->services);
+        } else {
+            $this->_current = null;
+        }
+
+        return $this->_current;
+    }
+
+    /**
+     * @access private
+     */
+    function current()
+    {
+        // Return the current service.
+        // Returns None if there are no services left.
+        return $this->_current;
+    }
+
+    /**
+     * @access private
+     */
+    function forURL($url)
+    {
+        return in_array($url, array($this->starting_url, $this->yadis_url));
+    }
+
+    /**
+     * @access private
+     */
+    function started()
+    {
+        // Has the first service been returned?
+        return $this->_current !== null;
+    }
+}
+
+/**
+ * State management for discovery.
+ *
+ * High-level usage pattern is to call .getNextService(discover) in
+ * order to find the next available service for this user for this
+ * session. Once a request completes, call .cleanup() to clean up the
+ * session state.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Discovery {
+
+    /**
+     * @access private
+     */
+    var $DEFAULT_SUFFIX = 'auth';
+
+    /**
+     * @access private
+     */
+    var $PREFIX = '_yadis_services_';
+
+    /**
+     * Initialize a discovery object.
+     *
+     * @param Auth_Yadis_PHPSession $session An object which
+     * implements the Auth_Yadis_PHPSession API.
+     * @param string $url The URL on which to attempt discovery.
+     * @param string $session_key_suffix The optional session key
+     * suffix override.
+     */
+    function Auth_Yadis_Discovery($session, $url,
+                                      $session_key_suffix = null)
+    {
+        /// Initialize a discovery object
+        $this->session = $session;
+        $this->url = $url;
+        if ($session_key_suffix === null) {
+            $session_key_suffix = $this->DEFAULT_SUFFIX;
+        }
+
+        $this->session_key_suffix = $session_key_suffix;
+        $this->session_key = $this->PREFIX . $this->session_key_suffix;
+    }
+
+    /**
+     * Return the next authentication service for the pair of
+     * user_input and session. This function handles fallback.
+     */
+    function getNextService($discover_cb, $fetcher)
+    {
+        $manager = $this->getManager();
+        if (!$manager || (!$manager->services)) {
+            $this->destroyManager();
+
+            list($yadis_url, $services) = call_user_func($discover_cb,
+                                                         $this->url,
+                                                         $fetcher);
+
+            $manager = $this->createManager($services, $yadis_url);
+        }
+
+        if ($manager) {
+            $loader = new Auth_Yadis_ManagerLoader();
+            $service = $manager->nextService();
+            $this->session->set($this->session_key,
+                                serialize($loader->toSession($manager)));
+        } else {
+            $service = null;
+        }
+
+        return $service;
+    }
+
+    /**
+     * Clean up Yadis-related services in the session and return the
+     * most-recently-attempted service from the manager, if one
+     * exists.
+     *
+     * @param $force True if the manager should be deleted regardless
+     * of whether it's a manager for $this->url.
+     */
+    function cleanup($force=false)
+    {
+        $manager = $this->getManager($force);
+        if ($manager) {
+            $service = $manager->current();
+            $this->destroyManager($force);
+        } else {
+            $service = null;
+        }
+
+        return $service;
+    }
+
+    /**
+     * @access private
+     */
+    function getSessionKey()
+    {
+        // Get the session key for this starting URL and suffix
+        return $this->PREFIX . $this->session_key_suffix;
+    }
+
+    /**
+     * @access private
+     *
+     * @param $force True if the manager should be returned regardless
+     * of whether it's a manager for $this->url.
+     */
+    function getManager($force=false)
+    {
+        // Extract the YadisServiceManager for this object's URL and
+        // suffix from the session.
+
+        $manager_str = $this->session->get($this->getSessionKey());
+        $manager = null;
+
+        if ($manager_str !== null) {
+            $loader = new Auth_Yadis_ManagerLoader();
+            $manager = $loader->fromSession(unserialize($manager_str));
+        }
+
+        if ($manager && ($manager->forURL($this->url) || $force)) {
+            return $manager;
+        }
+    }
+
+    /**
+     * @access private
+     */
+    function createManager($services, $yadis_url = null)
+    {
+        $key = $this->getSessionKey();
+        if ($this->getManager()) {
+            return $this->getManager();
+        }
+
+        if ($services) {
+            $loader = new Auth_Yadis_ManagerLoader();
+            $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
+                                              $services, $key);
+            $this->session->set($this->session_key,
+                                serialize($loader->toSession($manager)));
+            return $manager;
+        }
+    }
+
+    /**
+     * @access private
+     *
+     * @param $force True if the manager should be deleted regardless
+     * of whether it's a manager for $this->url.
+     */
+    function destroyManager($force=false)
+    {
+        if ($this->getManager($force) !== null) {
+            $key = $this->getSessionKey();
+            $this->session->del($key);
+        }
+    }
+}
+
+

--- /dev/null
+++ b/lib/openid-php/Auth/Yadis/XRDS.php
@@ -1,1 +1,479 @@
-
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Auth/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ALL', 101);
+
+/**
+ * This match mode means a given service must match ANY filters (at
+ * least one) passed to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ANY', 102);
+
+/**
+ * The priority value used for service elements with no priority
+ * specified.
+ */
+define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
+
+/**
+ * XRD XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
+
+/**
+ * XRDS XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
+
+function Auth_Yadis_getNSMap()
+{
+    return array('xrds' => Auth_Yadis_XMLNS_XRDS,
+                 'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
+}
+
+/**
+ * @access private
+ */
+function Auth_Yadis_array_scramble($arr)
+{
+    $result = array();
+
+    while (count($arr)) {
+        $index = array_rand($arr, 1);
+        $result[] = $arr[$index];
+        unset($arr[$index]);
+    }
+
+    return $result;
+}
+
+/**
+ * This class represents a <Service> element in an XRDS document.
+ * Objects of this type are returned by
+ * Auth_Yadis_XRDS::services() and
+ * Auth_Yadis_Yadis::services().  Each object corresponds directly
+ * to a <Service> element in the XRDS and supplies a
+ * getElements($name) method which you should use to inspect the
+ * element's contents.  See {@link Auth_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Service {
+
+    /**
+     * Creates an empty service object.
+     */
+    function Auth_Yadis_Service()
+    {
+        $this->element = null;
+        $this->parser = null;
+    }
+
+    /**
+     * Return the URIs in the "Type" elements, if any, of this Service
+     * element.
+     *
+     * @return array $type_uris An array of Type URI strings.
+     */
+    function getTypes()
+    {
+        $t = array();
+        foreach ($this->getElements('xrd:Type') as $elem) {
+            $c = $this->parser->content($elem);
+            if ($c) {
+                $t[] = $c;
+            }
+        }
+        return $t;
+    }
+
+    function matchTypes($type_uris)
+    {
+        $result = array();
+
+        foreach ($this->getTypes() as $typ) {
+            if (in_array($typ, $type_uris)) {
+                $result[] = $typ;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Return the URIs in the "URI" elements, if any, of this Service
+     * element.  The URIs are returned sorted in priority order.
+     *
+     * @return array $uris An array of URI strings.
+     */
+    function getURIs()
+    {
+        $uris = array();
+        $last = array();
+
+        foreach ($this->getElements('xrd:URI') as $elem) {
+            $uri_string = $this->parser->content($elem);
+            $attrs = $this->parser->attributes($elem);
+            if ($attrs &&
+                array_key_exists('priority', $attrs)) {
+                $priority = intval($attrs['priority']);
+                if (!array_key_exists($priority, $uris)) {
+                    $uris[$priority] = array();
+                }
+
+                $uris[$priority][] = $uri_string;
+            } else {
+                $last[] = $uri_string;
+            }
+        }
+
+        $keys = array_keys($uris);
+        sort($keys);
+
+        // Rebuild array of URIs.
+        $result = array();
+        foreach ($keys as $k) {
+            $new_uris = Auth_Yadis_array_scramble($uris[$k]);
+            $result = array_merge($result, $new_uris);
+        }
+
+        $result = array_merge($result,
+                              Auth_Yadis_array_scramble($last));
+
+        return $result;
+    }
+
+    /**
+     * Returns the "priority" attribute value of this <Service>
+     * element, if the attribute is present.  Returns null if not.
+     *
+     * @return mixed $result Null or integer, depending on whether
+     * this Service element has a 'priority' attribute.
+     */
+    function getPriority()
+    {
+        $attributes = $this->parser->attributes($this->element);
+
+        if (array_key_exists('priority', $attributes)) {
+            return intval($attributes['priority']);
+        }
+
+        return null;
+    }
+
+    /**
+     * Used to get XML elements from this object's <Service> element.
+     *
+     * This is what you should use to get all custom information out
+     * of this element. This is used by service filter functions to
+     * determine whether a service element contains specific tags,
+     * etc.  NOTE: this only considers elements which are direct
+     * children of the <Service> element for this object.
+     *
+     * @param string $name The name of the element to look for
+     * @return array $list An array of elements with the specified
+     * name which are direct children of the <Service> element.  The
+     * nodes returned by this function can be passed to $this->parser
+     * methods (see {@link Auth_Yadis_XMLParser}).
+     */
+    function getElements($name)
+    {
+        return $this->parser->evalXPath($name, $this->element);
+    }
+}
+
+/*
+ * Return the expiration date of this XRD element, or None if no
+ * expiration was specified.
+ *
+ * @param $default The value to use as the expiration if no expiration
+ * was specified in the XRD.
+ */
+function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
+{
+    $expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
+    if ($expires_element === null) {
+        return $default;
+    } else {
+        $expires_string = $expires_element->text;
+
+        // Will raise ValueError if the string is not the expected
+        // format
+        $t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
+
+        if ($t === false) {
+            return false;
+        }
+
+        // [int $hour [, int $minute [, int $second [,
+        //  int $month [, int $day [, int $year ]]]]]]
+        return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
+                      $t['tm_mon'], $t['tm_day'], $t['tm_year']);
+    }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre>  $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Auth_Yadis_XRDS will be returned.  Otherwise, null will be
+ * returned.  This class is used by the Auth_Yadis_Yadis::discover
+ * method.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XRDS {
+
+    /**
+     * Instantiate a Auth_Yadis_XRDS object.  Requires an XPath
+     * instance which has been used to parse a valid XRDS document.
+     */
+    function Auth_Yadis_XRDS($xmlParser, $xrdNodes)
+    {
+        $this->parser = $xmlParser;
+        $this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
+        $this->allXrdNodes = $xrdNodes;
+        $this->serviceList = array();
+        $this->_parse();
+    }
+
+    /**
+     * Parse an XML string (XRDS document) and return either a
+     * Auth_Yadis_XRDS object or null, depending on whether the
+     * XRDS XML is valid.
+     *
+     * @param string $xml_string An XRDS XML string.
+     * @return mixed $xrds An instance of Auth_Yadis_XRDS or null,
+     * depending on the validity of $xml_string
+     */
+    static function parseXRDS($xml_string, $extra_ns_map = null)
+    {
+        $_null = null;
+
+        if (!$xml_string) {
+            return $_null;
+        }
+
+        $parser = Auth_Yadis_getXMLParser();
+
+        $ns_map = Auth_Yadis_getNSMap();
+
+        if ($extra_ns_map && is_array($extra_ns_map)) {
+            $ns_map = array_merge($ns_map, $extra_ns_map);
+        }
+
+        if (!($parser && $parser->init($xml_string, $ns_map))) {
+            return $_null;
+        }
+
+        // Try to get root element.
+        $root = $parser->evalXPath('/xrds:XRDS[1]');
+        if (!$root) {
+            return $_null;
+        }
+
+        if (is_array($root)) {
+            $root = $root[0];
+        }
+
+        $attrs = $parser->attributes($root);
+
+        if (array_key_exists('xmlns:xrd', $attrs) &&
+            $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+            return $_null;
+        } else if (array_key_exists('xmlns', $attrs) &&
+                   preg_match('/xri/', $attrs['xmlns']) &&
+                   $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+            return $_null;
+        }
+
+        // Get the last XRD node.
+        $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+        if (!$xrd_nodes) {
+            return $_null;
+        }
+
+        $xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
+        return $xrds;
+    }
+
+    /**
+     * @access private
+     */
+    function _addService($priority, $service)
+    {
+        $priority = intval($priority);
+
+        if (!array_key_exists($priority, $this->serviceList)) {
+            $this->serviceList[$priority] = array();
+        }
+
+        $this->serviceList[$priority][] = $service;
+    }
+
+    /**
+     * Creates the service list using nodes from the XRDS XML
+     * document.
+     *
+     * @access private
+     */
+    function _parse()
+    {
+        $this->serviceList = array();
+
+        $services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
+
+        foreach ($services as $node) {
+            $s = new Auth_Yadis_Service();
+            $s->element = $node;
+            $s->parser = $this->parser;
+
+            $priority = $s->getPriority();
+
+            if ($priority === null) {
+                $priority = SERVICES_YADIS_MAX_PRIORITY;
+            }
+
+            $this->_addService($priority, $s);
+        }
+    }
+
+    /**
+     * Returns a list of service objects which correspond to <Service>
+     * elements in the XRDS XML document for this object.
+     *
+     * Optionally, an array of filter callbacks may be given to limit
+     * the list of returned service objects.  Furthermore, the default
+     * mode is to return all service objects which match ANY of the
+     * specified filters, but $filter_mode may be
+     * SERVICES_YADIS_MATCH_ALL if you want to be sure that the
+     * returned services match all the given filters.  See {@link
+     * Auth_Yadis_Yadis} for detailed usage information on filter
+     * functions.
+     *
+     * @param mixed $filters An array of callbacks to filter the
+     * returned services, or null if all services are to be returned.
+     * @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
+     * SERVICES_YADIS_MATCH_ANY, depending on whether the returned
+     * services should match ALL or ANY of the specified filters,
+     * respectively.
+     * @return mixed $services An array of {@link
+     * Auth_Yadis_Service} objects if $filter_mode is a valid
+     * mode; null if $filter_mode is an invalid mode (i.e., not
+     * SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
+     */
+    function services($filters = null,
+                      $filter_mode = SERVICES_YADIS_MATCH_ANY)
+    {
+
+        $pri_keys = array_keys($this->serviceList);
+        sort($pri_keys, SORT_NUMERIC);
+
+        // If no filters are specified, return the entire service
+        // list, ordered by priority.
+        if (!$filters ||
+            (!is_array($filters))) {
+
+            $result = array();
+            foreach ($pri_keys as $pri) {
+                $result = array_merge($result, $this->serviceList[$pri]);
+            }
+
+            return $result;
+        }
+
+        // If a bad filter mode is specified, return null.
+        if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
+                                          SERVICES_YADIS_MATCH_ALL))) {
+            return null;
+        }
+
+        // Otherwise, use the callbacks in the filter list to
+        // determine which services are returned.
+        $filtered = array();
+
+        foreach ($pri_keys as $priority_value) {
+            $service_obj_list = $this->serviceList[$priority_value];
+
+            foreach ($service_obj_list as $service) {
+
+                $matches = 0;
+
+                foreach ($filters as $filter) {
+
+                    if (call_user_func_array($filter, array($service))) {
+                        $matches++;
+
+                        if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
+                            $pri = $service->getPriority();
+                            if ($pri === null) {
+                                $pri = SERVICES_YADIS_MAX_PRIORITY;
+                            }
+
+                            if (!array_key_exists($pri, $filtered)) {
+                                $filtered[$pri] = array();
+                            }
+
+                            $filtered[$pri][] = $service;
+                            break;
+                        }
+                    }
+                }
+
+                if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
+                    ($matches == count($filters))) {
+
+                    $pri = $service->getPriority();
+                    if ($pri === null) {
+                        $pri = SERVICES_YADIS_MAX_PRIORITY;
+                    }
+
+                    if (!array_key_exists($pri, $filtered)) {
+                        $filtered[$pri] = array();
+                    }
+                    $filtered[$pri][] = $service;
+                }
+            }
+        }
+
+        $pri_keys = array_keys($filtered);
+        sort($pri_keys, SORT_NUMERIC);
+
+        $result = array();
+        foreach ($pri_keys as $pri) {
+            $result = array_merge($result, $filtered[$pri]);
+        }
+
+        return $result;
+    }
+}
+
+

file:b/lib/openid.php (new)
--- /dev/null
+++ b/lib/openid.php
@@ -1,1 +1,782 @@
-
+<?php
+/**
+ * This class provides a simple interface for OpenID (1.1 and 2.0) authentication.
+ * Supports Yadis discovery.
+ * The authentication process is stateless/dumb.
+ *
+ * Usage:
+ * Sign-on with OpenID is a two step process:
+ * Step one is authentication with the provider:
+ * <code>
+ * $openid = new LightOpenID('my-host.example.org');
+ * $openid->identity = 'ID supplied by user';
+ * header('Location: ' . $openid->authUrl());
+ * </code>
+ * The provider then sends various parameters via GET, one of them is openid_mode.
+ * Step two is verification:
+ * <code>
+ * if ($this->data['openid_mode']) {
+ *     $openid = new LightOpenID('my-host.example.org');
+ *     echo $openid->validate() ? 'Logged in.' : 'Failed';
+ * }
+ * </code>
+ *
+ * Change the 'my-host.example.org' to your domain name. Do NOT use $_SERVER['HTTP_HOST']
+ * for that, unless you know what you are doing.
+ *
+ * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias).
+ * The default values for those are:
+ * $openid->realm     = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
+ * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI'];
+ * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess.
+ *
+ * AX and SREG extensions are supported.
+ * To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl().
+ * These are arrays, with values being AX schema paths (the 'path' part of the URL).
+ * For example:
+ *   $openid->required = array('namePerson/friendly', 'contact/email');
+ *   $openid->optional = array('namePerson/first');
+ * If the server supports only SREG or OpenID 1.1, these are automaticaly
+ * mapped to SREG names, so that user doesn't have to know anything about the server.
+ *
+ * To get the values, use $openid->getAttributes().
+ *
+ *
+ * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled.
+ * @author Mewp
+ * @copyright Copyright (c) 2010, Mewp
+ * @license http://www.opensource.org/licenses/mit-license.php MIT
+ */
+class LightOpenID
+{
+    public $returnUrl
+         , $required = array()
+         , $optional = array()
+         , $verify_peer = null
+         , $capath = null
+         , $cainfo = null
+         , $data;
+    private $identity, $claimed_id;
+    protected $server, $version, $trustRoot, $aliases, $identifier_select = false
+            , $ax = false, $sreg = false, $setup_url = null;
+    static protected $ax_to_sreg = array(
+        'namePerson/friendly'     => 'nickname',
+        'contact/email'           => 'email',
+        'namePerson'              => 'fullname',
+        'birthDate'               => 'dob',
+        'person/gender'           => 'gender',
+        'contact/postalCode/home' => 'postcode',
+        'contact/country/home'    => 'country',
+        'pref/language'           => 'language',
+        'pref/timezone'           => 'timezone',
+        );
+
+    function __construct($host)
+    {
+        $this->trustRoot = (strpos($host, '://') ? $host : 'http://' . $host);
+        if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
+            || (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
+            && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
+        ) {
+            $this->trustRoot = (strpos($host, '://') ? $host : 'https://' . $host);
+        }
+
+        if(($host_end = strpos($this->trustRoot, '/', 8)) !== false) {
+            $this->trustRoot = substr($this->trustRoot, 0, $host_end);
+        }
+
+        $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?');
+        $this->returnUrl = $this->trustRoot . $uri;
+
+        $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET;
+
+        if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) {
+            throw new ErrorException('You must have either https wrappers or curl enabled.');
+        }
+    }
+
+    function __set($name, $value)
+    {
+        switch ($name) {
+        case 'identity':
+            if (strlen($value = trim((String) $value))) {
+                if (preg_match('#^xri:/*#i', $value, $m)) {
+                    $value = substr($value, strlen($m[0]));
+                } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) {
+                    $value = "http://$value";
+                }
+                if (preg_match('#^https?://[^/]+$#i', $value, $m)) {
+                    $value .= '/';
+                }
+            }
+            $this->$name = $this->claimed_id = $value;
+            break;
+        case 'trustRoot':
+        case 'realm':
+            $this->trustRoot = trim($value);
+        }
+    }
+
+    function __get($name)
+    {
+        switch ($name) {
+        case 'identity':
+            # We return claimed_id instead of identity,
+            # because the developer should see the claimed identifier,
+            # i.e. what he set as identity, not the op-local identifier (which is what we verify)
+            return $this->claimed_id;
+        case 'trustRoot':
+        case 'realm':
+            return $this->trustRoot;
+        case 'mode':
+            return empty($this->data['openid_mode']) ? null : $this->data['openid_mode'];
+        }
+    }
+
+    /**
+     * Checks if the server specified in the url exists.
+     *
+     * @param $url url to check
+     * @return true, if the server exists; false otherwise
+     */
+    function hostExists($url)
+    {
+        if (strpos($url, '/') === false) {
+            $server = $url;
+        } else {
+            $server = @parse_url($url, PHP_URL_HOST);
+        }
+
+        if (!$server) {
+            return false;
+        }
+
+        return !!gethostbynamel($server);
+    }
+
+    protected function request_curl($url, $method='GET', $params=array())
+    {
+        $params = http_build_query($params, '', '&');
+        $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : ''));
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($curl, CURLOPT_HEADER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*'));
+
+        if($this->verify_peer !== null) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer);
+            if($this->capath) {
+                curl_setopt($curl, CURLOPT_CAPATH, $this->capath);
+            }
+
+            if($this->cainfo) {
+                curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
+            }
+        }
+
+        if ($method == 'POST') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
+        } elseif ($method == 'HEAD') {
+            curl_setopt($curl, CURLOPT_HEADER, true);
+            curl_setopt($curl, CURLOPT_NOBODY, true);
+        } else {
+            curl_setopt($curl, CURLOPT_HTTPGET, true);
+        }
+        $response = curl_exec($curl);
+
+        if($method == 'HEAD') {
+            $headers = array();
+            foreach(explode("\n", $response) as $header) {
+                $pos = strpos($header,':');
+                $name = strtolower(trim(substr($header, 0, $pos)));
+                $headers[$name] = trim(substr($header, $pos+1));
+            }
+
+            # Updating claimed_id in case of redirections.
+            $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
+            if($effective_url != $url) {
+                $this->identity = $this->claimed_id = $effective_url;
+            }
+
+            return $headers;
+        }
+
+        if (curl_errno($curl)) {
+            throw new ErrorException(curl_error($curl), curl_errno($curl));
+        }
+
+        return $response;
+    }
+
+    protected function request_streams($url, $method='GET', $params=array())
+    {
+        if(!$this->hostExists($url)) {
+            throw new ErrorException("Could not connect to $url.", 404);
+        }
+
+        $params = http_build_query($params, '', '&');
+        switch($method) {
+        case 'GET':
+            $opts = array(
+                'http' => array(
+                    'method' => 'GET',
+                    'header' => 'Accept: application/xrds+xml, */*',
+                    'ignore_errors' => true,
+                ), 'ssl' => array(
+                    'CN_match' => parse_url($url, PHP_URL_HOST),
+                ),
+            );
+            $url = $url . ($params ? '?' . $params : '');
+            break;
+        case 'POST':
+            $opts = array(
+                'http' => array(
+                    'method' => 'POST',
+                    'header'  => 'Content-type: application/x-www-form-urlencoded',
+                    'content' => $params,
+                    'ignore_errors' => true,
+                ), 'ssl' => array(
+                    'CN_match' => parse_url($url, PHP_URL_HOST),
+                ),
+            );
+            break;
+        case 'HEAD':
+            # We want to send a HEAD request,
+            # but since get_headers doesn't accept $context parameter,
+            # we have to change the defaults.
+            $default = stream_context_get_options(stream_context_get_default());
+            stream_context_get_default(
+                array(
+                    'http' => array(
+                        'method' => 'HEAD',
+                        'header' => 'Accept: application/xrds+xml, */*',
+                        'ignore_errors' => true,
+                    ), 'ssl' => array(
+                        'CN_match' => parse_url($url, PHP_URL_HOST),
+                    ),
+                )
+            );
+
+            $url = $url . ($params ? '?' . $params : '');
+            $headers_tmp = get_headers ($url);
+            if(!$headers_tmp) {
+                return array();
+            }
+
+            # Parsing headers.
+            $headers = array();
+            foreach($headers_tmp as $header) {
+                $pos = strpos($header,':');
+                $name = strtolower(trim(substr($header, 0, $pos)));
+                $headers[$name] = trim(substr($header, $pos+1));
+
+                # Following possible redirections. The point is just to have
+                # claimed_id change with them, because get_headers() will
+                # follow redirections automatically.
+                # We ignore redirections with relative paths.
+                # If any known provider uses them, file a bug report.
+                if($name == 'location') {
+                    if(strpos($headers[$name], 'http') === 0) {
+                        $this->identity = $this->claimed_id = $headers[$name];
+                    } elseif($headers[$name][0] == '/') {
+                        $parsed_url = parse_url($this->claimed_id);
+                        $this->identity =
+                        $this->claimed_id = $parsed_url['scheme'] . '://'
+                                          . $parsed_url['host']
+                                          . $headers[$name];
+                    }
+                }
+            }
+
+            # And restore them.
+            stream_context_get_default($default);
+            return $headers;
+        }
+
+        if($this->verify_peer) {
+            $opts['ssl'] += array(
+                'verify_peer' => true,
+                'capath'      => $this->capath,
+                'cafile'      => $this->cainfo,
+            );
+        }
+
+        $context = stream_context_create ($opts);
+
+        return file_get_contents($url, false, $context);
+    }
+
+    protected function request($url, $method='GET', $params=array())
+    {
+        if (function_exists('curl_init')
+            && (!in_array('https', stream_get_wrappers()) || !ini_get('safe_mode') && !ini_get('open_basedir'))
+        ) {
+            return $this->request_curl($url, $method, $params);
+        }
+        return $this->request_streams($url, $method, $params);
+    }
+
+    protected function build_url($url, $parts)
+    {
+        if (isset($url['query'], $parts['query'])) {
+            $parts['query'] = $url['query'] . '&' . $parts['query'];
+        }
+
+        $url = $parts + $url;
+        $url = $url['scheme'] . '://'
+             . (empty($url['username'])?''
+                 :(empty($url['password'])? "{$url['username']}@"
+                 :"{$url['username']}:{$url['password']}@"))
+             . $url['host']
+             . (empty($url['port'])?'':":{$url['port']}")
+             . (empty($url['path'])?'':$url['path'])
+             . (empty($url['query'])?'':"?{$url['query']}")
+             . (empty($url['fragment'])?'':"#{$url['fragment']}");
+        return $url;
+    }
+
+    /**
+     * Helper function used to scan for <meta>/<link> tags and extract information
+     * from them
+     */
+    protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName)
+    {
+        preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1);
+        preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2);
+
+        $result = array_merge($matches1[1], $matches2[1]);
+        return empty($result)?false:$result[0];
+    }
+
+    /**
+     * Performs Yadis and HTML discovery. Normally not used.
+     * @param $url Identity URL.
+     * @return String OP Endpoint (i.e. OpenID provider address).
+     * @throws ErrorException
+     */
+    function discover($url)
+    {
+        if (!$url) throw new ErrorException('No identity supplied.');
+        # Use xri.net proxy to resolve i-name identities
+        if (!preg_match('#^https?:#', $url)) {
+            $url = "https://xri.net/$url";
+        }
+
+        # We save the original url in case of Yadis discovery failure.
+        # It can happen when we'll be lead to an XRDS document
+        # which does not have any OpenID2 services.
+        $originalUrl = $url;
+
+        # A flag to disable yadis discovery in case of failure in headers.
+        $yadis = true;
+
+        # We'll jump a maximum of 5 times, to avoid endless redirections.
+        for ($i = 0; $i < 5; $i ++) {
+            if ($yadis) {
+                $headers = $this->request($url, 'HEAD');
+
+                $next = false;
+                if (isset($headers['x-xrds-location'])) {
+                    $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location'])));
+                    $next = true;
+                }
+
+                if (isset($headers['content-type'])
+                    && (strpos($headers['content-type'], 'application/xrds+xml') !== false
+                        || strpos($headers['content-type'], 'text/xml') !== false)
+                ) {
+                    # Apparently, some providers return XRDS documents as text/html.
+                    # While it is against the spec, allowing this here shouldn't break
+                    # compatibility with anything.
+                    # ---
+                    # Found an XRDS document, now let's find the server, and optionally delegate.
+                    $content = $this->request($url, 'GET');
+
+                    preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m);
+                    foreach($m[1] as $content) {
+                        $content = ' ' . $content; # The space is added, so that strpos doesn't return 0.
+
+                        # OpenID 2
+                        $ns = preg_quote('http://specs.openid.net/auth/2.0/');
+                        if(preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) {
+                            if ($type[1] == 'server') $this->identifier_select = true;
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # Does the server advertise support for either AX or SREG?
+                            $this->ax   = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>');
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[2])) $this->identity = trim($delegate[2]);
+                            $this->version = 2;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+
+                        # OpenID 1.1
+                        $ns = preg_quote('http://openid.net/signon/1.1');
+                        if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) {
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # AX can be used only with OpenID 2.0, so checking only SREG
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[1])) $this->identity = $delegate[1];
+                            $this->version = 1;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+                    }
+
+                    $next = true;
+                    $yadis = false;
+                    $url = $originalUrl;
+                    $content = null;
+                    break;
+                }
+                if ($next) continue;
+
+                # There are no relevant information in headers, so we search the body.
+                $content = $this->request($url, 'GET');
+                $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content');
+                if ($location) {
+                    $url = $this->build_url(parse_url($url), parse_url($location));
+                    continue;
+                }
+            }
+
+            if (!$content) $content = $this->request($url, 'GET');
+
+            # At this point, the YADIS Discovery has failed, so we'll switch
+            # to openid2 HTML discovery, then fallback to openid 1.1 discovery.
+            $server   = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href');
+            $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href');
+            $this->version = 2;
+
+            if (!$server) {
+                # The same with openid 1.1
+                $server   = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href');
+                $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href');
+                $this->version = 1;
+            }
+
+            if ($server) {
+                # We found an OpenID2 OP Endpoint
+                if ($delegate) {
+                    # We have also found an OP-Local ID.
+                    $this->identity = $delegate;
+                }
+                $this->server = $server;
+                return $server;
+            }
+
+            throw new ErrorException("No OpenID Server found at $url", 404);
+        }
+        throw new ErrorException('Endless redirection!', 500);
+    }
+
+    protected function sregParams()
+    {
+        $params = array();
+        # We always use SREG 1.1, even if the server is advertising only support for 1.0.
+        # That's because it's fully backwards compatibile with 1.0, and some providers
+        # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com
+        $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
+        if ($this->required) {
+            $params['openid.sreg.required'] = array();
+            foreach ($this->required as $required) {
+                if (!isset(self::$ax_to_sreg[$required])) continue;
+                $params['openid.sreg.required'][] = self::$ax_to_sreg[$required];
+            }
+            $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']);
+        }
+
+        if ($this->optional) {
+            $params['openid.sreg.optional'] = array();
+            foreach ($this->optional as $optional) {
+                if (!isset(self::$ax_to_sreg[$optional])) continue;
+                $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional];
+            }
+            $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']);
+        }
+        return $params;
+    }
+
+    protected function axParams()
+    {
+        $params = array();
+        if ($this->required || $this->optional) {
+            $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0';
+            $params['openid.ax.mode'] = 'fetch_request';
+            $this->aliases  = array();
+            $counts   = array();
+            $required = array();
+            $optional = array();
+            foreach (array('required','optional') as $type) {
+                foreach ($this->$type as $alias => $field) {
+                    if (is_int($alias)) $alias = strtr($field, '/', '_');
+                    $this->aliases[$alias] = 'http://axschema.org/' . $field;
+                    if (empty($counts[$alias])) $counts[$alias] = 0;
+                    $counts[$alias] += 1;
+                    ${$type}[] = $alias;
+                }
+            }
+            foreach ($this->aliases as $alias => $ns) {
+                $params['openid.ax.type.' . $alias] = $ns;
+            }
+            foreach ($counts as $alias => $count) {
+                if ($count == 1) continue;
+                $params['openid.ax.count.' . $alias] = $count;
+            }
+
+            # Don't send empty ax.requied and ax.if_available.
+            # Google and possibly other providers refuse to support ax when one of these is empty.
+            if($required) {
+                $params['openid.ax.required'] = implode(',', $required);
+            }
+            if($optional) {
+                $params['openid.ax.if_available'] = implode(',', $optional);
+            }
+        }
+        return $params;
+    }
+
+    protected function authUrl_v1($immediate)
+    {
+	$returnUrl = $this->returnUrl;
+        # If we have an openid.delegate that is different from our claimed id,
+        # we need to somehow preserve the claimed id between requests.
+        # The simplest way is to just send it along with the return_to url.
+        if($this->identity != $this->claimed_id) {
+            $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id;
+        }
+
+        $params = array(
+            'openid.return_to'  => $returnUrl,
+            'openid.mode'       => $immediate ? 'checkid_immediate' : 'checkid_setup',
+            'openid.identity'   => $this->identity,
+            'openid.trust_root' => $this->trustRoot,
+            ) + $this->sregParams();
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    protected function authUrl_v2($immediate)
+    {
+        $params = array(
+            'openid.ns'          => 'http://specs.openid.net/auth/2.0',
+            'openid.mode'        => $immediate ? 'checkid_immediate' : 'checkid_setup',
+            'openid.return_to'   => $this->returnUrl,
+            'openid.realm'       => $this->trustRoot,
+        );
+        if ($this->ax) {
+            $params += $this->axParams();
+        }
+        if ($this->sreg) {
+            $params += $this->sregParams();
+        }
+        if (!$this->ax && !$this->sreg) {
+            # If OP doesn't advertise either SREG, nor AX, let's send them both
+            # in worst case we don't get anything in return.
+            $params += $this->axParams() + $this->sregParams();
+        }
+
+        if ($this->identifier_select) {
+            $params['openid.identity'] = $params['openid.claimed_id']
+                 = 'http://specs.openid.net/auth/2.0/identifier_select';
+        } else {
+            $params['openid.identity'] = $this->identity;
+            $params['openid.claimed_id'] = $this->claimed_id;
+        }
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    /**
+     * Returns authentication url. Usually, you want to redirect your user to it.
+     * @return String The authentication url.
+     * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1.
+     * @throws ErrorException
+     */
+    function authUrl($immediate = false)
+    {
+        if ($this->setup_url && !$immediate) return $this->setup_url;
+        if (!$this->server) $this->discover($this->identity);
+
+        if ($this->version == 2) {
+            return $this->authUrl_v2($immediate);
+        }
+        return $this->authUrl_v1($immediate);
+    }
+
+    /**
+     * Performs OpenID verification with the OP.
+     * @return Bool Whether the verification was successful.
+     * @throws ErrorException
+     */
+    function validate()
+    {
+        # If the request was using immediate mode, a failure may be reported
+        # by presenting user_setup_url (for 1.1) or reporting
+        # mode 'setup_needed' (for 2.0). Also catching all modes other than
+        # id_res, in order to avoid throwing errors.
+        if(isset($this->data['openid_user_setup_url'])) {
+            $this->setup_url = $this->data['openid_user_setup_url'];
+            return false;
+        }
+        if($this->mode != 'id_res') {
+            return false;
+        }
+
+        $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity'];
+        $params = array(
+            'openid.assoc_handle' => $this->data['openid_assoc_handle'],
+            'openid.signed'       => $this->data['openid_signed'],
+            'openid.sig'          => $this->data['openid_sig'],
+            );
+
+        if (isset($this->data['openid_ns'])) {
+            # We're dealing with an OpenID 2.0 server, so let's set an ns
+            # Even though we should know location of the endpoint,
+            # we still need to verify it by discovery, so $server is not set here
+            $params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
+        } elseif (isset($this->data['openid_claimed_id'])
+            && $this->data['openid_claimed_id'] != $this->data['openid_identity']
+        ) {
+            # If it's an OpenID 1 provider, and we've got claimed_id,
+            # we have to append it to the returnUrl, like authUrl_v1 does.
+            $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?')
+                             .  'openid.claimed_id=' . $this->claimed_id;
+        }
+
+        if ($this->data['openid_return_to'] != $this->returnUrl) {
+            # The return_to url must match the url of current request.
+            # I'm assuing that noone will set the returnUrl to something that doesn't make sense.
+            return false;
+        }
+
+        $server = $this->discover($this->claimed_id);
+
+        foreach (explode(',', $this->data['openid_signed']) as $item) {
+            # Checking whether magic_quotes_gpc is turned on, because
+            # the function may fail if it is. For example, when fetching
+            # AX namePerson, it might containg an apostrophe, which will be escaped.
+            # In such case, validation would fail, since we'd send different data than OP
+            # wants to verify. stripslashes() should solve that problem, but we can't
+            # use it when magic_quotes is off.
+            $value = $this->data['openid_' . str_replace('.','_',$item)];
+            $params['openid.' . $item] = get_magic_quotes_gpc() ? stripslashes($value) : $value;
+
+        }
+
+        $params['openid.mode'] = 'check_authentication';
+
+        $response = $this->request($server, 'POST', $params);
+
+        return preg_match('/is_valid\s*:\s*true/i', $response);
+    }
+
+    protected function getAxAttributes()
+    {
+        $alias = null;
+        if (isset($this->data['openid_ns_ax'])
+            && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0'
+        ) { # It's the most likely case, so we'll check it before
+            $alias = 'ax';
+        } else {
+            # 'ax' prefix is either undefined, or points to another extension,
+            # so we search for another prefix
+            foreach ($this->data as $key => $val) {
+                if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_'
+                    && $val == 'http://openid.net/srv/ax/1.0'
+                ) {
+                    $alias = substr($key, strlen('openid_ns_'));
+                    break;
+                }
+            }
+        }
+        if (!$alias) {
+            # An alias for AX schema has not been found,
+            # so there is no AX data in the OP's response
+            return array();
+        }
+
+        $attributes = array();
+        foreach (explode(',', $this->data['openid_signed']) as $key) {
+            $keyMatch = $alias . '.value.';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($this->data['openid_' . $alias . '_type_' . $key])) {
+                # OP is breaking the spec by returning a field without
+                # associated ns. This shouldn't happen, but it's better
+                # to check, than cause an E_NOTICE.
+                continue;
+            }
+            $value = $this->data['openid_' . $alias . '_value_' . $key];
+            $key = substr($this->data['openid_' . $alias . '_type_' . $key],
+                          strlen('http://axschema.org/'));
+
+            $attributes[$key] = $value;
+        }
+        return $attributes;
+    }
+
+    protected function getSregAttributes()
+    {
+        $attributes = array();
+        $sreg_to_ax = array_flip(self::$ax_to_sreg);
+        foreach (explode(',', $this->data['openid_signed']) as $key) {
+            $keyMatch = 'sreg.';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($sreg_to_ax[$key])) {
+                # The field name isn't part of the SREG spec, so we ignore it.
+                continue;
+            }
+            $attributes[$sreg_to_ax[$key]] = $this->data['openid_sreg_' . $key];
+        }
+        return $attributes;
+    }
+
+    /**
+     * Gets AX/SREG attributes provided by OP. should be used only after successful validaton.
+     * Note that it does not guarantee that any of the required/optional parameters will be present,
+     * or that there will be no other attributes besides those specified.
+     * In other words. OP may provide whatever information it wants to.
+     *     * SREG names will be mapped to AX names.
+     *     * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email'
+     * @see http://www.axschema.org/types/
+     */
+    function getAttributes()
+    {
+        if (isset($this->data['openid_ns'])
+            && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0'
+        ) { # OpenID 2.0
+            # We search for both AX and SREG attributes, with AX taking precedence.
+            return $this->getAxAttributes() + $this->getSregAttributes();
+        }
+        return $this->getSregAttributes();
+    }
+}
+