clockwerk-opensim – Blame information for rev 1

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