Merge pull request #472 from MelanieT/spmanager_fix
[mono.git] / mcs / class / System / System.Net / ServicePointManager.cs
index 034a5d0d5c6fa057d33360dd0e6c8656c13647db..21de4e04e02fbe8f6e7bb1d3726c3c957e180929 100644 (file)
@@ -36,7 +36,6 @@ using System.Configuration;
 using System.Net.Configuration;
 using System.Security.Cryptography.X509Certificates;
 
-#if NET_2_0
 using System.Globalization;
 using System.Net.Security;
 #if SECURITY_DEP
@@ -47,7 +46,6 @@ using Mono.Security.X509.Extensions;
 using Mono.Security.Protocol.Tls;
 using MSX = Mono.Security.X509;
 #endif
-#endif
 
 //
 // notes:
@@ -116,22 +114,25 @@ namespace System.Net
                private static bool _checkCRL = false;
                private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
 
-#if NET_1_1
 #if TARGET_JVM
                static bool expectContinue = false;
 #else
                static bool expectContinue = true;
 #endif
                static bool useNagle;
-#endif
-#if NET_2_0
                static RemoteCertificateValidationCallback server_cert_cb;
-#endif
+               static bool tcp_keepalive;
+               static int tcp_keepalive_time;
+               static int tcp_keepalive_interval;
 
                // Fields
                
                public const int DefaultNonPersistentConnectionLimit = 4;
+#if MONOTOUCH
+               public const int DefaultPersistentConnectionLimit = 10;
+#else
                public const int DefaultPersistentConnectionLimit = 2;
+#endif
 
 #if !NET_2_1
                const string configKey = "system.net/connectionManagement";
@@ -141,7 +142,7 @@ namespace System.Net
                static ServicePointManager ()
                {
 #if !NET_2_1
-#if NET_2_0 && CONFIGURATION_DEP
+#if CONFIGURATION_DEP
                        object cfg = ConfigurationManager.GetSection (configKey);
                        ConnectionManagementSection s = cfg as ConnectionManagementSection;
                        if (s != null) {
@@ -167,22 +168,14 @@ namespace System.Net
                
                // Properties
                
-#if NET_2_0
                [Obsolete ("Use ServerCertificateValidationCallback instead", false)]
-#endif
                public static ICertificatePolicy CertificatePolicy {
                        get { return policy; }
                        set { policy = value; }
                }
 
-#if NET_1_0
-               // we need it for SslClientStream
-               internal
-#else
                [MonoTODO("CRL checks not implemented")]
-               public
-#endif
-               static bool CheckCertificateRevocationList {
+               public static bool CheckCertificateRevocationList {
                        get { return _checkCRL; }
                        set { _checkCRL = false; }      // TODO - don't yet accept true
                }
@@ -194,10 +187,11 @@ namespace System.Net
                                        throw new ArgumentOutOfRangeException ("value");
 
                                defaultConnectionLimit = value; 
+                if (manager != null)
+                                       manager.Add ("*", defaultConnectionLimit);
                        }
                }
 
-#if NET_2_0
                static Exception GetMustImplement ()
                {
                        return new NotImplementedException ();
@@ -224,7 +218,6 @@ namespace System.Net
                                throw GetMustImplement ();
                        }
                }
-#endif
                
                public static int MaxServicePointIdleTime {
                        get { 
@@ -261,7 +254,6 @@ namespace System.Net
                        set { _securityProtocol = value; }
                }
 
-#if NET_2_0
                public static RemoteCertificateValidationCallback ServerCertificateValidationCallback
                {
                        get {
@@ -271,9 +263,7 @@ namespace System.Net
                                server_cert_cb = value;
                        }
                }
-#endif
 
-#if NET_1_1
                public static bool Expect100Continue {
                        get { return expectContinue; }
                        set { expectContinue = value; }
@@ -283,9 +273,22 @@ namespace System.Net
                        get { return useNagle; }
                        set { useNagle = value; }
                }
-#endif
+
                // Methods
-               
+               public static void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
+               {
+                       if (enabled) {
+                               if (keepAliveTime <= 0)
+                                       throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
+                               if (keepAliveInterval <= 0)
+                                       throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
+                       }
+
+                       tcp_keepalive = enabled;
+                       tcp_keepalive_time = keepAliveTime;
+                       tcp_keepalive_interval = keepAliveInterval;
+               }
+
                public static ServicePoint FindServicePoint (Uri address) 
                {
                        return FindServicePoint (address, GlobalProxySelection.Select);
@@ -302,6 +305,8 @@ namespace System.Net
                                throw new ArgumentNullException ("address");
 
                        RecycleServicePoints ();
+
+                       var origAddress = new Uri (address.Scheme + "://" + address.Authority);
                        
                        bool usesProxy = false;
                        bool useConnect = false;
@@ -320,7 +325,7 @@ namespace System.Net
                        
                        ServicePoint sp = null;
                        lock (servicePoints) {
-                               SPKey key = new SPKey (address, useConnect);
+                               SPKey key = new SPKey (origAddress, useConnect);
                                sp = servicePoints [key] as ServicePoint;
                                if (sp != null)
                                        return sp;
@@ -335,12 +340,11 @@ namespace System.Net
                                int limit = (int) manager.GetMaxConnections (addr);
 #endif
                                sp = new ServicePoint (address, limit, maxServicePointIdleTime);
-#if NET_1_1
                                sp.Expect100Continue = expectContinue;
                                sp.UseNagleAlgorithm = useNagle;
-#endif
                                sp.UsesProxy = usesProxy;
                                sp.UseConnect = useConnect;
+                               sp.SetTcpKeepAlive (tcp_keepalive, tcp_keepalive_time, tcp_keepalive_interval);
                                servicePoints.Add (key, sp);
                        }
                        
@@ -383,10 +387,44 @@ namespace System.Net
                                        servicePoints.Remove (list.GetByIndex (i));
                        }
                }
-#if NET_2_0 && SECURITY_DEP
+#if MOONLIGHT && SECURITY_DEP
+               internal class ChainValidationHelper {
+                       object sender;
+
+                       public ChainValidationHelper (object sender)
+                       {
+                               this.sender = sender;
+                       }
+
+                       // no need to check certificates since we are either
+                       // (a) loading from the site of origin (and we accepted its certificate to load from it)
+                       // (b) loading from a cross-domain site and we downloaded the policy file using the browser stack
+                       //     i.e. the certificate was accepted (or the policy would not be valid)
+                       internal ValidationResult ValidateChain (Mono.Security.X509.X509CertificateCollection certs)
+                       {
+                               return new ValidationResult (true, false, 0);
+                       }
+               }
+#elif SECURITY_DEP
                internal class ChainValidationHelper {
                        object sender;
                        string host;
+                       static bool is_macosx = System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary);
+                       static X509RevocationMode revocation_mode;
+
+                       static ChainValidationHelper ()
+                       {
+#if !MONOTOUCH
+                               revocation_mode = X509RevocationMode.NoCheck;
+                               try {
+                                       string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
+                                       if (String.IsNullOrEmpty (str))
+                                               return;
+                                       revocation_mode = (X509RevocationMode) Enum.Parse (typeof (X509RevocationMode), str, true);
+                               } catch {
+                               }
+#endif
+                       }
 
                        public ChainValidationHelper (object sender)
                        {
@@ -415,61 +453,93 @@ namespace System.Net
                                ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
                                RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback;
 
-                               X509Chain chain = new X509Chain ();
+                               X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
+                               int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
+                               SslPolicyErrors errors = 0;
+                               X509Chain chain = null;
+                               bool result = false;
+#if MONOTOUCH
+                               // The X509Chain is not really usable with MonoTouch (since the decision is not based on this data)
+                               // However if someone wants to override the results (good or bad) from iOS then they will want all
+                               // the certificates that the server provided (which generally does not include the root) so, only  
+                               // if there's a user callback, we'll create the X509Chain but won't build it
+                               // ref: https://bugzilla.xamarin.com/show_bug.cgi?id=7245
+                               if (cb != null) {
+#endif
+                               chain = new X509Chain ();
                                chain.ChainPolicy = new X509ChainPolicy ();
+                               chain.ChainPolicy.RevocationMode = revocation_mode;
                                for (int i = 1; i < certs.Count; i++) {
                                        X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData);
                                        chain.ChainPolicy.ExtraStore.Add (c2);
                                }
-
-                               X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
-                               int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
-                               SslPolicyErrors errors = 0;
-                               if (!chain.Build (leaf))
-                                       errors |= GetErrorsFromChain (chain);
-                               if (!CheckCertificateUsage (leaf)) {
-                                       errors |= SslPolicyErrors.RemoteCertificateChainErrors;
-                                       status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
+#if MONOTOUCH
                                }
-                               if (!CheckServerIdentity (leaf, Host)) {
-                                       errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
-                                       status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
+#else
+                               try {
+                                       if (!chain.Build (leaf))
+                                               errors |= GetErrorsFromChain (chain);
+                               } catch (Exception e) {
+                                       Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
+                                       Console.Error.WriteLine ("Please, report this problem to the Mono team");
+                                       errors |= SslPolicyErrors.RemoteCertificateChainErrors;
                                }
 
-                               bool result = false;
-                               // No certificate root found means no mozroots or monotouch
-                               if (Environment.OSVersion.Platform == PlatformID.MacOSX) {
-#if !MONOTOUCH
-                               if (System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary)) {
+                               // for OSX and iOS we're using the native API to check for the SSL server policy and host names
+                               if (!is_macosx) {
+                                       if (!CheckCertificateUsage (leaf)) {
+                                               errors |= SslPolicyErrors.RemoteCertificateChainErrors;
+                                               status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
+                                       }
+
+                                       if (!CheckServerIdentity (certs [0], Host)) {
+                                               errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
+                                               status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
+                                       }
+                               } else {
 #endif
                                        // Attempt to use OSX certificates
                                        // Ideally we should return the SecTrustResult
-                                       MSX.OSX509Certificates.SecTrustResult trustResult;
+                                       MSX.OSX509Certificates.SecTrustResult trustResult = MSX.OSX509Certificates.SecTrustResult.Deny;
                                        try {
-                                               trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs);
+                                               trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs, Host);
                                                // We could use the other values of trustResult to pass this extra information
                                                // to the .NET 2 callback for values like SecTrustResult.Confirm
                                                result = (trustResult == MSX.OSX509Certificates.SecTrustResult.Proceed ||
                                                                  trustResult == MSX.OSX509Certificates.SecTrustResult.Unspecified);
-
                                        } catch {
                                                // Ignore
                                        }
-                                       // Clear error status if the OS told us to trust the certificate
+                                       
                                        if (result) {
-                                               status11 = 0;
+                                               // TrustEvaluateSsl was successful so there's no trust error
+                                               // IOW we discard our own chain (since we trust OSX one instead)
                                                errors = 0;
+                                       } else {
+                                               // callback and DefaultCertificatePolicy needs this since 'result' is not specified
+                                               status11 = (int) trustResult;
+                                               errors |= SslPolicyErrors.RemoteCertificateChainErrors;
                                        }
 #if !MONOTOUCH
                                }
 #endif
+
+#if MONODROID
+                               result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
+                               if (result) {
+                                       // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
+                                       // Android (there are no mozroots or preinstalled root certificates),
+                                       // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
+                                       // Android just verified the chain; clear RemoteCertificateChainErrors.
+                                       errors  &= ~SslPolicyErrors.RemoteCertificateChainErrors;
                                }
+#endif
 
                                if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) {
                                        ServicePoint sp = null;
                                        HttpWebRequest req = sender as HttpWebRequest;
                                        if (req != null)
-                                               sp = req.ServicePoint;
+                                               sp = req.ServicePointNoLock;
                                        if (status11 == 0 && errors != 0)
                                                status11 = GetStatusFromChain (chain);
 
@@ -543,7 +613,7 @@ namespace System.Net
                                }
                                return (int) result;
                        }
-
+#if !MONOTOUCH
                        static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
                        {
                                SslPolicyErrors errors = SslPolicyErrors.None;
@@ -565,37 +635,43 @@ namespace System.Net
                        // DH certificates requires some changes - does anyone use one ?
                        static bool CheckCertificateUsage (X509Certificate2 cert) 
                        {
-                               // certificate extensions are required for this
-                               // we "must" accept older certificates without proofs
-                               if (cert.Version < 3)
-                                       return true;
-
-                               X509KeyUsageExtension kux = (X509KeyUsageExtension) cert.Extensions ["2.5.29.15"];
-                               X509EnhancedKeyUsageExtension eku = (X509EnhancedKeyUsageExtension) cert.Extensions ["2.5.29.37"];
-                               if (kux != null && eku != null) {
-                                       // RFC3280 states that when both KeyUsageExtension and 
-                                       // ExtendedKeyUsageExtension are present then BOTH should
-                                       // be valid
-                                       if ((kux.KeyUsages & s_flags) == 0)
-                                               return false;
-                                       return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
-                                               eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
-                               } else if (kux != null) {
-                                       return ((kux.KeyUsages & s_flags) != 0);
-                               } else if (eku != null) {
-                                       // Server Authentication (1.3.6.1.5.5.7.3.1) or
-                                       // Netscape Server Gated Crypto (2.16.840.1.113730.4)
-                                       return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
-                                               eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
-                               }
+                               try {
+                                       // certificate extensions are required for this
+                                       // we "must" accept older certificates without proofs
+                                       if (cert.Version < 3)
+                                               return true;
+
+                                       X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
+                                       X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
+                                       if (kux != null && eku != null) {
+                                               // RFC3280 states that when both KeyUsageExtension and 
+                                               // ExtendedKeyUsageExtension are present then BOTH should
+                                               // be valid
+                                               if ((kux.KeyUsages & s_flags) == 0)
+                                                       return false;
+                                               return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
+                                                       eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
+                                       } else if (kux != null) {
+                                               return ((kux.KeyUsages & s_flags) != 0);
+                                       } else if (eku != null) {
+                                               // Server Authentication (1.3.6.1.5.5.7.3.1) or
+                                               // Netscape Server Gated Crypto (2.16.840.1.113730.4)
+                                               return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
+                                                       eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
+                                       }
 
-                               // last chance - try with older (deprecated) Netscape extensions
-                               X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
-                               if (ext != null) {
-                                       string text = ext.NetscapeCertType (false);
-                                       return text.IndexOf ("SSL Server Authentication") != -1;
+                                       // last chance - try with older (deprecated) Netscape extensions
+                                       X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
+                                       if (ext != null) {
+                                               string text = ext.NetscapeCertType (false);
+                                               return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
+                                       }
+                                       return true;
+                               } catch (Exception e) {
+                                       Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
+                                       Console.Error.WriteLine ("Please, report this problem to the Mono team");
+                                       return false;
                                }
-                               return true;
                        }
 
                        // RFC2818 - HTTP Over TLS, Section 3.1
@@ -608,28 +684,33 @@ namespace System.Net
                        // 2.1.         exact match is required
                        // 3.   Use of the most specific Common Name (CN=) in the Subject
                        // 3.1          Existing practice but DEPRECATED
-                       static bool CheckServerIdentity (X509Certificate2 cert, string targetHost) 
+                       static bool CheckServerIdentity (Mono.Security.X509.X509Certificate cert, string targetHost) 
                        {
-                               X509Extension ext = cert.Extensions ["2.5.29.17"];
-                               // 1. subjectAltName
-                               if (ext != null) {
-                                       ASN1 asn = new ASN1 (ext.RawData);
-                                       SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (asn);
-                                       // 1.1 - multiple dNSName
-                                       foreach (string dns in subjectAltName.DNSNames) {
-                                               // 1.2 TODO - wildcard support
-                                               if (Match (targetHost, dns))
-                                                       return true;
-                                       }
-                                       // 2. ipAddress
-                                       foreach (string ip in subjectAltName.IPAddresses) {
-                                               // 2.1. Exact match required
-                                               if (ip == targetHost)
-                                                       return true;
+                               try {
+                                       Mono.Security.X509.X509Extension ext = cert.Extensions ["2.5.29.17"];
+                                       // 1. subjectAltName
+                                       if (ext != null) {
+                                               SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
+                                               // 1.1 - multiple dNSName
+                                               foreach (string dns in subjectAltName.DNSNames) {
+                                                       // 1.2 TODO - wildcard support
+                                                       if (Match (targetHost, dns))
+                                                               return true;
+                                               }
+                                               // 2. ipAddress
+                                               foreach (string ip in subjectAltName.IPAddresses) {
+                                                       // 2.1. Exact match required
+                                                       if (ip == targetHost)
+                                                               return true;
+                                               }
                                        }
+                                       // 3. Common Name (CN=)
+                                       return CheckDomainName (cert.SubjectName, targetHost);
+                               } catch (Exception e) {
+                                       Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
+                                       Console.Error.WriteLine ("Please, report this problem to the Mono team");
+                                       return false;
                                }
-                               // 3. Common Name (CN=)
-                               return CheckDomainName (cert.SubjectName.Format (false), targetHost);
                        }
 
                        static bool CheckDomainName (string subjectName, string targetHost)
@@ -693,6 +774,7 @@ namespace System.Net
                                string start = pattern.Substring (0, index);
                                return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
                        }
+#endif
                }
 #endif
        }