RequestData.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. *
  27. */
  28. using System;
  29. using System.IO;
  30. using System.Reflection;
  31. using System.Text;
  32. using System.Security.Cryptography;
  33. using System.Text.RegularExpressions;
  34. using System.Collections.Generic;
  35. using System.Collections.Specialized;
  36. using OpenSim.Framework.Servers;
  37. using libsecondlife;
  38. using System.Xml;
  39. namespace OpenSim.ApplicationPlugins.Rest.Inventory
  40. {
  41. /// <summary>
  42. /// This class represents the current REST request. It
  43. /// encapsulates the request/response state and takes care
  44. /// of response generation without exposing the REST handler
  45. /// to the actual mechanisms involved.
  46. ///
  47. /// This structure is created on entry to the Handler
  48. /// method and is disposed of upon return. It is part of
  49. /// the plug-in infrastructure, rather than the functionally
  50. /// specifici REST handler, and fundamental changes to
  51. /// this should be reflected in the Rest HandlerVersion. The
  52. /// object is instantiated, and may be extended by, any
  53. /// given handler. See the inventory handler for an example
  54. /// of this.
  55. ///
  56. /// If possible, the underlying request/response state is not
  57. /// changed until the handler explicitly issues a Respond call.
  58. /// This ensures that the request/response pair can be safely
  59. /// processed by subsequent, unrelated, handlers even id the
  60. /// agent handler had completed much of its processing. Think
  61. /// of it as a transactional req/resp capability.
  62. /// </summary>
  63. internal class RequestData
  64. {
  65. // HTTP Server interface data
  66. internal OSHttpRequest request = null;
  67. internal OSHttpResponse response = null;
  68. // Request lifetime values
  69. internal NameValueCollection headers = null;
  70. internal List<string> removed_headers = null;
  71. internal byte[] buffer = null;
  72. internal string body = null;
  73. internal string html = null;
  74. internal string entity = null;
  75. internal string path = null;
  76. internal string method = null;
  77. internal string statusDescription = null;
  78. internal string redirectLocation = null;
  79. internal string[] pathNodes = null;
  80. internal string[] parameters = null;
  81. internal int statusCode = 0;
  82. internal bool handled = false;
  83. internal LLUUID uuid = LLUUID.Zero;
  84. internal Encoding encoding = Rest.Encoding;
  85. internal Uri uri = null;
  86. internal string query = null;
  87. internal bool fail = false;
  88. internal string hostname = "localhost";
  89. internal int port = 80;
  90. internal string prefix = Rest.UrlPathSeparator;
  91. // Authentication related state
  92. internal bool authenticated = false;
  93. internal string scheme = Rest.AS_DIGEST;
  94. internal string realm = Rest.Realm;
  95. internal string domain = null;
  96. internal string nonce = null;
  97. internal string cnonce = null;
  98. internal string qop = Rest.Qop_Auth;
  99. internal string opaque = null;
  100. internal string stale = null;
  101. internal string algorithm = Rest.Digest_MD5;
  102. internal string authParms = null;
  103. internal string authPrefix = null;
  104. internal string userName = String.Empty;
  105. internal string userPass = String.Empty;
  106. internal LLUUID client = LLUUID.Zero;
  107. // XML related state
  108. internal XmlWriter writer = null;
  109. internal XmlReader reader = null;
  110. // Internal working state
  111. private StringBuilder sbuilder = new StringBuilder(1024);
  112. private MemoryStream xmldata = null;
  113. private static readonly string[] EmptyPath = { String.Empty };
  114. // Session related tables. These are only needed if QOP is set to "auth-sess"
  115. // and for now at least, it is not. Session related authentication is of
  116. // questionable merit in the context of REST anyway, but it is, arguably, more
  117. // secure.
  118. private static Dictionary<string,string> cntable = new Dictionary<string,string>();
  119. private static Dictionary<string,string> sktable = new Dictionary<string,string>();
  120. // This dictionary is used to keep track fo all of the parameters discovered
  121. // when the authorisation header is anaylsed.
  122. private Dictionary<string,string> authparms = new Dictionary<string,string>();
  123. // These regular expressions are used to decipher the various header entries.
  124. private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
  125. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  126. private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
  127. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  128. private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>\\S+)\"",
  129. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  130. private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
  131. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  132. private static Regex reuserPass = new Regex("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\S*)",
  133. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  134. // For efficiency, we create static instances of these objects
  135. private static MD5 md5hash = MD5.Create();
  136. private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
  137. // Constructor
  138. internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix)
  139. {
  140. request = p_request;
  141. response = p_response;
  142. sbuilder.Length = 0;
  143. encoding = request.ContentEncoding;
  144. if (encoding == null)
  145. {
  146. encoding = Rest.Encoding;
  147. }
  148. method = request.HttpMethod.ToLower();
  149. initUrl();
  150. initParameters(qprefix.Length);
  151. }
  152. // Just for convenience...
  153. internal string MsgId
  154. {
  155. get { return Rest.MsgId; }
  156. }
  157. // Defer authentication check until requested
  158. internal bool IsAuthenticated
  159. {
  160. get
  161. {
  162. if (Rest.Authenticate)
  163. {
  164. if (!authenticated)
  165. {
  166. authenticate();
  167. }
  168. return authenticated;
  169. }
  170. else return true;
  171. }
  172. }
  173. /// <summary>
  174. /// The REST handler has requested authentication. Authentication
  175. /// is considered to be with respect to the current values for
  176. /// Realm, domain, etc.
  177. ///
  178. /// This method checks to see if the current request is already
  179. /// authenticated for this domain. If it is, then it returns
  180. /// true. If it is not, then it issues a challenge to the client
  181. /// and responds negatively to the request.
  182. /// </summary>
  183. private void authenticate()
  184. {
  185. string authdata = request.Headers.Get("Authorization");
  186. string reqscheme = String.Empty;
  187. // If we don't have an authorization header, then this
  188. // user is certainly not authorized. This is the typical
  189. // pivot for the 1st request by a client.
  190. if (authdata == null)
  191. {
  192. Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
  193. DoChallenge();
  194. }
  195. // So, we have authentication data, now we have to check to
  196. // see what we got and whether or not it is valid for the
  197. // current domain. To do this we need to interpret the data
  198. // provided in the Authorization header. First we need to
  199. // identify the scheme being used and route accordingly.
  200. MatchCollection matches = schema.Matches(authdata);
  201. foreach (Match m in matches)
  202. {
  203. Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
  204. reqscheme = m.Groups["scheme"].Value.ToLower();
  205. }
  206. // If we want a specific authentication mechanism, make sure
  207. // we get it.
  208. if (scheme != null && scheme.ToLower() != reqscheme)
  209. {
  210. Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", MsgId);
  211. DoChallenge();
  212. }
  213. // In the future, these could be made into plug-ins...
  214. // But for now at least we have no reason to use anything other
  215. // then MD5. TLS/SSL are taken care of elsewhere.
  216. switch (reqscheme)
  217. {
  218. case "digest" :
  219. Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
  220. DoDigest(authdata);
  221. break;
  222. case "basic" :
  223. Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
  224. DoBasic(authdata);
  225. break;
  226. }
  227. // If the current header is invalid, then a challenge is still needed.
  228. if (!authenticated)
  229. {
  230. Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
  231. DoChallenge();
  232. }
  233. }
  234. /// <summary>
  235. /// Construct the necessary WWW-Authenticate headers and fail the request
  236. /// with a NOT AUTHORIZED response. The parameters are the union of values
  237. /// required by the supported schemes.
  238. /// </summary>
  239. private void DoChallenge()
  240. {
  241. Flush();
  242. nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
  243. Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
  244. Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized);
  245. }
  246. /// <summary>
  247. /// Interpret a BASIC authorization claim
  248. /// This is here for completeness, it is not used.
  249. /// </summary>
  250. private void DoBasic(string authdata)
  251. {
  252. string response = null;
  253. MatchCollection matches = basicParms.Matches(authdata);
  254. // In the case of basic authentication there is
  255. // only expected to be a single argument.
  256. foreach (Match m in matches)
  257. {
  258. authparms.Add("response",m.Groups["pval"].Value);
  259. Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
  260. MsgId, "response", m.Groups["pval"].Value);
  261. }
  262. // Did we get a valid response?
  263. if (authparms.TryGetValue("response", out response))
  264. {
  265. // Decode
  266. response = Rest.Base64ToString(response);
  267. Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
  268. // Extract user & password
  269. Match m = reuserPass.Match(response);
  270. userName = m.Groups["user"].Value;
  271. userPass = m.Groups["pass"].Value;
  272. // Validate against user database
  273. authenticated = Validate(userName,userPass);
  274. }
  275. }
  276. /// <summary>
  277. /// This is an RFC2617 compliant HTTP MD5 Digest authentication
  278. /// implementation. It has been tested with Firefox, Java HTTP client,
  279. /// and Miscrosoft's Internet Explorer V7.
  280. /// </summary>
  281. private void DoDigest(string authdata)
  282. {
  283. string response = null;
  284. MatchCollection matches = digestParm1.Matches(authdata);
  285. // Collect all of the supplied parameters and store them
  286. // in a dictionary (for ease of access)
  287. foreach (Match m in matches)
  288. {
  289. authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
  290. Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
  291. MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
  292. }
  293. // And pick up any tokens too
  294. matches = digestParm2.Matches(authdata);
  295. foreach (Match m in matches)
  296. {
  297. authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
  298. Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
  299. MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
  300. }
  301. // A response string MUST be returned, otherwise we are
  302. // NOT authenticated.
  303. Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
  304. if (authparms.TryGetValue("response", out response))
  305. {
  306. string temp = null;
  307. do
  308. {
  309. string nck = null;
  310. string ncl = null;
  311. // The userid is sent in clear text. Needed for the
  312. // verification.
  313. authparms.TryGetValue("username", out userName);
  314. // All URI's of which this is a prefix are
  315. // optimistically considered to be authenticated by the
  316. // client. This is also needed to verify the response.
  317. authparms.TryGetValue("uri", out authPrefix);
  318. // There MUST be a nonce string present. We're not preserving any server
  319. // side state and we can;t validate the MD5 unless the lcient returns it
  320. // to us, as it should.
  321. if (!authparms.TryGetValue("nonce", out nonce))
  322. {
  323. Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
  324. break;
  325. }
  326. // If there is an opaque string present, it had better
  327. // match what we sent.
  328. if (authparms.TryGetValue("opaque", out temp))
  329. {
  330. if (temp != opaque)
  331. {
  332. Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
  333. break;
  334. }
  335. }
  336. // If an algorithm string is present, it had better
  337. // match what we sent.
  338. if (authparms.TryGetValue("algorithm", out temp))
  339. {
  340. if (temp != algorithm)
  341. {
  342. Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
  343. break;
  344. }
  345. }
  346. // Quality of protection considerations...
  347. if (authparms.TryGetValue("qop", out temp))
  348. {
  349. qop = temp.ToLower(); // replace with actual value used
  350. // if QOP was specified then
  351. // these MUST be present.
  352. if (!authparms.ContainsKey("cnonce"))
  353. {
  354. Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
  355. break;
  356. }
  357. cnonce = authparms["cnonce"];
  358. if (!authparms.ContainsKey("nc"))
  359. {
  360. Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
  361. break;
  362. }
  363. nck = authparms["nc"];
  364. if (cntable.TryGetValue(cnonce, out ncl))
  365. {
  366. if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck))
  367. {
  368. Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
  369. break;
  370. }
  371. cntable[cnonce] = nck;
  372. }
  373. else
  374. {
  375. lock (cntable) cntable.Add(cnonce, nck);
  376. }
  377. }
  378. else
  379. {
  380. qop = String.Empty;
  381. // if QOP was not specified then
  382. // these MUST NOT be present.
  383. if (authparms.ContainsKey("cnonce"))
  384. {
  385. Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
  386. break;
  387. }
  388. if (authparms.ContainsKey("nc"))
  389. {
  390. Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
  391. break;
  392. }
  393. }
  394. // Validate the supplied userid/password info
  395. authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
  396. }
  397. while (false);
  398. }
  399. }
  400. // Indicate that authentication is required
  401. internal void Challenge(string scheme, string realm, string domain, string nonce,
  402. string opaque, string stale, string alg,
  403. string qop, string auth)
  404. {
  405. sbuilder.Length = 0;
  406. if (scheme == null || scheme == Rest.AS_DIGEST)
  407. {
  408. sbuilder.Append(Rest.AS_DIGEST);
  409. sbuilder.Append(" ");
  410. if (realm != null)
  411. {
  412. sbuilder.Append("realm=");
  413. sbuilder.Append(Rest.CS_DQUOTE);
  414. sbuilder.Append(realm);
  415. sbuilder.Append(Rest.CS_DQUOTE);
  416. sbuilder.Append(Rest.CS_COMMA);
  417. }
  418. if (nonce != null)
  419. {
  420. sbuilder.Append("nonce=");
  421. sbuilder.Append(Rest.CS_DQUOTE);
  422. sbuilder.Append(nonce);
  423. sbuilder.Append(Rest.CS_DQUOTE);
  424. sbuilder.Append(Rest.CS_COMMA);
  425. }
  426. if (opaque != null)
  427. {
  428. sbuilder.Append("opaque=");
  429. sbuilder.Append(Rest.CS_DQUOTE);
  430. sbuilder.Append(opaque);
  431. sbuilder.Append(Rest.CS_DQUOTE);
  432. sbuilder.Append(Rest.CS_COMMA);
  433. }
  434. if (stale != null)
  435. {
  436. sbuilder.Append("stale=");
  437. sbuilder.Append(Rest.CS_DQUOTE);
  438. sbuilder.Append(stale);
  439. sbuilder.Append(Rest.CS_DQUOTE);
  440. sbuilder.Append(Rest.CS_COMMA);
  441. }
  442. if (alg != null)
  443. {
  444. sbuilder.Append("algorithm=");
  445. sbuilder.Append(alg);
  446. sbuilder.Append(Rest.CS_COMMA);
  447. }
  448. if (qop != String.Empty)
  449. {
  450. sbuilder.Append("qop=");
  451. sbuilder.Append(Rest.CS_DQUOTE);
  452. sbuilder.Append(qop);
  453. sbuilder.Append(Rest.CS_DQUOTE);
  454. sbuilder.Append(Rest.CS_COMMA);
  455. }
  456. if (auth != null)
  457. {
  458. sbuilder.Append(auth);
  459. sbuilder.Append(Rest.CS_COMMA);
  460. }
  461. if (Rest.Domains.Count != 0)
  462. {
  463. sbuilder.Append("domain=");
  464. sbuilder.Append(Rest.CS_DQUOTE);
  465. foreach (string dom in Rest.Domains.Values)
  466. {
  467. sbuilder.Append(dom);
  468. sbuilder.Append(Rest.CS_SPACE);
  469. }
  470. if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE)
  471. {
  472. sbuilder.Length = sbuilder.Length-1;
  473. }
  474. sbuilder.Append(Rest.CS_DQUOTE);
  475. sbuilder.Append(Rest.CS_COMMA);
  476. }
  477. if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA)
  478. {
  479. sbuilder.Length = sbuilder.Length-1;
  480. }
  481. AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
  482. }
  483. if (scheme == null || scheme == Rest.AS_BASIC)
  484. {
  485. sbuilder.Append(Rest.AS_BASIC);
  486. if (realm != null)
  487. {
  488. sbuilder.Append(" realm=\"");
  489. sbuilder.Append(realm);
  490. sbuilder.Append("\"");
  491. }
  492. AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
  493. }
  494. }
  495. private bool Validate(string user, string pass)
  496. {
  497. Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
  498. return user == "awebb" && pass == getPassword(user);
  499. }
  500. private string getPassword(string user)
  501. {
  502. return Rest.GodKey;
  503. }
  504. // Validate the request-digest
  505. private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
  506. {
  507. string patt = null;
  508. string payl = String.Empty;
  509. string KDS = null;
  510. string HA1 = null;
  511. string HA2 = null;
  512. string pass = getPassword(user);
  513. // Generate H(A1)
  514. if (algorithm == Rest.Digest_MD5Sess)
  515. {
  516. if (!sktable.ContainsKey(cnonce))
  517. {
  518. patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
  519. HA1 = HashToString(patt);
  520. sktable.Add(cnonce, HA1);
  521. }
  522. else
  523. {
  524. HA1 = sktable[cnonce];
  525. }
  526. }
  527. else
  528. {
  529. patt = String.Format("{0}:{1}:{2}", user, realm, pass);
  530. HA1 = HashToString(patt);
  531. }
  532. // Generate H(A2)
  533. if (qop == "auth-int")
  534. {
  535. patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
  536. }
  537. else
  538. {
  539. patt = String.Format("{0}:{1}", request.HttpMethod, uri);
  540. }
  541. HA2 = HashToString(patt);
  542. // Generate Digest
  543. if (qop != String.Empty)
  544. {
  545. patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
  546. }
  547. else
  548. {
  549. patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
  550. }
  551. KDS = HashToString(patt);
  552. // Compare the generated sequence with the original
  553. return (0 == sc.Compare(KDS, response));
  554. }
  555. private string HashToString(string pattern)
  556. {
  557. Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
  558. byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
  559. sbuilder.Length = 0;
  560. for (int i = 0; i < hash.Length; i++)
  561. {
  562. sbuilder.Append(hash[i].ToString("x2"));
  563. }
  564. Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
  565. return sbuilder.ToString();
  566. }
  567. internal void Complete()
  568. {
  569. statusCode = Rest.HttpStatusCodeOK;
  570. statusDescription = Rest.HttpStatusDescOK;
  571. }
  572. internal void Redirect(string Url, bool temp)
  573. {
  574. redirectLocation = Url;
  575. if (temp)
  576. {
  577. statusCode = Rest.HttpStatusCodeTemporaryRedirect;
  578. statusDescription = Rest.HttpStatusDescTemporaryRedirect;
  579. }
  580. else
  581. {
  582. statusCode = Rest.HttpStatusCodePermanentRedirect;
  583. statusDescription = Rest.HttpStatusDescPermanentRedirect;
  584. }
  585. Fail(statusCode, statusDescription, true);
  586. }
  587. // Fail for an arbitrary reason. Just a failure with
  588. // headers.
  589. internal void Fail(int code, string message)
  590. {
  591. Fail(code, message, true);
  592. }
  593. // More adventurous. This failure also includes a
  594. // specified entity.
  595. internal void Fail(int code, string message, string data)
  596. {
  597. buffer = null;
  598. body = data;
  599. Fail(code, message, false);
  600. }
  601. internal void Fail(int code, string message, bool reset)
  602. {
  603. statusCode = code;
  604. statusDescription = message;
  605. if (reset)
  606. {
  607. buffer = null;
  608. body = null;
  609. }
  610. if (Rest.DEBUG)
  611. {
  612. Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
  613. Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
  614. Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
  615. Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
  616. Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
  617. Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
  618. Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
  619. Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
  620. Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
  621. Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
  622. Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
  623. Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
  624. }
  625. fail = true;
  626. Respond("Failure response");
  627. RestException re = new RestException(message+" <"+code+">");
  628. re.statusCode = code;
  629. re.statusDesc = message;
  630. re.httpmethod = method;
  631. re.httppath = path;
  632. throw re;
  633. }
  634. // Reject this request
  635. internal void Reject()
  636. {
  637. Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented);
  638. }
  639. // This MUST be called by an agent handler before it returns
  640. // control to Handle, otherwise the request will be ignored.
  641. // This is called implciitly for the REST stream handlers and
  642. // is harmless if it is called twice.
  643. internal virtual bool Respond(string reason)
  644. {
  645. Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
  646. if (!handled)
  647. {
  648. Rest.Log.DebugFormat("{0} Generating Response", MsgId);
  649. // Process any arbitrary headers collected
  650. BuildHeaders();
  651. // A Head request can NOT have a body!
  652. if (method != Rest.HEAD)
  653. {
  654. Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
  655. if (writer != null)
  656. {
  657. Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
  658. Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
  659. writer.Flush();
  660. writer.Close();
  661. if (!fail)
  662. {
  663. buffer = xmldata.ToArray();
  664. AddHeader("Content-Type","application/xml");
  665. }
  666. xmldata.Close();
  667. Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
  668. Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
  669. }
  670. // If buffer != null, then we assume that
  671. // this has already been done some other
  672. // way. For example, transfer encoding might
  673. // have been done.
  674. if (buffer == null)
  675. {
  676. if (body != null && body.Length > 0)
  677. {
  678. Rest.Log.DebugFormat("{0} String-based entity", MsgId);
  679. buffer = encoding.GetBytes(body);
  680. }
  681. }
  682. if (buffer != null)
  683. {
  684. Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
  685. if (response.Headers.Get("Content-Encoding") == null)
  686. response.ContentEncoding = encoding;
  687. response.ContentLength64 = buffer.Length;
  688. response.SendChunked = false;
  689. response.KeepAlive = false;
  690. }
  691. }
  692. // Set the status code & description. If nothing
  693. // has been stored, we consider that a success
  694. if (statusCode == 0)
  695. {
  696. Complete();
  697. }
  698. response.StatusCode = statusCode;
  699. if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily ||
  700. response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
  701. {
  702. response.RedirectLocation = redirectLocation;
  703. }
  704. if (statusDescription != null)
  705. {
  706. response.StatusDescription = statusDescription;
  707. }
  708. // Finally we send back our response, consuming
  709. // any exceptions that doing so might produce.
  710. // We've left the setting of handled' until the
  711. // last minute because the header settings included
  712. // above are pretty harmless. But everything from
  713. // here on down probably leaves the response
  714. // element unusable by anyone else.
  715. handled = true;
  716. if (buffer != null && buffer.Length != 0)
  717. {
  718. Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
  719. MsgId, buffer.Length, encoding.GetString(buffer));
  720. response.OutputStream.Write(buffer, 0, buffer.Length);
  721. }
  722. response.OutputStream.Close();
  723. if (request.InputStream != null)
  724. {
  725. request.InputStream.Close();
  726. }
  727. }
  728. Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
  729. return handled;
  730. }
  731. // Add a header to the table. If the header
  732. // already exists, it is replaced.
  733. internal void AddHeader(string hdr, string data)
  734. {
  735. if (headers == null)
  736. {
  737. headers = new NameValueCollection();
  738. }
  739. headers[hdr] = data;
  740. }
  741. // Keep explicit track of any headers which
  742. // are to be removed.
  743. internal void RemoveHeader(string hdr)
  744. {
  745. if (removed_headers == null)
  746. {
  747. removed_headers = new List<string>();
  748. }
  749. removed_headers.Add(hdr);
  750. if (headers != null)
  751. {
  752. headers.Remove(hdr);
  753. }
  754. }
  755. // Should it prove necessary, we could always
  756. // restore the header collection from a cloned
  757. // copy, but for now we'll assume that that is
  758. // not necessary.
  759. private void BuildHeaders()
  760. {
  761. if (removed_headers != null)
  762. {
  763. foreach (string h in removed_headers)
  764. {
  765. Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
  766. response.Headers.Remove(h);
  767. }
  768. }
  769. if (headers!= null)
  770. {
  771. for (int i = 0; i < headers.Count; i++)
  772. {
  773. Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
  774. MsgId, headers.GetKey(i), headers.Get(i));
  775. response.Headers.Add(headers.GetKey(i), headers.Get(i));
  776. }
  777. }
  778. }
  779. /// <summary>
  780. /// Helper methods for deconstructing and reconstructing
  781. /// URI path data.
  782. /// </summary>
  783. private void initUrl()
  784. {
  785. uri = request.Url;
  786. if (query == null)
  787. {
  788. query = uri.Query;
  789. }
  790. // If the path has not been previously initialized,
  791. // do so now.
  792. if (path == null)
  793. {
  794. path = uri.AbsolutePath;
  795. if (path.EndsWith(Rest.UrlPathSeparator))
  796. path = path.Substring(0,path.Length-1);
  797. path = Uri.UnescapeDataString(path);
  798. }
  799. // If we succeeded in getting a path, perform any
  800. // additional pre-processing required.
  801. if (path != null)
  802. {
  803. if (Rest.ExtendedEscape)
  804. {
  805. // Handle "+". Not a standard substitution, but
  806. // common enough...
  807. path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
  808. }
  809. pathNodes = path.Split(Rest.CA_PATHSEP);
  810. }
  811. else
  812. {
  813. pathNodes = EmptyPath;
  814. }
  815. // Request server context info
  816. hostname = uri.Host;
  817. port = uri.Port;
  818. }
  819. internal int initParameters(int prfxlen)
  820. {
  821. if (prfxlen < path.Length-1)
  822. {
  823. parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
  824. }
  825. else
  826. {
  827. parameters = new string[0];
  828. }
  829. // Generate a debug list of the decoded parameters
  830. if (Rest.DEBUG && prfxlen < path.Length-1)
  831. {
  832. Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
  833. for (int i = 0; i < parameters.Length; i++)
  834. {
  835. Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
  836. }
  837. }
  838. return parameters.Length;
  839. }
  840. internal string[] PathNodes
  841. {
  842. get
  843. {
  844. if (pathNodes == null)
  845. {
  846. initUrl();
  847. }
  848. return pathNodes;
  849. }
  850. }
  851. internal string BuildUrl(int first, int last)
  852. {
  853. if (pathNodes == null)
  854. {
  855. initUrl();
  856. }
  857. if (first < 0)
  858. {
  859. first = first + pathNodes.Length;
  860. }
  861. if (last < 0)
  862. {
  863. last = last + pathNodes.Length;
  864. if (last < 0)
  865. {
  866. return Rest.UrlPathSeparator;
  867. }
  868. }
  869. sbuilder.Length = 0;
  870. sbuilder.Append(Rest.UrlPathSeparator);
  871. if (first <= last)
  872. {
  873. for (int i = first; i <= last; i++)
  874. {
  875. sbuilder.Append(pathNodes[i]);
  876. sbuilder.Append(Rest.UrlPathSeparator);
  877. }
  878. }
  879. else
  880. {
  881. for (int i = last; i >= first; i--)
  882. {
  883. sbuilder.Append(pathNodes[i]);
  884. sbuilder.Append(Rest.UrlPathSeparator);
  885. }
  886. }
  887. return sbuilder.ToString();
  888. }
  889. // Setup the XML writer for output
  890. internal void initXmlWriter()
  891. {
  892. XmlWriterSettings settings = new XmlWriterSettings();
  893. xmldata = new MemoryStream();
  894. settings.Indent = true;
  895. settings.IndentChars = " ";
  896. settings.Encoding = encoding;
  897. settings.CloseOutput = false;
  898. settings.OmitXmlDeclaration = true;
  899. settings.ConformanceLevel = ConformanceLevel.Fragment;
  900. writer = XmlWriter.Create(xmldata, settings);
  901. }
  902. internal void initXmlReader()
  903. {
  904. XmlReaderSettings settings = new XmlReaderSettings();
  905. settings.ConformanceLevel = ConformanceLevel.Fragment;
  906. settings.IgnoreComments = true;
  907. settings.IgnoreWhitespace = true;
  908. settings.IgnoreProcessingInstructions = true;
  909. settings.ValidationType = ValidationType.None;
  910. // reader = XmlReader.Create(new StringReader(entity),settings);
  911. reader = XmlReader.Create(request.InputStream,settings);
  912. }
  913. private void Flush()
  914. {
  915. byte[] dbuffer = new byte[8192];
  916. while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
  917. return;
  918. }
  919. // This allows us to make errors a bit more apparent in REST
  920. internal void SendHtml(string text)
  921. {
  922. SendHtml("OpenSim REST Interface 1.0", text);
  923. }
  924. internal void SendHtml(string title, string text)
  925. {
  926. AddHeader(Rest.HttpHeaderContentType, "text/html");
  927. sbuilder.Length = 0;
  928. sbuilder.Append("<html>");
  929. sbuilder.Append("<head>");
  930. sbuilder.Append("<title>");
  931. sbuilder.Append(title);
  932. sbuilder.Append("</title>");
  933. sbuilder.Append("</head>");
  934. sbuilder.Append("<body>");
  935. sbuilder.Append("<br />");
  936. sbuilder.Append("<p>");
  937. sbuilder.Append(text);
  938. sbuilder.Append("</p>");
  939. sbuilder.Append("</body>");
  940. sbuilder.Append("</html>");
  941. html = sbuilder.ToString();
  942. }
  943. }
  944. }