Merge pull request #2208 from martinbooth/dataannotation_strings_from_corefx_without_resx
[mono.git] / mcs / class / System / Mono.Net.Security / LegacySslStream.cs
1 //
2 // System.Net.Security.SslStream.cs
3 //
4 // Authors:
5 //      Tim Coleman (tim@timcoleman.com)
6 //      Atsushi Enomoto (atsushi@ximian.com)
7 //      Marek Safar (marek.safar@gmail.com)
8 //
9 // Copyright (C) Tim Coleman, 2004
10 // (c) 2004,2007 Novell, Inc. (http://www.novell.com)
11 // Copyright 2011 Xamarin Inc.
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 #if SECURITY_DEP
36
37 #if MONO_X509_ALIAS
38 extern alias PrebuiltSystem;
39 #endif
40 #if MONO_SECURITY_ALIAS
41 extern alias MonoSecurity;
42 #endif
43
44 #if MONO_SECURITY_ALIAS
45 using MonoCipherAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.CipherAlgorithmType;
46 using MonoHashAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.HashAlgorithmType;
47 using MonoExchangeAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
48 using MonoSecurityProtocolType = MonoSecurity::Mono.Security.Protocol.Tls.SecurityProtocolType;
49 using MonoSecurity::Mono.Security.Protocol.Tls;
50 using MonoSecurity::Mono.Security.Interface;
51 #else
52 using MonoCipherAlgorithmType = Mono.Security.Protocol.Tls.CipherAlgorithmType;
53 using MonoHashAlgorithmType = Mono.Security.Protocol.Tls.HashAlgorithmType;
54 using MonoExchangeAlgorithmType = Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
55 using MonoSecurityProtocolType = Mono.Security.Protocol.Tls.SecurityProtocolType;
56 using Mono.Security.Protocol.Tls;
57 using Mono.Security.Interface;
58 #endif
59 #if MONO_X509_ALIAS
60 using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
61 #endif
62
63 using CipherAlgorithmType = System.Security.Authentication.CipherAlgorithmType;
64 using HashAlgorithmType = System.Security.Authentication.HashAlgorithmType;
65 using ExchangeAlgorithmType = System.Security.Authentication.ExchangeAlgorithmType;
66
67 using System;
68 using System.IO;
69 using System.Net;
70 using System.Net.Security;
71 using System.Security.Authentication;
72 using System.Security.Cryptography.X509Certificates;
73 using System.Security.Principal;
74 using System.Security.Cryptography;
75
76 using System.Threading.Tasks;
77
78 namespace Mono.Net.Security 
79 {
80         [MonoTODO ("Non-X509Certificate2 certificate is not supported")]
81         internal class LegacySslStream : AuthenticatedStream, IMonoSslStream
82         {
83                 #region Fields
84
85                 SslStreamBase ssl_stream;
86                 MonoTlsProvider provider;
87                 MonoTlsSettings settings;
88                 ICertificateValidator certificateValidator;
89
90                 #endregion // Fields
91
92                 #region Constructors
93
94                 public LegacySslStream (Stream innerStream, bool leaveInnerStreamOpen, MonoTlsProvider provider, MonoTlsSettings settings)
95                         : base (innerStream, leaveInnerStreamOpen)
96                 {
97                         this.provider = provider;
98                         this.settings = settings;
99                         this.certificateValidator = ChainValidationHelper.GetDefaultValidator (provider, settings);
100                 }
101                 #endregion // Constructors
102
103                 #region Properties
104
105                 public override bool CanRead {
106                         get { return InnerStream.CanRead; }
107                 }
108
109                 public override bool CanSeek {
110                         get { return InnerStream.CanSeek; }
111                 }
112
113                 public override bool CanTimeout {
114                         get { return InnerStream.CanTimeout; }
115                 }
116
117                 public override bool CanWrite {
118                         get { return InnerStream.CanWrite; }
119                 }
120
121                 public override long Length {
122                         get { return InnerStream.Length; }
123                 }
124
125                 public override long Position {
126                         get { return InnerStream.Position; }
127                         set {
128                                 throw new NotSupportedException ("This stream does not support seek operations");
129                         }
130                 }
131
132                 // AuthenticatedStream overrides
133
134                 public override bool IsAuthenticated { 
135                         get { return ssl_stream != null; }
136                 }
137
138                 public override bool IsEncrypted { 
139                         get { return IsAuthenticated; }
140                 }
141
142                 public override bool IsMutuallyAuthenticated { 
143                         get { return IsAuthenticated && (IsServer ? RemoteCertificate != null : LocalCertificate != null); }
144                 }
145
146                 public override bool IsServer { 
147                         get { return ssl_stream is SslServerStream; }
148                 }
149
150                 public override bool IsSigned { 
151                         get { return IsAuthenticated; }
152                 }
153
154                 public override int ReadTimeout {
155                         get { return InnerStream.ReadTimeout; }
156                         set { InnerStream.ReadTimeout = value; }
157                 }
158
159                 public override int WriteTimeout {
160                         get { return InnerStream.WriteTimeout; }
161                         set { InnerStream.WriteTimeout = value; }
162                 }
163
164                 // SslStream
165
166                 public virtual bool CheckCertRevocationStatus {
167                         get {
168                                 if (!IsAuthenticated)
169                                         return false;
170
171                                 return ssl_stream.CheckCertRevocationStatus;
172                         }
173                 }
174
175                 public virtual CipherAlgorithmType CipherAlgorithm  {
176                         get {
177                                 CheckConnectionAuthenticated ();
178
179                                 switch (ssl_stream.CipherAlgorithm) {
180                                 case MonoCipherAlgorithmType.Des:
181                                         return CipherAlgorithmType.Des;
182                                 case MonoCipherAlgorithmType.None:
183                                         return CipherAlgorithmType.None;
184                                 case MonoCipherAlgorithmType.Rc2:
185                                         return CipherAlgorithmType.Rc2;
186                                 case MonoCipherAlgorithmType.Rc4:
187                                         return CipherAlgorithmType.Rc4;
188                                 case MonoCipherAlgorithmType.SkipJack:
189                                         break;
190                                 case MonoCipherAlgorithmType.TripleDes:
191                                         return CipherAlgorithmType.TripleDes;
192                                 case MonoCipherAlgorithmType.Rijndael:
193                                         switch (ssl_stream.CipherStrength) {
194                                         case 128:
195                                                 return CipherAlgorithmType.Aes128;
196                                         case 192:
197                                                 return CipherAlgorithmType.Aes192;
198                                         case 256:
199                                                 return CipherAlgorithmType.Aes256;
200                                         }
201                                         break;
202                                 }
203
204                                 throw new InvalidOperationException ("Not supported cipher algorithm is in use. It is likely a bug in SslStream.");
205                         }
206                 }
207
208                 public virtual int CipherStrength  {
209                         get {
210                                 CheckConnectionAuthenticated ();
211
212                                 return ssl_stream.CipherStrength;
213                         }
214                 }
215
216                 public virtual HashAlgorithmType HashAlgorithm  {
217                         get {
218                                 CheckConnectionAuthenticated ();
219
220                                 switch (ssl_stream.HashAlgorithm) {
221                                 case MonoHashAlgorithmType.Md5:
222                                         return HashAlgorithmType.Md5;
223                                 case MonoHashAlgorithmType.None:
224                                         return HashAlgorithmType.None;
225                                 case MonoHashAlgorithmType.Sha1:
226                                         return HashAlgorithmType.Sha1;
227                                 }
228
229                                 throw new InvalidOperationException ("Not supported hash algorithm is in use. It is likely a bug in SslStream.");
230                         }
231                 }
232
233                 public virtual int HashStrength  {
234                         get {
235                                 CheckConnectionAuthenticated ();
236
237                                 return ssl_stream.HashStrength;
238                         }
239                 }
240
241                 public virtual ExchangeAlgorithmType KeyExchangeAlgorithm { 
242                         get {
243                                 CheckConnectionAuthenticated ();
244
245                                 switch (ssl_stream.KeyExchangeAlgorithm) {
246                                 case MonoExchangeAlgorithmType.DiffieHellman:
247                                         return ExchangeAlgorithmType.DiffieHellman;
248                                 case MonoExchangeAlgorithmType.Fortezza:
249                                         break;
250                                 case MonoExchangeAlgorithmType.None:
251                                         return ExchangeAlgorithmType.None;
252                                 case MonoExchangeAlgorithmType.RsaKeyX:
253                                         return ExchangeAlgorithmType.RsaKeyX;
254                                 case MonoExchangeAlgorithmType.RsaSign:
255                                         return ExchangeAlgorithmType.RsaSign;
256                                 }
257
258                                 throw new InvalidOperationException ("Not supported exchange algorithm is in use. It is likely a bug in SslStream.");
259                         }
260                 }
261
262                 public virtual int KeyExchangeStrength { 
263                         get {
264                                 CheckConnectionAuthenticated ();
265
266                                 return ssl_stream.KeyExchangeStrength;
267                         }
268                 }
269
270                 X509Certificate IMonoSslStream.InternalLocalCertificate {
271                         get {
272                                 return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
273                         }
274                 }
275
276                 public virtual X509Certificate LocalCertificate {
277                         get {
278                                 CheckConnectionAuthenticated ();
279
280                                 return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
281                         }
282                 }
283
284                 public virtual X509Certificate RemoteCertificate {
285                         get {
286                                 CheckConnectionAuthenticated ();
287                                 return !IsServer ? ssl_stream.ServerCertificate : ((SslServerStream) ssl_stream).ClientCertificate;
288                         }
289                 }
290
291                 public virtual SslProtocols SslProtocol {
292                         get {
293                                 CheckConnectionAuthenticated ();
294
295                                 switch (ssl_stream.SecurityProtocol) {
296                                 case MonoSecurityProtocolType.Default:
297                                         return SslProtocols.Default;
298                                 case MonoSecurityProtocolType.Ssl2:
299                                         return SslProtocols.Ssl2;
300                                 case MonoSecurityProtocolType.Ssl3:
301                                         return SslProtocols.Ssl3;
302                                 case MonoSecurityProtocolType.Tls:
303                                         return SslProtocols.Tls;
304                                 }
305
306                                 throw new InvalidOperationException ("Not supported SSL/TLS protocol is in use. It is likely a bug in SslStream.");
307                         }
308                 }
309
310                 #endregion // Properties
311
312                 #region Methods
313
314 /*
315                 AsymmetricAlgorithm GetPrivateKey (X509Certificate cert, string targetHost)
316                 {
317                         // FIXME: what can I do for non-X509Certificate2 ?
318                         X509Certificate2 cert2 = cert as X509Certificate2;
319                         return cert2 != null ? cert2.PrivateKey : null;
320                 }
321 */
322                 X509Certificate OnCertificateSelection (X509CertificateCollection clientCerts, X509Certificate serverCert, string targetHost, X509CertificateCollection serverRequestedCerts)
323                 {
324                         string [] acceptableIssuers = new string [serverRequestedCerts != null ? serverRequestedCerts.Count : 0];
325                         for (int i = 0; i < acceptableIssuers.Length; i++)
326                                 acceptableIssuers [i] = serverRequestedCerts [i].GetIssuerName ();
327                         X509Certificate clientCertificate;
328                         certificateValidator.SelectClientCertificate (targetHost, clientCerts, serverCert, acceptableIssuers, out clientCertificate);
329                         return clientCertificate;
330                 }
331
332                 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
333                 {
334                         return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false, asyncCallback, asyncState);
335                 }
336
337                 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
338                 {
339                         if (IsAuthenticated)
340                                 throw new InvalidOperationException ("This SslStream is already authenticated");
341
342                         SslClientStream s = new SslClientStream (InnerStream, targetHost, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols), clientCertificates);
343                         s.CheckCertRevocationStatus = checkCertificateRevocation;
344
345                         // Due to the Mono.Security internal, it cannot reuse
346                         // the delegated argument, as Mono.Security creates 
347                         // another instance of X509Certificate which lacks 
348                         // private key but is filled the private key via this
349                         // delegate.
350                         s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string host) {
351                                 string hash = cert.GetCertHashString ();
352                                 // ... so, we cannot use the delegate argument.
353                                 foreach (X509Certificate cc in clientCertificates) {
354                                         if (cc.GetCertHashString () != hash)
355                                                 continue;
356                                         X509Certificate2 cert2 = cc as X509Certificate2;
357                                         cert2 = cert2 ?? new X509Certificate2 (cc);
358                                         return cert2.PrivateKey;
359                                 }
360                                 return null;
361                         };
362
363                         // Even if validation_callback is null this allows us to verify requests where the user
364                         // does not provide a verification callback but attempts to authenticate with the website
365                         // as a client (see https://bugzilla.xamarin.com/show_bug.cgi?id=18962 for an example)
366                         s.ServerCertValidation2 += (mcerts) => {
367                                 X509CertificateCollection certs = null;
368                                 if (mcerts != null) {
369                                         certs = new X509CertificateCollection ();
370                                         for (int i = 0; i < mcerts.Count; i++)
371                                                 certs.Add (new X509Certificate2 (mcerts [i].RawData));
372                                 }
373                                 return ((ChainValidationHelper)certificateValidator).ValidateCertificate (targetHost, false, certs);
374                         };
375                         s.ClientCertSelectionDelegate = OnCertificateSelection;
376
377                         ssl_stream = s;
378
379                         return BeginWrite (new byte [0], 0, 0, asyncCallback, asyncState);
380                 }
381
382                 public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
383                 {
384                         CheckConnectionAuthenticated ();
385
386                         return ssl_stream.BeginRead (buffer, offset, count, asyncCallback, asyncState);
387                 }
388
389                 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
390                 {
391                         return BeginAuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false, asyncCallback, asyncState);
392                 }
393
394                 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
395                 {
396                         if (IsAuthenticated)
397                                 throw new InvalidOperationException ("This SslStream is already authenticated");
398
399                         SslServerStream s = new SslServerStream (InnerStream, serverCertificate, false, clientCertificateRequired, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols));
400                         s.CheckCertRevocationStatus = checkCertificateRevocation;
401                         // Due to the Mono.Security internal, it cannot reuse
402                         // the delegated argument, as Mono.Security creates 
403                         // another instance of X509Certificate which lacks 
404                         // private key but is filled the private key via this
405                         // delegate.
406                         s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string targetHost) {
407                                 // ... so, we cannot use the delegate argument.
408                                 X509Certificate2 cert2 = serverCertificate as X509Certificate2 ?? new X509Certificate2 (serverCertificate);
409                                 return cert2 != null ? cert2.PrivateKey : null;
410                         };
411
412                         s.ClientCertValidationDelegate = delegate (X509Certificate cert, int[] certErrors) {
413                                 var errors = certErrors.Length > 0 ? MonoSslPolicyErrors.RemoteCertificateChainErrors : MonoSslPolicyErrors.None;
414                                 return ((ChainValidationHelper)certificateValidator).ValidateClientCertificate (cert, errors);
415                         };
416
417                         ssl_stream = s;
418
419                         return BeginWrite (new byte[0], 0, 0, asyncCallback, asyncState);
420                 }
421
422                 MonoSecurityProtocolType GetMonoSslProtocol (SslProtocols ms)
423                 {
424                         switch (ms) {
425                         case SslProtocols.Ssl2:
426                                 return MonoSecurityProtocolType.Ssl2;
427                         case SslProtocols.Ssl3:
428                                 return MonoSecurityProtocolType.Ssl3;
429                         case SslProtocols.Tls:
430                                 return MonoSecurityProtocolType.Tls;
431                         default:
432                                 return MonoSecurityProtocolType.Default;
433                         }
434                 }
435
436                 public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
437                 {
438                         CheckConnectionAuthenticated ();
439
440                         return ssl_stream.BeginWrite (buffer, offset, count, asyncCallback, asyncState);
441                 }
442
443                 public virtual void AuthenticateAsClient (string targetHost)
444                 {
445                         AuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false);
446                 }
447
448                 public virtual void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
449                 {
450                         EndAuthenticateAsClient (BeginAuthenticateAsClient (
451                                 targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, null, null));
452                 }
453
454                 public virtual void AuthenticateAsServer (X509Certificate serverCertificate)
455                 {
456                         AuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false);
457                 }
458
459                 public virtual void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
460                 {
461                         EndAuthenticateAsServer (BeginAuthenticateAsServer (
462                                 serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, null, null));
463                 }
464
465                 protected override void Dispose (bool disposing)
466                 {
467                         if (disposing) {
468                                 if (ssl_stream != null)
469                                         ssl_stream.Dispose ();
470                                 ssl_stream = null;
471                         }
472                         base.Dispose (disposing);
473                 }
474
475                 public virtual void EndAuthenticateAsClient (IAsyncResult asyncResult)
476                 {
477                         CheckConnectionAuthenticated ();
478
479                         if (CanRead)
480                                 ssl_stream.EndRead (asyncResult);
481                         else
482                                 ssl_stream.EndWrite (asyncResult);
483                 }
484
485                 public virtual void EndAuthenticateAsServer (IAsyncResult asyncResult)
486                 {
487                         CheckConnectionAuthenticated ();
488
489                         if (CanRead)
490                                 ssl_stream.EndRead (asyncResult);
491                         else
492                                 ssl_stream.EndWrite (asyncResult);
493                 }
494
495                 public override int EndRead (IAsyncResult asyncResult)
496                 {
497                         CheckConnectionAuthenticated ();
498
499                         return ssl_stream.EndRead (asyncResult);
500                 }
501
502                 public override void EndWrite (IAsyncResult asyncResult)
503                 {
504                         CheckConnectionAuthenticated ();
505
506                         ssl_stream.EndWrite (asyncResult);
507                 }
508
509                 public override void Flush ()
510                 {
511                         CheckConnectionAuthenticated ();
512
513                         InnerStream.Flush ();
514                 }
515
516                 public override int Read (byte[] buffer, int offset, int count)
517                 {
518                         return EndRead (BeginRead (buffer, offset, count, null, null));
519                 }
520
521                 public override long Seek (long offset, SeekOrigin origin)
522                 {
523                         throw new NotSupportedException ("This stream does not support seek operations");
524                 }
525
526                 public override void SetLength (long value)
527                 {
528                         InnerStream.SetLength (value);
529                 }
530
531                 public override void Write (byte[] buffer, int offset, int count)
532                 {
533                         EndWrite (BeginWrite (buffer, offset, count, null, null));
534                 }
535
536                 public void Write (byte[] buffer)
537                 {
538                         Write (buffer, 0, buffer.Length);
539                 }
540
541                 void CheckConnectionAuthenticated ()
542                 {
543                         if (!IsAuthenticated)
544                                 throw new InvalidOperationException ("This operation is invalid until it is successfully authenticated");
545                 }
546
547                 public virtual Task AuthenticateAsClientAsync (string targetHost)
548                 {
549                         return Task.Factory.FromAsync (BeginAuthenticateAsClient, EndAuthenticateAsClient, targetHost, null);
550                 }
551
552                 public virtual Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
553                 {
554                         var t = Tuple.Create (targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, this);
555
556                         return Task.Factory.FromAsync ((callback, state) => {
557                                 var d = (Tuple<string, X509CertificateCollection, SslProtocols, bool, LegacySslStream>) state;
558                                 return d.Item5.BeginAuthenticateAsClient (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
559                         }, EndAuthenticateAsClient, t);
560                 }
561
562                 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate)
563                 {
564                         return Task.Factory.FromAsync (BeginAuthenticateAsServer, EndAuthenticateAsServer, serverCertificate, null);
565                 }
566
567                 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
568                 {
569                         var t = Tuple.Create (serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, this);
570
571                         return Task.Factory.FromAsync ((callback, state) => {
572                                 var d = (Tuple<X509Certificate, bool, SslProtocols, bool, LegacySslStream>) state;
573                                 return d.Item5.BeginAuthenticateAsServer (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
574                         }, EndAuthenticateAsServer, t);
575                 }
576
577                 #endregion // Methods
578
579                 #region IMonoSslStream
580
581                 AuthenticatedStream IMonoSslStream.AuthenticatedStream {
582                         get { return this; }
583                 }
584
585                 TransportContext IMonoSslStream.TransportContext {
586                         get { throw new NotSupportedException (); }
587                 }
588
589                 #endregion
590         }
591 }
592
593 #endif