2006-04-10 Sebastien Pouliot <sebastien@ximian.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                 // we keep this item valid for 3 minutes (if unused)
37                 private const int ValidityInterval = 3 * 60;
38
39                 private bool disposed;
40                 private DateTime validuntil;
41                 private string host;
42
43                 // see RFC2246 - Section 7
44                 private byte[] sid;
45                 private byte[] masterSecret;
46
47                 public ClientSessionInfo (string hostname, byte[] id)
48                 {
49                         host = hostname;
50                         sid = id;
51                         KeepAlive ();
52                 }
53
54                 ~ClientSessionInfo ()
55                 {
56                         Dispose (false);
57                 }
58
59
60                 public string HostName {
61                         get { return host; }
62                 }
63
64                 public byte[] Id {
65                         get { return sid; }
66                 }
67
68                 public bool Valid {
69                         get { return (validuntil > DateTime.UtcNow); }
70                 }
71
72
73                 public void GetContext (Context context)
74                 {
75                         CheckDisposed ();
76                         masterSecret = (byte[]) context.MasterSecret.Clone ();
77                 }
78
79                 public void SetContext (Context context)
80                 {
81                         CheckDisposed ();
82                         context.MasterSecret = (byte[]) masterSecret.Clone ();
83                 }
84
85                 public void KeepAlive ()
86                 {
87                         CheckDisposed ();
88                         validuntil = DateTime.UtcNow.AddSeconds (ValidityInterval);
89                 }
90
91                 public void Dispose ()
92                 {
93                         Dispose (true);
94                         GC.SuppressFinalize (this);
95                 }
96
97                 private void Dispose (bool disposing)
98                 {
99                         if (!disposed) {
100                                 validuntil = DateTime.MinValue;
101                                 host = null;
102                                 sid = null;
103
104                                 if (masterSecret != null) {
105                                         Array.Clear (masterSecret, 0, masterSecret.Length);
106                                         masterSecret = null;
107                                 }
108                         }
109                         disposed = true;
110                 }
111
112                 private void CheckDisposed ()
113                 {
114                         if (disposed) {
115                                 string msg = Locale.GetText ("Cache session information were disposed.");
116                                 throw new ObjectDisposedException (msg);
117                         }
118                 }
119         }
120
121         // note: locking is aggressive but isn't used often (and we gain much more :)
122         internal class ClientSessionCache {
123
124                 static Hashtable cache;
125                 static object locker;
126
127                 static ClientSessionCache ()
128                 {
129                         cache = new Hashtable ();
130                         locker = new object ();
131                 }
132
133                 // note: we may have multiple connections with a host, so 
134                 // possibly multiple entries per host (each with a different 
135                 // id), so we do not use the host as the hashtable key
136                 static public void Add (string host, byte[] id)
137                 {
138                         lock (locker) {
139                                 string uid = BitConverter.ToString (id);
140                                 ClientSessionInfo si = (ClientSessionInfo) cache[uid];
141                                 if (si == null) {
142                                         cache.Add (uid, new ClientSessionInfo (host, id));
143                                 } else if (si.HostName == host) {
144                                         // we already have this and it's still valid
145                                         // on the server, so we'll keep it a little longer
146                                         si.KeepAlive ();
147                                 } else {
148                                         // it's very unlikely but the same session id 
149                                         // could be used by more than one host. In this
150                                         // case we replace the older one with the new one
151                                         si.Dispose ();
152                                         cache.Remove (uid);
153                                         cache.Add (uid, new ClientSessionInfo (host, id));
154                                 }
155                         }
156                 }
157
158                 // return the first session us
159                 static public byte[] FromHost (string host)
160                 {
161                         lock (locker) {
162                                 foreach (ClientSessionInfo si in cache.Values) {
163                                         if (si.HostName == host) {
164                                                 if (si.Valid) {
165                                                         // ensure it's still valid when we really need it
166                                                         si.KeepAlive ();
167                                                         return si.Id;
168                                                 }
169                                         }
170                                 }
171                                 return null;
172                         }
173                 }
174
175                 // only called inside the lock
176                 static private ClientSessionInfo FromContext (Context context)
177                 {
178                         if (context == null)
179                                 return null;
180
181                         byte[] id = context.SessionId;
182                         if ((id == null) || (id.Length == 0))
183                                 return null;
184
185                         // do we have a session cached for this host ?
186                         string uid = BitConverter.ToString (id);
187
188                         ClientSessionInfo si = (ClientSessionInfo) cache[uid];
189                         if (si == null)
190                                 return null;
191
192                         // In the unlikely case of multiple hosts using the same 
193                         // session id, we just act like we do not know about it
194                         if (context.ClientSettings.TargetHost != si.HostName)
195                                 return null;
196
197                         // yes, so what's its status ?
198                         if (!si.Valid) {
199                                 si.Dispose ();
200                                 cache.Remove (uid);
201                                 return null;
202                         }
203
204                         // ok, it make sense
205                         return si;
206                 }
207
208                 static public bool SetContextInCache (Context context)
209                 {
210                         lock (locker) {
211                                 ClientSessionInfo csi = FromContext (context);
212                                 if (csi == null)
213                                         return false;
214
215                                 csi.GetContext (context);
216                                 csi.KeepAlive ();
217                                 return true;
218                         }
219                 }
220
221                 static public bool SetContextFromCache (Context context)
222                 {
223                         lock (locker) {
224                                 ClientSessionInfo csi = FromContext (context);
225                                 if (csi == null)
226                                         return false;
227
228                                 csi.SetContext (context);
229                                 csi.KeepAlive ();
230                                 return true;
231                         }
232                 }
233         }
234 }