2010-07-25 Carlos Alberto Cortez <calberto.cortez@gmail.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / ClientSessionCache.cs
1 //
2 // ClientSessionCache.cs: Client-side cache for re-using sessions
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // Copyright (C) 2006 Novell (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31
32 namespace Mono.Security.Protocol.Tls {
33
34         internal class ClientSessionInfo : IDisposable {
35
36                 // (by default) we keep this item valid for 3 minutes (if unused)
37                 private const int DefaultValidityInterval = 3 * 60;
38                 private static readonly int ValidityInterval;
39
40                 private bool disposed;
41                 private DateTime validuntil;
42                 private string host;
43
44                 // see RFC2246 - Section 7
45                 private byte[] sid;
46                 private byte[] masterSecret;
47
48                 static ClientSessionInfo ()
49                 {
50                         string user_cache_timeout = Environment.GetEnvironmentVariable ("MONO_TLS_SESSION_CACHE_TIMEOUT");
51                         if (user_cache_timeout == null) {
52                                 ValidityInterval = DefaultValidityInterval;
53                         } else {
54                                 try {
55                                         ValidityInterval = Int32.Parse (user_cache_timeout);
56                                 }
57                                 catch {
58                                         ValidityInterval = DefaultValidityInterval;
59                                 }
60                         }
61                 }
62
63                 public ClientSessionInfo (string hostname, byte[] id)
64                 {
65                         host = hostname;
66                         sid = id;
67                         KeepAlive ();
68                 }
69
70                 ~ClientSessionInfo ()
71                 {
72                         Dispose (false);
73                 }
74
75
76                 public string HostName {
77                         get { return host; }
78                 }
79
80                 public byte[] Id {
81                         get { return sid; }
82                 }
83
84                 public bool Valid {
85                         get { return ((masterSecret != null) && (validuntil > DateTime.UtcNow)); }
86                 }
87
88
89                 public void GetContext (Context context)
90                 {
91                         CheckDisposed ();
92                         if (context.MasterSecret != null)
93                                 masterSecret = (byte[]) context.MasterSecret.Clone ();
94                 }
95
96                 public void SetContext (Context context)
97                 {
98                         CheckDisposed ();
99                         if (masterSecret != null)
100                                 context.MasterSecret = (byte[]) masterSecret.Clone ();
101                 }
102
103                 public void KeepAlive ()
104                 {
105                         CheckDisposed ();
106                         validuntil = DateTime.UtcNow.AddSeconds (ValidityInterval);
107                 }
108
109                 public void Dispose ()
110                 {
111                         Dispose (true);
112                         GC.SuppressFinalize (this);
113                 }
114
115                 private void Dispose (bool disposing)
116                 {
117                         if (!disposed) {
118                                 validuntil = DateTime.MinValue;
119                                 host = null;
120                                 sid = null;
121
122                                 if (masterSecret != null) {
123                                         Array.Clear (masterSecret, 0, masterSecret.Length);
124                                         masterSecret = null;
125                                 }
126                         }
127                         disposed = true;
128                 }
129
130                 private void CheckDisposed ()
131                 {
132                         if (disposed) {
133                                 string msg = Locale.GetText ("Cache session information were disposed.");
134                                 throw new ObjectDisposedException (msg);
135                         }
136                 }
137         }
138
139         // note: locking is aggressive but isn't used often (and we gain much more :)
140         internal class ClientSessionCache {
141
142                 static Hashtable cache;
143                 static object locker;
144
145                 static ClientSessionCache ()
146                 {
147                         cache = new Hashtable ();
148                         locker = new object ();
149                 }
150
151                 // note: we may have multiple connections with a host, so 
152                 // possibly multiple entries per host (each with a different 
153                 // id), so we do not use the host as the hashtable key
154                 static public void Add (string host, byte[] id)
155                 {
156                         lock (locker) {
157                                 string uid = BitConverter.ToString (id);
158                                 ClientSessionInfo si = (ClientSessionInfo) cache[uid];
159                                 if (si == null) {
160                                         cache.Add (uid, new ClientSessionInfo (host, id));
161                                 } else if (si.HostName == host) {
162                                         // we already have this and it's still valid
163                                         // on the server, so we'll keep it a little longer
164                                         si.KeepAlive ();
165                                 } else {
166                                         // it's very unlikely but the same session id 
167                                         // could be used by more than one host. In this
168                                         // case we replace the older one with the new one
169                                         si.Dispose ();
170                                         cache.Remove (uid);
171                                         cache.Add (uid, new ClientSessionInfo (host, id));
172                                 }
173                         }
174                 }
175
176                 // return the first session us
177                 static public byte[] FromHost (string host)
178                 {
179                         lock (locker) {
180                                 foreach (ClientSessionInfo si in cache.Values) {
181                                         if (si.HostName == host) {
182                                                 if (si.Valid) {
183                                                         // ensure it's still valid when we really need it
184                                                         si.KeepAlive ();
185                                                         return si.Id;
186                                                 }
187                                         }
188                                 }
189                                 return null;
190                         }
191                 }
192
193                 // only called inside the lock
194                 static private ClientSessionInfo FromContext (Context context, bool checkValidity)
195                 {
196                         if (context == null)
197                                 return null;
198
199                         byte[] id = context.SessionId;
200                         if ((id == null) || (id.Length == 0))
201                                 return null;
202
203                         // do we have a session cached for this host ?
204                         string uid = BitConverter.ToString (id);
205
206                         ClientSessionInfo si = (ClientSessionInfo) cache[uid];
207                         if (si == null)
208                                 return null;
209
210                         // In the unlikely case of multiple hosts using the same 
211                         // session id, we just act like we do not know about it
212                         if (context.ClientSettings.TargetHost != si.HostName)
213                                 return null;
214
215                         // yes, so what's its status ?
216                         if (checkValidity && !si.Valid) {
217                                 si.Dispose ();
218                                 cache.Remove (uid);
219                                 return null;
220                         }
221
222                         // ok, it make sense
223                         return si;
224                 }
225
226                 static public bool SetContextInCache (Context context)
227                 {
228                         lock (locker) {
229                                 // Don't check the validity because the masterKey of the ClientSessionInfo
230                                 // can still be null when this is called the first time
231                                 ClientSessionInfo csi = FromContext (context, false);
232                                 if (csi == null)
233                                         return false;
234
235                                 csi.GetContext (context);
236                                 csi.KeepAlive ();
237                                 return true;
238                         }
239                 }
240
241                 static public bool SetContextFromCache (Context context)
242                 {
243                         lock (locker) {
244                                 ClientSessionInfo csi = FromContext (context, true);
245                                 if (csi == null)
246                                         return false;
247
248                                 csi.SetContext (context);
249                                 csi.KeepAlive ();
250                                 return true;
251                         }
252                 }
253         }
254 }