aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1465
1 files changed, 0 insertions, 1465 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
deleted file mode 100644
index 10f1a6e..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
+++ /dev/null
@@ -1,1465 +0,0 @@
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
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Security.Cryptography;
32using System.Text;
33using System.Text.RegularExpressions;
34using System.Xml;
35using OpenSim.Framework;
36using OpenSim.Framework.Servers;
37using OpenSim.Framework.Servers.HttpServer;
38using OpenSim.Services.Interfaces;
39
40using OpenMetaverse;
41
42namespace OpenSim.ApplicationPlugins.Rest.Inventory
43{
44
45 /// <summary>
46 /// This class represents the current REST request. It
47 /// encapsulates the request/response state and takes care
48 /// of response generation without exposing the REST handler
49 /// to the actual mechanisms involved.
50 ///
51 /// This structure is created on entry to the Handler
52 /// method and is disposed of upon return. It is part of
53 /// the plug-in infrastructure, rather than the functionally
54 /// specific REST handler, and fundamental changes to
55 /// this should be reflected in the Rest HandlerVersion. The
56 /// object is instantiated, and may be extended by, any
57 /// given handler. See the inventory handler for an example
58 /// of this.
59 ///
60 /// If possible, the underlying request/response state is not
61 /// changed until the handler explicitly issues a Respond call.
62 /// This ensures that the request/response pair can be safely
63 /// processed by subsequent, unrelated, handlers even id the
64 /// agent handler had completed much of its processing. Think
65 /// of it as a transactional req/resp capability.
66 /// </summary>
67
68 public class RequestData
69 {
70
71 // HTTP Server interface data (Received values)
72
73 internal OSHttpRequest request = null;
74 internal OSHttpResponse response = null;
75 internal string qprefix = null;
76
77 // Request lifetime values
78 // buffer is global because it is referenced by the handler
79 // in supported of streamed requests.
80 // If a service provider wants to construct the message
81 // body explicitly it can use body to do this. The value
82 // in body is used if the buffer is still null when a response
83 // is generated.
84 // Storing information in body will suppress the return of
85 // statusBody which is only intended to report status on
86 // requests which do not themselves ordinarily generate
87 // an informational response. All of this is handled in
88 // Respond().
89
90 internal byte[] buffer = null;
91 internal string body = null;
92 internal string bodyType = "text/html";
93
94 // The encoding in effect is set to a server default. It may
95 // subsequently be overridden by a Content header. This
96 // value is established during construction and is used
97 // wherever encoding services are needed.
98
99 internal Encoding encoding = Rest.Encoding;
100
101 // These values are derived from the supplied URL. They
102 // are initialized during construction.
103
104 internal string path = null;
105 internal string method = null;
106 internal Uri uri = null;
107 internal string query = null;
108 internal string hostname = "localhost";
109 internal int port = 80;
110
111 // The path part of the URI is decomposed. pathNodes
112 // is an array of every element in the URI. Parameters
113 // is an array that contains only those nodes that
114 // are not a part of the authority prefix
115
116 private string[] pathNodes = null;
117 private string[] parameters = null;
118 private static readonly string[] EmptyPath = { String.Empty };
119
120 // The status code gets set during the course of processing
121 // and is the HTTP completion code. The status body is
122 // initialized during construction, is appended to during the
123 // course of execution, and is finalized during Respond
124 // processing.
125 //
126 // Fail processing marks the request as failed and this is
127 // then used to inhibit processing during Response processing.
128
129 internal int statusCode = 0;
130 internal string statusBody = String.Empty;
131 internal bool fail = false;
132
133 // This carries the URL to which the client should be redirected.
134 // It is set by the service provider using the Redirect call.
135
136 internal string redirectLocation = null;
137
138 // These values influence response processing. They can be set by
139 // service providers according to need. The defaults are generally
140 // good.
141
142 internal bool keepAlive = false;
143 internal bool chunked = false;
144
145 // XML related state
146
147 internal XmlWriter writer = null;
148 internal XmlReader reader = null;
149
150 // Internal working state
151
152 private StringBuilder sbuilder = new StringBuilder(1024);
153 private MemoryStream xmldata = null;
154
155 // This is used to make the response mechanism idempotent.
156
157 internal bool handled = false;
158
159 // Authentication related state
160 //
161 // Two supported authentication mechanisms are:
162 // scheme = Rest.AS_BASIC;
163 // scheme = Rest.AS_DIGEST;
164 // Presented in that order (as required by spec)
165 // A service provider can set the scheme variable to
166 // force selection of a particular authentication model
167 // (choosing from amongst those supported of course)
168 //
169
170 internal bool authenticated = false;
171 internal string scheme = Rest.Scheme;
172 internal string realm = Rest.Realm;
173 internal string domain = null;
174 internal string nonce = null;
175 internal string cnonce = null;
176 internal string qop = Rest.Qop_Auth;
177 internal string opaque = null;
178 internal string stale = null;
179 internal string algorithm = Rest.Digest_MD5;
180 internal string authParms = null;
181 internal string authPrefix = null;
182 internal string userName = String.Empty;
183 internal string userPass = String.Empty;
184
185 // Session related tables. These are only needed if QOP is set to "auth-sess"
186 // and for now at least, it is not. Session related authentication is of
187 // questionable merit in the context of REST anyway, but it is, arguably, more
188 // secure.
189
190 private static Dictionary<string,string> cntable = new Dictionary<string,string>();
191 private static Dictionary<string,string> sktable = new Dictionary<string,string>();
192
193 // This dictionary is used to keep track fo all of the parameters discovered
194 // when the authorisation header is anaylsed.
195
196 private Dictionary<string,string> authparms = new Dictionary<string,string>();
197
198 // These regular expressions are used to decipher the various header entries.
199
200 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
201 RegexOptions.Compiled | RegexOptions.IgnoreCase);
202
203 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
204 RegexOptions.Compiled | RegexOptions.IgnoreCase);
205
206 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
207 RegexOptions.Compiled | RegexOptions.IgnoreCase);
208
209 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
210 RegexOptions.Compiled | RegexOptions.IgnoreCase);
211
212 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
213 RegexOptions.Compiled | RegexOptions.IgnoreCase);
214
215 // For efficiency, we create static instances of these objects
216
217 private static MD5 md5hash = MD5.Create();
218
219 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
220
221#region properties
222
223 // Just for convenience...
224
225 internal string MsgId
226 {
227 get { return Rest.MsgId; }
228 }
229
230 /// <summary>
231 /// Return a boolean indication of whether or no an authenticated user is
232 /// associated with this request. This could be wholly integrated, but
233 /// that would make authentication mandatory.
234 /// </summary>
235
236 internal bool IsAuthenticated
237 {
238 get
239 {
240 if (Rest.Authenticate)
241 {
242 if (!authenticated)
243 {
244 authenticate();
245 }
246
247 return authenticated;
248 }
249 else return true;
250 }
251 }
252
253 /// <summary>
254 /// Access to all 'nodes' in the supplied URI as an
255 /// array of strings.
256 /// </summary>
257
258 internal string[] PathNodes
259 {
260 get
261 {
262 return pathNodes;
263 }
264 }
265
266 /// <summary>
267 /// Access to all non-prefix 'nodes' in the supplied URI as an
268 /// array of strings. These identify a specific resource that
269 /// is managed by the authority (the prefix).
270 /// </summary>
271
272 internal string[] Parameters
273 {
274 get
275 {
276 return parameters;
277 }
278 }
279
280#endregion properties
281
282#region constructors
283
284 // Constructor
285
286 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
287 {
288
289 request = p_request;
290 response = p_response;
291 qprefix = p_qprefix;
292
293 sbuilder.Length = 0;
294
295 encoding = request.ContentEncoding;
296
297 if (encoding == null)
298 {
299 encoding = Rest.Encoding;
300 }
301
302 method = request.HttpMethod.ToLower();
303 initUrl();
304
305 initParameters(p_qprefix.Length);
306
307 }
308
309#endregion constructors
310
311#region authentication_common
312
313 /// <summary>
314 /// The REST handler has requested authentication. Authentication
315 /// is considered to be with respect to the current values for
316 /// Realm, domain, etc.
317 ///
318 /// This method checks to see if the current request is already
319 /// authenticated for this domain. If it is, then it returns
320 /// true. If it is not, then it issues a challenge to the client
321 /// and responds negatively to the request.
322 ///
323 /// As soon as authentication failure is detected the method calls
324 /// DoChallenge() which terminates the request with REST exception
325 /// for unauthroized access.
326 /// </summary>
327
328 private void authenticate()
329 {
330
331 string authdata = request.Headers.Get("Authorization");
332 string reqscheme = String.Empty;
333
334 // If we don't have an authorization header, then this
335 // user is certainly not authorized. This is the typical
336 // pivot for the 1st request by a client.
337
338 if (authdata == null)
339 {
340 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
341 DoChallenge();
342 }
343
344 // So, we have authentication data, now we have to check to
345 // see what we got and whether or not it is valid for the
346 // current domain. To do this we need to interpret the data
347 // provided in the Authorization header. First we need to
348 // identify the scheme being used and route accordingly.
349
350 MatchCollection matches = schema.Matches(authdata);
351
352 foreach (Match m in matches)
353 {
354 Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
355 reqscheme = m.Groups["scheme"].Value.ToLower();
356 }
357
358 // If we want a specific authentication mechanism, make sure
359 // we get it. null indicates we don't care. non-null indicates
360 // a specific scheme requirement.
361
362 if (scheme != null && scheme.ToLower() != reqscheme)
363 {
364 Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId);
365 DoChallenge();
366 }
367
368 // In the future, these could be made into plug-ins...
369 // But for now at least we have no reason to use anything other
370 // then MD5. TLS/SSL are taken care of elsewhere.
371
372 switch (reqscheme)
373 {
374 case "digest" :
375 Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
376 DoDigest(authdata);
377 break;
378
379 case "basic" :
380 Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
381 DoBasic(authdata);
382 break;
383 }
384
385 // If the current header is invalid, then a challenge is still needed.
386
387 if (!authenticated)
388 {
389 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
390 DoChallenge();
391 }
392
393 }
394
395 /// <summary>
396 /// Construct the necessary WWW-Authenticate headers and fail the request
397 /// with a NOT AUTHORIZED response. The parameters are the union of values
398 /// required by the supported schemes.
399 /// </summary>
400
401 private void DoChallenge()
402 {
403 Flush();
404 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
405 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
406 Fail(Rest.HttpStatusCodeNotAuthorized);
407 }
408
409 /// <summary>
410 /// The Flush() call is here to support a problem encountered with the
411 /// client where an authentication rejection was lost because the rejection
412 /// may flow before the clienthas finished sending us the inbound data stream,
413 /// in which case the client responds to the socket error on out put, and
414 /// never sees the authentication challenge. The client should be fixed,
415 /// because this solution leaves the server prone to DOS attacks. A message
416 /// will be issued whenever flushing occurs. It can be enabled/disabled from
417 /// the configuration file.
418 /// </summary>
419
420 private void Flush()
421 {
422 if (Rest.FlushEnabled)
423 {
424 byte[] dbuffer = new byte[8192];
425 Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
426 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
427 }
428 return;
429 }
430
431 // Indicate that authentication is required
432
433 private void Challenge(string scheme, string realm, string domain, string nonce,
434 string opaque, string stale, string alg,
435 string qop, string auth)
436 {
437
438 sbuilder.Length = 0;
439
440 // The service provider can force a particular scheme by
441 // assigning a value to scheme.
442
443 // Basic authentication is pretty simple.
444 // Just specify the realm in question.
445
446 if (scheme == null || scheme == Rest.AS_BASIC)
447 {
448
449 sbuilder.Append(Rest.AS_BASIC);
450
451 if (realm != null)
452 {
453 sbuilder.Append(" realm=");
454 sbuilder.Append(Rest.CS_DQUOTE);
455 sbuilder.Append(realm);
456 sbuilder.Append(Rest.CS_DQUOTE);
457 }
458 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
459 }
460
461 sbuilder.Length = 0;
462
463 // Digest authentication takes somewhat more
464 // to express.
465
466 if (scheme == null || scheme == Rest.AS_DIGEST)
467 {
468
469 sbuilder.Append(Rest.AS_DIGEST);
470 sbuilder.Append(" ");
471
472 // Specify the effective realm. This should
473 // never be null if we are uthenticating, as it is required for all
474 // authentication schemes. It defines, in conjunction with the
475 // absolute URI information, the domain to which the authentication
476 // applies. It is an arbitrary string. I *believe* this allows an
477 // authentication to apply to disjoint resources within the same
478 // server.
479
480 if (realm != null)
481 {
482 sbuilder.Append("realm=");
483 sbuilder.Append(Rest.CS_DQUOTE);
484 sbuilder.Append(realm);
485 sbuilder.Append(Rest.CS_DQUOTE);
486 sbuilder.Append(Rest.CS_COMMA);
487 }
488
489 // Share our nonce. This is *uniquely* generated each time a 401 is
490 // returned. We do not generate a very sophisticated nonce at the
491 // moment (it's simply a base64 encoded UUID).
492
493 if (nonce != null)
494 {
495 sbuilder.Append("nonce=");
496 sbuilder.Append(Rest.CS_DQUOTE);
497 sbuilder.Append(nonce);
498 sbuilder.Append(Rest.CS_DQUOTE);
499 sbuilder.Append(Rest.CS_COMMA);
500 }
501
502 // The opaque string should be returned by the client unchanged in all
503 // subsequent requests.
504
505 if (opaque != null)
506 {
507 sbuilder.Append("opaque=");
508 sbuilder.Append(Rest.CS_DQUOTE);
509 sbuilder.Append(opaque);
510 sbuilder.Append(Rest.CS_DQUOTE);
511 sbuilder.Append(Rest.CS_COMMA);
512 }
513
514 // This flag indicates that the authentication was rejected because the
515 // included nonce was stale. The server might use timestamp information
516 // in the nonce to determine this. We do not.
517
518 if (stale != null)
519 {
520 sbuilder.Append("stale=");
521 sbuilder.Append(Rest.CS_DQUOTE);
522 sbuilder.Append(stale);
523 sbuilder.Append(Rest.CS_DQUOTE);
524 sbuilder.Append(Rest.CS_COMMA);
525 }
526
527 // Identifies the algorithm used to produce the digest and checksum.
528 // The default is MD5.
529
530 if (alg != null)
531 {
532 sbuilder.Append("algorithm=");
533 sbuilder.Append(alg);
534 sbuilder.Append(Rest.CS_COMMA);
535 }
536
537 // Theoretically QOP is optional, but it is required by a compliant
538 // with current versions of the scheme. In fact IE requires that QOP
539 // be specified and will refuse to authenticate otherwise.
540
541 if (qop != String.Empty)
542 {
543 sbuilder.Append("qop=");
544 sbuilder.Append(Rest.CS_DQUOTE);
545 sbuilder.Append(qop);
546 sbuilder.Append(Rest.CS_DQUOTE);
547 sbuilder.Append(Rest.CS_COMMA);
548 }
549
550 // This parameter allows for arbitrary extensions to the protocol.
551 // Unrecognized values should be simply ignored.
552
553 if (auth != null)
554 {
555 sbuilder.Append(auth);
556 sbuilder.Append(Rest.CS_COMMA);
557 }
558
559 // We don't know the userid that will be used
560 // so we cannot make any authentication domain
561 // assumptions. So the prefix will determine
562 // this.
563
564 sbuilder.Append("domain=");
565 sbuilder.Append(Rest.CS_DQUOTE);
566 sbuilder.Append(qprefix);
567 sbuilder.Append(Rest.CS_DQUOTE);
568
569 // Generate the authenticate header and we're basically
570 // done.
571
572 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
573
574 }
575
576 }
577
578#endregion authentication_common
579
580#region authentication_basic
581
582 /// <summary>
583 /// Interpret a BASIC authorization claim. Some clients can only
584 /// understand this and also expect it to be the first one
585 /// offered. So we do.
586 /// OpenSim also needs this, as it is the only scheme that allows
587 /// authentication using the hashed passwords stored in the
588 /// user database.
589 /// </summary>
590
591 private void DoBasic(string authdata)
592 {
593
594 string response = null;
595
596 MatchCollection matches = basicParms.Matches(authdata);
597
598 // In the case of basic authentication there is
599 // only expected to be a single argument.
600
601 foreach (Match m in matches)
602 {
603 authparms.Add("response",m.Groups["pval"].Value);
604 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
605 MsgId, "response", m.Groups["pval"].Value);
606 }
607
608 // Did we get a valid response?
609
610 if (authparms.TryGetValue("response", out response))
611 {
612 // Decode
613 response = Rest.Base64ToString(response);
614 Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
615
616 // Extract user & password
617 Match m = reuserPass.Match(response);
618 userName = m.Groups["user"].Value;
619 userPass = m.Groups["pass"].Value;
620
621 // Validate against user database
622 authenticated = Validate(userName,userPass);
623 }
624
625 }
626
627 /// <summary>
628 /// This method provides validation in support of the BASIC
629 /// authentication method. This is not normaly expected to be
630 /// used, but is included for completeness (and because I tried
631 /// it first).
632 /// </summary>
633
634 private bool Validate(string user, string pass)
635 {
636
637 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
638
639 // Both values are required
640
641 if (user == null || pass == null)
642 return false;
643
644 // Eliminate any leading or trailing spaces
645 user = user.Trim();
646
647 return vetPassword(user, pass);
648
649 }
650
651 /// <summary>
652 /// This is used by the BASIC authentication scheme to calculate
653 /// the double hash used by OpenSim to encode user's passwords.
654 /// It returns true, if the supplied password is actually correct.
655 /// If the specified user-id is not recognized, but the password
656 /// matches the God password, then this is accepted as an admin
657 /// session.
658 /// </summary>
659
660 private bool vetPassword(string user, string pass)
661 {
662
663 int x;
664 string first;
665 string last;
666
667 // Distinguish the parts, if necessary
668
669 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
670 {
671 first = user.Substring(0,x);
672 last = user.Substring(x+1);
673 }
674 else
675 {
676 first = user;
677 last = String.Empty;
678 }
679
680 UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last);
681
682 // If we don't recognize the user id, perhaps it is god?
683 if (account == null)
684 return pass == Rest.GodKey;
685
686 return (Rest.AuthServices.Authenticate(account.PrincipalID, pass, 1) != string.Empty);
687
688 }
689
690#endregion authentication_basic
691
692#region authentication_digest
693
694 /// <summary>
695 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
696 /// implementation. It has been tested with Firefox, Java HTTP client,
697 /// and Microsoft's Internet Explorer V7.
698 /// </summary>
699
700 private void DoDigest(string authdata)
701 {
702
703 string response = null;
704
705 // Find all of the values of the for x = "y"
706
707 MatchCollection matches = digestParm1.Matches(authdata);
708
709 foreach (Match m in matches)
710 {
711 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
712 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
713 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
714 }
715
716 // Find all of the values of the for x = y
717
718 matches = digestParm2.Matches(authdata);
719
720 foreach (Match m in matches)
721 {
722 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
723 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
724 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
725 }
726
727 // A response string MUST be returned, otherwise we are
728 // NOT authenticated.
729
730 Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
731
732 if (authparms.TryGetValue("response", out response))
733 {
734
735 string temp = null;
736
737 do
738 {
739
740 string nck = null;
741 string ncl = null;
742
743 // The userid is sent in clear text. Needed for the
744 // verification.
745
746 authparms.TryGetValue("username", out userName);
747
748 // All URI's of which this is a prefix are
749 // optimistically considered to be authenticated by the
750 // client. This is also needed to verify the response.
751
752 authparms.TryGetValue("uri", out authPrefix);
753
754 // There MUST be a nonce string present. We're not preserving any server
755 // side state and we can't validate the MD5 unless the client returns it
756 // to us, as it should.
757
758 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
759 {
760 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
761 break;
762 }
763
764 // If there is an opaque string present, it had better
765 // match what we sent.
766
767 if (authparms.TryGetValue("opaque", out temp))
768 {
769 if (temp != opaque)
770 {
771 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
772 break;
773 }
774 }
775
776 // If an algorithm string is present, it had better
777 // match what we sent.
778
779 if (authparms.TryGetValue("algorithm", out temp))
780 {
781 if (temp != algorithm)
782 {
783 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
784 break;
785 }
786 }
787
788 // Quality of protection considerations...
789
790 if (authparms.TryGetValue("qop", out temp))
791 {
792
793 qop = temp.ToLower(); // replace with actual value used
794
795 // if QOP was specified then
796 // these MUST be present.
797
798 if (!authparms.ContainsKey("cnonce"))
799 {
800 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
801 Fail(Rest.HttpStatusCodeBadRequest);
802 break;
803 }
804
805 cnonce = authparms["cnonce"];
806
807 if (!authparms.TryGetValue("nc", out nck) || nck == null)
808 {
809 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
810 Fail(Rest.HttpStatusCodeBadRequest);
811 break;
812 }
813
814 Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId);
815
816 if (cntable.TryGetValue(nonce, out ncl))
817 {
818 Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl);
819
820 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
821 {
822 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
823 Fail(Rest.HttpStatusCodeBadRequest);
824 break;
825 }
826 cntable[nonce] = nck;
827 }
828 else
829 {
830 lock (cntable) cntable.Add(nonce, nck);
831 }
832
833 }
834 else
835 {
836
837 qop = String.Empty;
838
839 // if QOP was not specified then
840 // these MUST NOT be present.
841 if (authparms.ContainsKey("cnonce"))
842 {
843 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
844 Fail(Rest.HttpStatusCodeBadRequest);
845 break;
846 }
847 if (authparms.ContainsKey("nc"))
848 {
849 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
850 Fail(Rest.HttpStatusCodeBadRequest);
851 break;
852 }
853 }
854
855 // Validate the supplied userid/password info
856
857 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
858
859 }
860 while (false);
861
862 }
863 else
864 Fail(Rest.HttpStatusCodeBadRequest);
865
866 }
867
868 /// <summary>
869 /// This mechanism is used by the digest authentication mechanism
870 /// to return the user's password. In fact, because the OpenSim
871 /// user's passwords are already hashed, and the HTTP mechanism
872 /// does not supply an open password, the hashed passwords cannot
873 /// be used unless the client has used the same salting mechanism
874 /// to has the password before using it in the authentication
875 /// algorithn. This is not inconceivable...
876 /// </summary>
877
878 private string getPassword(string user)
879 {
880
881 int x;
882 string first;
883 string last;
884
885 // Distinguish the parts, if necessary
886
887 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
888 {
889 first = user.Substring(0,x);
890 last = user.Substring(x+1);
891 }
892 else
893 {
894 first = user;
895 last = String.Empty;
896 }
897
898 UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last);
899 // If we don;t recognize the user id, perhaps it is god?
900
901 if (account == null)
902 {
903 Rest.Log.DebugFormat("{0} Administrator", MsgId);
904 return Rest.GodKey;
905 }
906 else
907 {
908 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
909
910 // !!! REFACTORING PROBLEM
911 // This is what it was. It doesn't work in 0.7
912 // Nothing retrieves the password from the authentication service, there's only authentication.
913 //return udata.PasswordHash;
914 return string.Empty;
915 }
916
917 }
918
919 // Validate the request-digest
920
921 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
922 {
923
924 string patt = null;
925 string payl = String.Empty;
926 string KDS = null;
927 string HA1 = null;
928 string HA2 = null;
929 string pass = getPassword(user);
930
931 // Generate H(A1)
932
933 if (algorithm == Rest.Digest_MD5Sess)
934 {
935 if (!sktable.ContainsKey(cnonce))
936 {
937 patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
938 HA1 = HashToString(patt);
939 sktable.Add(cnonce, HA1);
940 }
941 else
942 {
943 HA1 = sktable[cnonce];
944 }
945 }
946 else
947 {
948 patt = String.Format("{0}:{1}:{2}", user, realm, pass);
949 HA1 = HashToString(patt);
950 }
951
952 // Generate H(A2)
953
954 if (qop == "auth-int")
955 {
956 patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
957 }
958 else
959 {
960 patt = String.Format("{0}:{1}", request.HttpMethod, uri);
961 }
962
963 HA2 = HashToString(patt);
964
965 // Generate Digest
966
967 if (qop != String.Empty)
968 {
969 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
970 }
971 else
972 {
973 patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
974 }
975
976 KDS = HashToString(patt);
977
978 // Compare the generated sequence with the original
979
980 return (0 == sc.Compare(KDS, response));
981
982 }
983
984 private string HashToString(string pattern)
985 {
986
987 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
988
989 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
990
991 sbuilder.Length = 0;
992
993 for (int i = 0; i < hash.Length; i++)
994 {
995 sbuilder.Append(hash[i].ToString("x2"));
996 }
997
998 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
999
1000 return sbuilder.ToString();
1001
1002 }
1003
1004#endregion authentication_digest
1005
1006#region service_interface
1007
1008 /// <summary>
1009 /// Conditionally set a normal completion code. This allows a normal
1010 /// execution path to default.
1011 /// </summary>
1012
1013 internal void Complete()
1014 {
1015 if (statusCode == 0)
1016 {
1017 statusCode = Rest.HttpStatusCodeOK;
1018 }
1019 }
1020
1021 /// <summary>
1022 /// Indicate a functionally-dependent conclusion to the
1023 /// request. See Rest.cs for a list of possible values.
1024 /// </summary>
1025
1026 internal void Complete(int code)
1027 {
1028 statusCode = code;
1029 }
1030
1031 /// <summary>
1032 /// Indicate that a request should be redirected, using
1033 /// the HTTP completion codes. Permanent and temporary
1034 /// redirections may be indicated. The supplied URL is
1035 /// the new location of the resource.
1036 /// </summary>
1037
1038 internal void Redirect(string Url, bool temp)
1039 {
1040
1041 redirectLocation = Url;
1042
1043 if (temp)
1044 {
1045 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
1046 }
1047 else
1048 {
1049 statusCode = Rest.HttpStatusCodePermanentRedirect;
1050 }
1051
1052 Fail(statusCode, String.Empty, true);
1053
1054 }
1055
1056 /// <summary>
1057 /// Fail for an arbitrary reason. Just a failure with
1058 /// headers. The supplied message will be returned in the
1059 /// message body.
1060 /// </summary>
1061
1062 internal void Fail(int code)
1063 {
1064 Fail(code, String.Empty, false);
1065 }
1066
1067 /// <summary>
1068 /// For the more adventurous. This failure also includes a
1069 /// specified entity to be appended to the code-related
1070 /// status string.
1071 /// </summary>
1072
1073 internal void Fail(int code, string addendum)
1074 {
1075 Fail(code, addendum, false);
1076 }
1077
1078 internal void Fail(int code, string addendum, bool reset)
1079 {
1080
1081 statusCode = code;
1082 appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));
1083
1084 // Add any final addendum to the status information
1085
1086 if (addendum != String.Empty)
1087 {
1088 appendStatus(String.Format(addendum));
1089 }
1090
1091 // Help us understand why the request is being rejected
1092
1093 if (Rest.DEBUG)
1094 {
1095 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
1096 Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
1097 Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
1098 Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
1099 Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
1100 Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
1101 Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
1102 Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
1103 Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
1104 Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
1105 Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
1106 Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
1107 Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
1108 }
1109
1110 fail = true;
1111
1112 // Respond to the client's request, tag the response (for the
1113 // benefit of trace) to indicate the reason.
1114
1115 Respond(String.Format("Failure response: ({0}) : {1} ",
1116 code, Rest.HttpStatusDesc[code]));
1117
1118 // Finally initialize and the throw a RestException. All of the
1119 // handler's infrastructure knows that this is a "normal"
1120 // completion from a code point-of-view.
1121
1122 RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");
1123
1124 re.statusCode = code;
1125 re.statusDesc = Rest.HttpStatusDesc[code];
1126 re.httpmethod = method;
1127 re.httppath = path;
1128
1129 throw re;
1130
1131 }
1132
1133 // Reject this request
1134
1135 internal void Reject()
1136 {
1137 Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
1138 }
1139
1140 // This MUST be called by an agent handler before it returns
1141 // control to Handle, otherwise the request will be ignored.
1142 // This is called implciitly for the REST stream handlers and
1143 // is harmless if it is called twice.
1144
1145 internal virtual bool Respond(string reason)
1146 {
1147
1148
1149 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
1150
1151 // We do this to try and make multiple Respond requests harmless,
1152 // as it is sometimes convenient to isse a response without
1153 // certain knowledge that it has not previously been done.
1154
1155 if (!handled)
1156 {
1157
1158 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
1159 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
1160
1161 // A Head request can NOT have a body! So don't waste time on
1162 // formatting if we're going to reject it anyway!
1163
1164 if (method != Rest.HEAD)
1165 {
1166
1167 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
1168
1169 // If the writer is non-null then we know that an XML
1170 // data component exists. Flush and close the writer and
1171 // then convert the result to the expected buffer format
1172 // unless the request has already been failed for some
1173 // reason.
1174
1175 if (writer != null)
1176 {
1177 Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
1178 Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
1179 writer.Flush();
1180 writer.Close();
1181 if (!fail)
1182 {
1183 buffer = xmldata.ToArray();
1184 AddHeader("Content-Type","application/xml");
1185 }
1186 xmldata.Close();
1187 Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
1188 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
1189 }
1190
1191 if (buffer == null && body != null)
1192 {
1193 buffer = encoding.GetBytes(body);
1194 AddHeader("Content-Type",bodyType);
1195 }
1196
1197 // OK, if the buffer contains something, regardless of how
1198 // it got there, set various response headers accordingly.
1199
1200 if (buffer != null)
1201 {
1202 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
1203 }
1204 else
1205 {
1206 if (statusBody != String.Empty)
1207 {
1208 statusBody += Rest.statusTail;
1209 buffer = encoding.GetBytes(statusBody);
1210 AddHeader("Content-Type","text/html");
1211 }
1212 else
1213 {
1214 statusBody = Rest.statusHead;
1215 appendStatus(String.Format(": ({0}) {1}",
1216 statusCode, Rest.HttpStatusDesc[statusCode]));
1217 statusBody += Rest.statusTail;
1218 buffer = encoding.GetBytes(statusBody);
1219 AddHeader("Content-Type","text/html");
1220 }
1221 }
1222
1223 response.ContentLength64 = buffer.Length;
1224
1225 if (response.ContentEncoding == null)
1226 response.ContentEncoding = encoding;
1227
1228 response.SendChunked = chunked;
1229 response.KeepAlive = keepAlive;
1230
1231 }
1232
1233 // Set the status code & description. If nothing has been stored,
1234 // we consider that a success.
1235
1236 if (statusCode == 0)
1237 {
1238 Complete();
1239 }
1240
1241 // Set the response code in the actual carrier
1242
1243 response.StatusCode = statusCode;
1244
1245 // For a redirect we need to set the relocation header accordingly
1246
1247 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
1248 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
1249 {
1250 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
1251 response.RedirectLocation = redirectLocation;
1252 }
1253
1254 // And include the status description if provided.
1255
1256 response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];
1257
1258 // Finally we send back our response.
1259
1260 // We've left the setting of handled' until the
1261 // last minute because the header settings included
1262 // above are pretty harmless. But everything from
1263 // here on down probably leaves the response
1264 // element unusable by anyone else.
1265
1266 handled = true;
1267
1268 // DumpHeaders();
1269
1270 // if (request.InputStream != null)
1271 // {
1272 // Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
1273 // request.InputStream.Close();
1274 // }
1275
1276 if (buffer != null && buffer.Length != 0)
1277 {
1278 Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length);
1279 // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
1280 // MsgId, buffer.Length, encoding.GetString(buffer));
1281 response.OutputStream.Write(buffer, 0, buffer.Length);
1282 }
1283
1284 // Closing the outputstream should complete the transmission process
1285
1286 Rest.Log.DebugFormat("{0} Sending response", MsgId);
1287 // response.OutputStream.Close();
1288 response.Send();
1289
1290 }
1291
1292 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
1293
1294 return handled;
1295
1296 }
1297
1298 /// <summary>
1299 /// These methods allow a service provider to manipulate the
1300 /// request/response headers. The DumpHeaders method is intended
1301 /// for problem diagnosis.
1302 /// </summary>
1303
1304 internal void AddHeader(string hdr, string data)
1305 {
1306 if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data);
1307 response.AddHeader(hdr, data);
1308 }
1309
1310 // internal void RemoveHeader(string hdr)
1311 // {
1312 // if (Rest.DEBUG)
1313 // {
1314 // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
1315 // if (response.Headers.Get(hdr) == null)
1316 // {
1317 // Rest.Log.DebugFormat("{0} No such header existed",
1318 // MsgId, hdr);
1319 // }
1320 // }
1321 // response.Headers.Remove(hdr);
1322 // }
1323
1324 // internal void DumpHeaders()
1325 // {
1326 // if (Rest.DEBUG)
1327 // {
1328 // for (int i=0;i<response.Headers.Count;i++)
1329 // {
1330 // Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
1331 // response.Headers.Get(i));
1332 // }
1333 // }
1334 // }
1335
1336 // Setup the XML writer for output
1337
1338 internal void initXmlWriter()
1339 {
1340 XmlWriterSettings settings = new XmlWriterSettings();
1341 xmldata = new MemoryStream();
1342 settings.Indent = true;
1343 settings.IndentChars = " ";
1344 settings.Encoding = encoding;
1345 settings.CloseOutput = false;
1346 settings.OmitXmlDeclaration = true;
1347 settings.ConformanceLevel = ConformanceLevel.Fragment;
1348 writer = XmlWriter.Create(xmldata, settings);
1349 }
1350
1351 internal void initXmlReader()
1352 {
1353
1354 XmlReaderSettings settings = new XmlReaderSettings();
1355
1356 settings.ConformanceLevel = ConformanceLevel.Fragment;
1357 settings.IgnoreComments = true;
1358 settings.IgnoreWhitespace = true;
1359 settings.IgnoreProcessingInstructions = true;
1360 settings.ValidationType = ValidationType.None;
1361
1362 reader = XmlReader.Create(request.InputStream,settings);
1363
1364 }
1365
1366 internal void appendStatus(string msg)
1367 {
1368 if (statusBody == String.Empty)
1369 {
1370 statusBody = String.Format(Rest.statusHead, request.HttpMethod);
1371 }
1372
1373 statusBody = String.Format("{0} {1}", statusBody, msg);
1374 }
1375
1376#endregion service_interface
1377
1378#region internal_methods
1379
1380 /// <summary>
1381 /// Helper methods for deconstructing and reconstructing
1382 /// URI path data.
1383 /// </summary>
1384
1385 private void initUrl()
1386 {
1387
1388 uri = request.Url;
1389
1390 if (query == null)
1391 {
1392 query = uri.Query;
1393 }
1394
1395 // If the path has not been previously initialized,
1396 // do so now.
1397
1398 if (path == null)
1399 {
1400 path = uri.AbsolutePath;
1401 if (path.EndsWith(Rest.UrlPathSeparator))
1402 path = path.Substring(0,path.Length-1);
1403 }
1404
1405 // If we succeeded in getting a path, perform any
1406 // additional pre-processing required.
1407
1408 if (path != null)
1409 {
1410 if (Rest.ExtendedEscape)
1411 {
1412 // Handle "+". Not a standard substitution, but
1413 // common enough...
1414 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1415 }
1416 pathNodes = path.Split(Rest.CA_PATHSEP);
1417 }
1418 else
1419 {
1420 pathNodes = EmptyPath;
1421 }
1422
1423 // Elimiate any %-escaped values. This is left until here
1424 // so that escaped "+' are not mistakenly replaced.
1425
1426 path = Uri.UnescapeDataString(path);
1427
1428 // Request server context info
1429
1430 hostname = uri.Host;
1431 port = uri.Port;
1432
1433 }
1434
1435 private int initParameters(int prfxlen)
1436 {
1437
1438 if (prfxlen < path.Length-1)
1439 {
1440 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
1441 }
1442 else
1443 {
1444 parameters = new string[0];
1445 }
1446
1447 // Generate a debug list of the decoded parameters
1448
1449 if (Rest.DEBUG && prfxlen < path.Length-1)
1450 {
1451 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1452 for (int i = 0; i < parameters.Length; i++)
1453 {
1454 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1455 }
1456 }
1457
1458 return parameters.Length;
1459
1460 }
1461
1462#endregion internal_methods
1463
1464 }
1465}