ScriptsHttpRequests.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  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 OpenSimulator 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. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Linq;
  31. using System.Net;
  32. using System.Net.Mail;
  33. using System.Net.Security;
  34. using System.Reflection;
  35. using System.Text;
  36. using System.Threading;
  37. using System.Security.Cryptography.X509Certificates;
  38. using log4net;
  39. using Nini.Config;
  40. using OpenMetaverse;
  41. using OpenSim.Framework;
  42. using OpenSim.Framework.Communications;
  43. using OpenSim.Framework.Servers;
  44. using OpenSim.Framework.Servers.HttpServer;
  45. using OpenSim.Region.Framework.Interfaces;
  46. using OpenSim.Region.Framework.Scenes;
  47. using Mono.Addins;
  48. /*****************************************************
  49. *
  50. * ScriptsHttpRequests
  51. *
  52. * Implements the llHttpRequest and http_response
  53. * callback.
  54. *
  55. * Some stuff was already in LSLLongCmdHandler, and then
  56. * there was this file with a stub class in it. So,
  57. * I am moving some of the objects and functions out of
  58. * LSLLongCmdHandler, such as the HttpRequestClass, the
  59. * start and stop methods, and setting up pending and
  60. * completed queues. These are processed in the
  61. * LSLLongCmdHandler polling loop. Similiar to the
  62. * XMLRPCModule, since that seems to work.
  63. *
  64. * //TODO
  65. *
  66. * This probably needs some throttling mechanism but
  67. * it's wide open right now. This applies to both
  68. * number of requests and data volume.
  69. *
  70. * Linden puts all kinds of header fields in the requests.
  71. * Not doing any of that:
  72. * User-Agent
  73. * X-SecondLife-Shard
  74. * X-SecondLife-Object-Name
  75. * X-SecondLife-Object-Key
  76. * X-SecondLife-Region
  77. * X-SecondLife-Local-Position
  78. * X-SecondLife-Local-Velocity
  79. * X-SecondLife-Local-Rotation
  80. * X-SecondLife-Owner-Name
  81. * X-SecondLife-Owner-Key
  82. *
  83. * HTTPS support
  84. *
  85. * Configurable timeout?
  86. * Configurable max response size?
  87. * Configurable
  88. *
  89. * **************************************************/
  90. namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
  91. {
  92. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")]
  93. public class HttpRequestModule : ISharedRegionModule, IHttpRequestModule
  94. {
  95. // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  96. private object HttpListLock = new object();
  97. private int httpTimeout = 30000;
  98. private string m_name = "HttpScriptRequests";
  99. private OutboundUrlFilter m_outboundUrlFilter;
  100. private string m_proxyurl = "";
  101. private string m_proxyexcepts = "";
  102. // <request id, HttpRequestClass>
  103. private Dictionary<UUID, HttpRequestClass> m_pendingRequests;
  104. private Scene m_scene;
  105. // private Queue<HttpRequestClass> rpcQueue = new Queue<HttpRequestClass>();
  106. public HttpRequestModule()
  107. {
  108. ServicePointManager.ServerCertificateValidationCallback +=ValidateServerCertificate;
  109. }
  110. public static bool ValidateServerCertificate(
  111. object sender,
  112. X509Certificate certificate,
  113. X509Chain chain,
  114. SslPolicyErrors sslPolicyErrors)
  115. {
  116. // If this is a web request we need to check the headers first
  117. // We may want to ignore SSL
  118. if (sender is HttpWebRequest)
  119. {
  120. HttpWebRequest Request = (HttpWebRequest)sender;
  121. ServicePoint sp = Request.ServicePoint;
  122. // We don't case about encryption, get out of here
  123. if (Request.Headers.Get("NoVerifyCert") != null)
  124. {
  125. return true;
  126. }
  127. // If there was an upstream cert verification error, bail
  128. if ((((int)sslPolicyErrors) & ~4) != 0)
  129. return false;
  130. // Check for policy and execute it if defined
  131. #pragma warning disable 0618
  132. if (ServicePointManager.CertificatePolicy != null)
  133. {
  134. return ServicePointManager.CertificatePolicy.CheckValidationResult (sp, certificate, Request, 0);
  135. }
  136. #pragma warning restore 0618
  137. return true;
  138. }
  139. // If it's not HTTP, trust .NET to check it
  140. if ((((int)sslPolicyErrors) & ~4) != 0)
  141. return false;
  142. return true;
  143. }
  144. #region IHttpRequestModule Members
  145. public UUID MakeHttpRequest(string url, string parameters, string body)
  146. {
  147. return UUID.Zero;
  148. }
  149. public UUID StartHttpRequest(
  150. uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body,
  151. out HttpInitialRequestStatus status)
  152. {
  153. UUID reqID = UUID.Random();
  154. HttpRequestClass htc = new HttpRequestClass();
  155. // Partial implementation: support for parameter flags needed
  156. // see http://wiki.secondlife.com/wiki/LlHTTPRequest
  157. //
  158. // Parameters are expected in {key, value, ... , key, value}
  159. if (parameters != null)
  160. {
  161. string[] parms = parameters.ToArray();
  162. for (int i = 0; i < parms.Length; i += 2)
  163. {
  164. switch (Int32.Parse(parms[i]))
  165. {
  166. case (int)HttpRequestConstants.HTTP_METHOD:
  167. htc.HttpMethod = parms[i + 1];
  168. break;
  169. case (int)HttpRequestConstants.HTTP_MIMETYPE:
  170. htc.HttpMIMEType = parms[i + 1];
  171. break;
  172. case (int)HttpRequestConstants.HTTP_BODY_MAXLENGTH:
  173. // TODO implement me
  174. break;
  175. case (int)HttpRequestConstants.HTTP_VERIFY_CERT:
  176. htc.HttpVerifyCert = (int.Parse(parms[i + 1]) != 0);
  177. break;
  178. case (int)HttpRequestConstants.HTTP_VERBOSE_THROTTLE:
  179. // TODO implement me
  180. break;
  181. case (int)HttpRequestConstants.HTTP_CUSTOM_HEADER:
  182. //Parameters are in pairs and custom header takes
  183. //arguments in pairs so adjust for header marker.
  184. ++i;
  185. //Maximum of 8 headers are allowed based on the
  186. //Second Life documentation for llHTTPRequest.
  187. for (int count = 1; count <= 8; ++count)
  188. {
  189. //Not enough parameters remaining for a header?
  190. if (parms.Length - i < 2)
  191. break;
  192. //Have we reached the end of the list of headers?
  193. //End is marked by a string with a single digit.
  194. //We already know we have at least one parameter
  195. //so it is safe to do this check at top of loop.
  196. if (Char.IsDigit(parms[i][0]))
  197. break;
  198. if (htc.HttpCustomHeaders == null)
  199. htc.HttpCustomHeaders = new List<string>();
  200. htc.HttpCustomHeaders.Add(parms[i]);
  201. htc.HttpCustomHeaders.Add(parms[i+1]);
  202. i += 2;
  203. }
  204. break;
  205. case (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE:
  206. htc.HttpPragmaNoCache = (int.Parse(parms[i + 1]) != 0);
  207. break;
  208. }
  209. }
  210. }
  211. htc.RequestModule = this;
  212. htc.LocalID = localID;
  213. htc.ItemID = itemID;
  214. htc.Url = url;
  215. htc.ReqID = reqID;
  216. htc.HttpTimeout = httpTimeout;
  217. htc.OutboundBody = body;
  218. htc.ResponseHeaders = headers;
  219. htc.proxyurl = m_proxyurl;
  220. htc.proxyexcepts = m_proxyexcepts;
  221. // Same number as default HttpWebRequest.MaximumAutomaticRedirections
  222. htc.MaxRedirects = 50;
  223. if (StartHttpRequest(htc))
  224. {
  225. status = HttpInitialRequestStatus.OK;
  226. return htc.ReqID;
  227. }
  228. else
  229. {
  230. status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER;
  231. return UUID.Zero;
  232. }
  233. }
  234. /// <summary>
  235. /// Would a caller to this module be allowed to make a request to the given URL?
  236. /// </summary>
  237. /// <returns></returns>
  238. public bool CheckAllowed(Uri url)
  239. {
  240. return m_outboundUrlFilter.CheckAllowed(url);
  241. }
  242. public bool StartHttpRequest(HttpRequestClass req)
  243. {
  244. if (!CheckAllowed(new Uri(req.Url)))
  245. return false;
  246. lock (HttpListLock)
  247. {
  248. m_pendingRequests.Add(req.ReqID, req);
  249. }
  250. req.Process();
  251. return true;
  252. }
  253. public void StopHttpRequestsForScript(UUID id)
  254. {
  255. if (m_pendingRequests != null)
  256. {
  257. List<UUID> keysToRemove = null;
  258. lock (HttpListLock)
  259. {
  260. foreach (HttpRequestClass req in m_pendingRequests.Values)
  261. {
  262. if (req.ItemID == id)
  263. {
  264. req.Stop();
  265. if (keysToRemove == null)
  266. keysToRemove = new List<UUID>();
  267. keysToRemove.Add(req.ReqID);
  268. }
  269. }
  270. if (keysToRemove != null)
  271. keysToRemove.ForEach(keyToRemove => m_pendingRequests.Remove(keyToRemove));
  272. }
  273. }
  274. }
  275. /*
  276. * TODO
  277. * Not sure how important ordering is is here - the next first
  278. * one completed in the list is returned, based soley on its list
  279. * position, not the order in which the request was started or
  280. * finished. I thought about setting up a queue for this, but
  281. * it will need some refactoring and this works 'enough' right now
  282. */
  283. public IServiceRequest GetNextCompletedRequest()
  284. {
  285. lock (HttpListLock)
  286. {
  287. foreach (HttpRequestClass req in m_pendingRequests.Values)
  288. {
  289. if (req.Finished)
  290. return req;
  291. }
  292. }
  293. return null;
  294. }
  295. public void RemoveCompletedRequest(UUID id)
  296. {
  297. lock (HttpListLock)
  298. {
  299. HttpRequestClass tmpReq;
  300. if (m_pendingRequests.TryGetValue(id, out tmpReq))
  301. {
  302. tmpReq.Stop();
  303. tmpReq = null;
  304. m_pendingRequests.Remove(id);
  305. }
  306. }
  307. }
  308. #endregion
  309. #region ISharedRegionModule Members
  310. public void Initialise(IConfigSource config)
  311. {
  312. m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
  313. m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
  314. m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config);
  315. m_pendingRequests = new Dictionary<UUID, HttpRequestClass>();
  316. }
  317. public void AddRegion(Scene scene)
  318. {
  319. m_scene = scene;
  320. m_scene.RegisterModuleInterface<IHttpRequestModule>(this);
  321. }
  322. public void RemoveRegion(Scene scene)
  323. {
  324. scene.UnregisterModuleInterface<IHttpRequestModule>(this);
  325. if (scene == m_scene)
  326. m_scene = null;
  327. }
  328. public void PostInitialise()
  329. {
  330. }
  331. public void RegionLoaded(Scene scene)
  332. {
  333. }
  334. public void Close()
  335. {
  336. }
  337. public string Name
  338. {
  339. get { return m_name; }
  340. }
  341. public Type ReplaceableInterface
  342. {
  343. get { return null; }
  344. }
  345. #endregion
  346. }
  347. public class HttpRequestClass : IServiceRequest
  348. {
  349. // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  350. // Constants for parameters
  351. // public const int HTTP_BODY_MAXLENGTH = 2;
  352. // public const int HTTP_METHOD = 0;
  353. // public const int HTTP_MIMETYPE = 1;
  354. // public const int HTTP_VERIFY_CERT = 3;
  355. // public const int HTTP_VERBOSE_THROTTLE = 4;
  356. // public const int HTTP_CUSTOM_HEADER = 5;
  357. // public const int HTTP_PRAGMA_NO_CACHE = 6;
  358. /// <summary>
  359. /// Module that made this request.
  360. /// </summary>
  361. public HttpRequestModule RequestModule { get; set; }
  362. private bool _finished;
  363. public bool Finished
  364. {
  365. get { return _finished; }
  366. }
  367. // public int HttpBodyMaxLen = 2048; // not implemented
  368. // Parameter members and default values
  369. public string HttpMethod = "GET";
  370. public string HttpMIMEType = "text/plain;charset=utf-8";
  371. public int HttpTimeout;
  372. public bool HttpVerifyCert = true;
  373. //public bool HttpVerboseThrottle = true; // not implemented
  374. public List<string> HttpCustomHeaders = null;
  375. public bool HttpPragmaNoCache = true;
  376. // Request info
  377. private UUID _itemID;
  378. public UUID ItemID
  379. {
  380. get { return _itemID; }
  381. set { _itemID = value; }
  382. }
  383. private uint _localID;
  384. public uint LocalID
  385. {
  386. get { return _localID; }
  387. set { _localID = value; }
  388. }
  389. public DateTime Next;
  390. public string proxyurl;
  391. public string proxyexcepts;
  392. /// <summary>
  393. /// Number of HTTP redirects that this request has been through.
  394. /// </summary>
  395. public int Redirects { get; private set; }
  396. /// <summary>
  397. /// Maximum number of HTTP redirects allowed for this request.
  398. /// </summary>
  399. public int MaxRedirects { get; set; }
  400. public string OutboundBody;
  401. private UUID _reqID;
  402. public UUID ReqID
  403. {
  404. get { return _reqID; }
  405. set { _reqID = value; }
  406. }
  407. public HttpWebRequest Request;
  408. public string ResponseBody;
  409. public List<string> ResponseMetadata;
  410. public Dictionary<string, string> ResponseHeaders;
  411. public int Status;
  412. public string Url;
  413. public void Process()
  414. {
  415. SendRequest();
  416. }
  417. public void SendRequest()
  418. {
  419. try
  420. {
  421. Request = (HttpWebRequest)WebRequest.Create(Url);
  422. Request.AllowAutoRedirect = false;
  423. Request.Method = HttpMethod;
  424. Request.ContentType = HttpMIMEType;
  425. if (!HttpVerifyCert)
  426. {
  427. // We could hijack Connection Group Name to identify
  428. // a desired security exception. But at the moment we'll use a dummy header instead.
  429. // Request.ConnectionGroupName = "NoVerify";
  430. Request.Headers.Add("NoVerifyCert", "true");
  431. }
  432. // else
  433. // {
  434. // Request.ConnectionGroupName="Verify";
  435. // }
  436. if (!HttpPragmaNoCache)
  437. {
  438. Request.Headers.Add("Pragma", "no-cache");
  439. }
  440. if (HttpCustomHeaders != null)
  441. {
  442. for (int i = 0; i < HttpCustomHeaders.Count; i += 2)
  443. Request.Headers.Add(HttpCustomHeaders[i],
  444. HttpCustomHeaders[i+1]);
  445. }
  446. if (!string.IsNullOrEmpty(proxyurl))
  447. {
  448. if (!string.IsNullOrEmpty(proxyexcepts))
  449. {
  450. string[] elist = proxyexcepts.Split(';');
  451. Request.Proxy = new WebProxy(proxyurl, true, elist);
  452. }
  453. else
  454. {
  455. Request.Proxy = new WebProxy(proxyurl, true);
  456. }
  457. }
  458. if (ResponseHeaders != null)
  459. {
  460. foreach (KeyValuePair<string, string> entry in ResponseHeaders)
  461. if (entry.Key.ToLower().Equals("user-agent") && Request is HttpWebRequest)
  462. ((HttpWebRequest)Request).UserAgent = entry.Value;
  463. else
  464. Request.Headers[entry.Key] = entry.Value;
  465. }
  466. // Encode outbound data
  467. if (!string.IsNullOrEmpty(OutboundBody))
  468. {
  469. byte[] data = Util.UTF8.GetBytes(OutboundBody);
  470. Request.ContentLength = data.Length;
  471. using (Stream bstream = Request.GetRequestStream())
  472. bstream.Write(data, 0, data.Length);
  473. }
  474. try
  475. {
  476. IAsyncResult result = (IAsyncResult)Request.BeginGetResponse(ResponseCallback, null);
  477. ThreadPool.RegisterWaitForSingleObject(
  478. result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), null, HttpTimeout, true);
  479. }
  480. catch (WebException e)
  481. {
  482. if (e.Status != WebExceptionStatus.ProtocolError)
  483. {
  484. throw;
  485. }
  486. HttpWebResponse response = (HttpWebResponse)e.Response;
  487. Status = (int)response.StatusCode;
  488. ResponseBody = response.StatusDescription;
  489. _finished = true;
  490. }
  491. }
  492. catch (Exception e)
  493. {
  494. // m_log.Debug(
  495. // string.Format("[SCRIPTS HTTP REQUESTS]: Exception on request to {0} for {1} ", Url, ItemID), e);
  496. Status = (int)OSHttpStatusCode.ClientErrorJoker;
  497. ResponseBody = e.Message;
  498. _finished = true;
  499. }
  500. }
  501. private void ResponseCallback(IAsyncResult ar)
  502. {
  503. HttpWebResponse response = null;
  504. try
  505. {
  506. try
  507. {
  508. response = (HttpWebResponse)Request.EndGetResponse(ar);
  509. }
  510. catch (WebException e)
  511. {
  512. if (e.Status != WebExceptionStatus.ProtocolError)
  513. {
  514. throw;
  515. }
  516. response = (HttpWebResponse)e.Response;
  517. }
  518. Status = (int)response.StatusCode;
  519. using (Stream stream = response.GetResponseStream())
  520. {
  521. StreamReader reader = new StreamReader(stream, Encoding.UTF8);
  522. ResponseBody = reader.ReadToEnd();
  523. }
  524. }
  525. catch (Exception e)
  526. {
  527. Status = (int)OSHttpStatusCode.ClientErrorJoker;
  528. ResponseBody = e.Message;
  529. // m_log.Debug(
  530. // string.Format("[SCRIPTS HTTP REQUESTS]: Exception on response to {0} for {1} ", Url, ItemID), e);
  531. }
  532. finally
  533. {
  534. if (response != null)
  535. response.Close();
  536. // We need to resubmit
  537. if (
  538. (Status == (int)HttpStatusCode.MovedPermanently
  539. || Status == (int)HttpStatusCode.Found
  540. || Status == (int)HttpStatusCode.SeeOther
  541. || Status == (int)HttpStatusCode.TemporaryRedirect))
  542. {
  543. if (Redirects >= MaxRedirects)
  544. {
  545. Status = (int)OSHttpStatusCode.ClientErrorJoker;
  546. ResponseBody = "Number of redirects exceeded max redirects";
  547. _finished = true;
  548. }
  549. else
  550. {
  551. string location = response.Headers["Location"];
  552. if (location == null)
  553. {
  554. Status = (int)OSHttpStatusCode.ClientErrorJoker;
  555. ResponseBody = "HTTP redirect code but no location header";
  556. _finished = true;
  557. }
  558. else if (!RequestModule.CheckAllowed(new Uri(location)))
  559. {
  560. Status = (int)OSHttpStatusCode.ClientErrorJoker;
  561. ResponseBody = "URL from HTTP redirect blocked: " + location;
  562. _finished = true;
  563. }
  564. else
  565. {
  566. Status = 0;
  567. Url = response.Headers["Location"];
  568. Redirects++;
  569. ResponseBody = null;
  570. // m_log.DebugFormat("Redirecting to [{0}]", Url);
  571. Process();
  572. }
  573. }
  574. }
  575. else
  576. {
  577. _finished = true;
  578. }
  579. }
  580. }
  581. private void TimeoutCallback(object state, bool timedOut)
  582. {
  583. if (timedOut)
  584. Request.Abort();
  585. }
  586. public void Stop()
  587. {
  588. // m_log.DebugFormat("[SCRIPTS HTTP REQUESTS]: Stopping request to {0} for {1} ", Url, ItemID);
  589. if (Request != null)
  590. Request.Abort();
  591. }
  592. }
  593. }