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