/// <summary> /// Establishes a connection to the specified IMAP server. /// </summary> /// <remarks> /// <para>Establishes a connection to an IMAP or IMAP/S server. If the schema /// in the uri is "imap", a clear-text connection is made and defaults to using /// port 143 if no port is specified in the URI. However, if the schema in the /// uri is "imaps", an SSL connection is made using the /// <see cref="ClientCertificates"/> and defaults to port 993 unless a port /// is specified in the URI.</para> /// <para>It should be noted that when using a clear-text IMAP connection, /// if the server advertizes support for the STARTTLS extension, the client /// will automatically switch into TLS mode before authenticating unless the /// <paramref name="uri"/> contains a query string to disable it.</para> /// <para>If the IMAP server advertizes the COMPRESS extension and either does not /// support the STARTTLS extension or the <paramref name="uri"/> explicitly disabled /// the use of the STARTTLS extension, then the client will automatically opt into /// using a compressed data connection to optimize bandwidth usage unless the /// <paramref name="uri"/> contains a query string to explicitly disable it.</para> /// <para>If a successful connection is made, the <see cref="AuthenticationMechanisms"/> /// and <see cref="Capabilities"/> properties will be populated.</para> /// </remarks> /// <param name="uri">The server URI. The <see cref="System.Uri.Scheme"/> should either /// be "imap" to make a clear-text connection or "imaps" to make an SSL connection.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// The <paramref name="uri"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// The <paramref name="uri"/> is not an absolute URI. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="ImapClient"/> is already connected. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// An IMAP protocol error occurred. /// </exception> public void Connect(Uri uri, CancellationToken cancellationToken) { CheckDisposed (); if (uri == null) throw new ArgumentNullException ("uri"); if (!uri.IsAbsoluteUri) throw new ArgumentException ("The uri must be absolute.", "uri"); if (IsConnected) throw new InvalidOperationException ("The ImapClient is already connected."); var imaps = uri.Scheme.ToLowerInvariant () == "imaps"; var port = uri.Port > 0 ? uri.Port : (imaps ? 993 : 143); var query = uri.ParsedQuery (); Stream stream; string value; var starttls = !imaps && (!query.TryGetValue ("starttls", out value) || Convert.ToBoolean (value)); var compress = !imaps && (!query.TryGetValue ("compress", out value) || Convert.ToBoolean (value)); #if !NETFX_CORE var ipAddresses = Dns.GetHostAddresses (uri.DnsSafeHost); Socket socket = null; for (int i = 0; i < ipAddresses.Length; i++) { socket = new Socket (ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); cancellationToken.ThrowIfCancellationRequested (); try { socket.Connect (ipAddresses[i], port); break; } catch { if (i + 1 == ipAddresses.Length) throw; } } if (imaps) { var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient (uri.Host, ClientCertificates, SslProtocols.Default, true); stream = ssl; } else { stream = new NetworkStream (socket, true); } #else socket = new StreamSocket (); cancellationToken.ThrowIfCancellationRequested (); socket.ConnectAsync (new HostName (uri.DnsSafeHost), port.ToString (), imaps ? SocketProtectionLevel.Tls12 : SocketProtectionLevel.PlainSocket) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); stream = new DuplexStream (socket.InputStream.AsStreamForRead (), socket.OutputStream.AsStreamForWrite ()); #endif host = uri.Host; logger.LogConnect (uri); engine.Connect (new ImapStream (stream, logger), cancellationToken); // Only query the CAPABILITIES if the greeting didn't include them. if (engine.CapabilitiesVersion == 0) engine.QueryCapabilities (cancellationToken); if (starttls && (engine.Capabilities & ImapCapabilities.StartTLS) != 0) { var ic = engine.QueueCommand (cancellationToken, null, "STARTTLS\r\n"); engine.Wait (ic); if (ic.Result == ImapCommandResult.Ok) { #if !NETFX_CORE var tls = new SslStream (stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient (uri.Host, ClientCertificates, SslProtocols.Tls, true); engine.Stream.Stream = tls; #else socket.UpgradeToSslAsync (SocketProtectionLevel.Tls12, new HostName (uri.DnsSafeHost)) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); #endif // Query the CAPABILITIES again if the server did not include an // untagged CAPABILITIES response to the STARTTLS command. if (engine.CapabilitiesVersion == 1) engine.QueryCapabilities (cancellationToken); } } else if (compress && (engine.Capabilities & ImapCapabilities.Compress) != 0) { var ic = engine.QueueCommand (cancellationToken, null, "COMPRESS DEFLATE\r\n"); engine.Wait (ic); if (ic.Result == ImapCommandResult.Ok) { var unzip = new DeflateStream (stream, CompressionMode.Decompress); var zip = new DeflateStream (stream, CompressionMode.Compress); engine.Stream.Stream = new DuplexStream (unzip, zip); // Query the CAPABILITIES again if the server did not include an // untagged CAPABILITIES response to the COMPRESS command. if (engine.CapabilitiesVersion == 1) engine.QueryCapabilities (cancellationToken); } } }
/// <summary> /// Establish a connection to the specified IMAP server. /// </summary> /// <remarks> /// <para>Establishes a connection to the specified IMAP or IMAP/S server.</para> /// <para>If the <paramref name="port"/> has a value of <c>0</c>, then the /// <paramref name="options"/> parameter is used to determine the default port to /// connect to. The default port used with <see cref="SecureSocketOptions.SslOnConnect"/> /// is <c>993</c>. All other values will use a default port of <c>143</c>.</para> /// <para>If the <paramref name="options"/> has a value of /// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used /// to determine the default security options. If the <paramref name="port"/> has a value /// of <c>993</c>, then the default options used will be /// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use /// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para> /// <para>Once a connection is established, properties such as /// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be /// populated.</para> /// </remarks> /// <example> /// <code language="c#" source="Examples\ImapExamples.cs" region="DownloadMessages"/> /// </example> /// <param name="host">The host name to connect to.</param> /// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param> /// <param name="options">The secure socket options to when connecting.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="host"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// The <paramref name="host"/> is a zero-length string. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="ImapClient"/> is already connected. /// </exception> /// <exception cref="System.NotSupportedException"> /// <paramref name="options"/> was set to /// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/> /// and the IMAP server does not support the STARTTLS extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// An IMAP protocol error occurred. /// </exception> public override void Connect (string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken)) { if (host == null) throw new ArgumentNullException ("host"); if (host.Length == 0) throw new ArgumentException ("The host name cannot be empty.", "host"); if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException ("port"); CheckDisposed (); if (IsConnected) throw new InvalidOperationException ("The ImapClient is already connected."); Stream stream; bool starttls; Uri uri; ComputeDefaultValues (host, ref port, ref options, out uri, out starttls); #if !NETFX_CORE var ipAddresses = Dns.GetHostAddresses (host); Socket socket = null; for (int i = 0; i < ipAddresses.Length; i++) { socket = new Socket (ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { cancellationToken.ThrowIfCancellationRequested (); socket.Connect (ipAddresses[i], port); break; } catch (OperationCanceledException) { socket.Dispose (); throw; } catch { socket.Dispose (); if (i + 1 == ipAddresses.Length) throw; } } if (socket == null) throw new IOException (string.Format ("Failed to resolve host: {0}", host)); engine.Uri = uri; if (options == SecureSocketOptions.SslOnConnect) { var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient (host, ClientCertificates, DefaultSslProtocols, true); stream = ssl; } else { stream = new NetworkStream (socket, true); } #else var protection = options == SecureSocketOptions.SslOnConnect ? SocketProtectionLevel.Tls12 : SocketProtectionLevel.PlainSocket; socket = new StreamSocket (); try { cancellationToken.ThrowIfCancellationRequested (); socket.ConnectAsync (new HostName (host), port.ToString (), protection) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); } catch { socket.Dispose (); socket = null; throw; } stream = new DuplexStream (socket.InputStream.AsStreamForRead (0), socket.OutputStream.AsStreamForWrite (0)); engine.Uri = uri; #endif if (stream.CanTimeout) { stream.WriteTimeout = timeout; stream.ReadTimeout = timeout; } ProtocolLogger.LogConnect (uri); engine.Connect (new ImapStream (stream, socket, ProtocolLogger), cancellationToken); try { // Only query the CAPABILITIES if the greeting didn't include them. if (engine.CapabilitiesVersion == 0) engine.QueryCapabilities (cancellationToken); if (options == SecureSocketOptions.StartTls && (engine.Capabilities & ImapCapabilities.StartTLS) == 0) throw new NotSupportedException ("The IMAP server does not support the STARTTLS extension."); if (starttls && (engine.Capabilities & ImapCapabilities.StartTLS) != 0) { var ic = engine.QueueCommand (cancellationToken, null, "STARTTLS\r\n"); engine.Wait (ic); if (ic.Response == ImapCommandResponse.Ok) { #if !NETFX_CORE var tls = new SslStream (stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient (host, ClientCertificates, DefaultSslProtocols, true); engine.Stream.Stream = tls; #else socket.UpgradeToSslAsync (SocketProtectionLevel.Tls12, new HostName (host)) .AsTask (cancellationToken) .GetAwaiter () .GetResult (); #endif // Query the CAPABILITIES again if the server did not include an // untagged CAPABILITIES response to the STARTTLS command. if (engine.CapabilitiesVersion == 1) engine.QueryCapabilities (cancellationToken); } else if (options == SecureSocketOptions.StartTls) { throw ImapCommandException.Create ("STARTTLS", ic); } } } catch { engine.Disconnect (); throw; } engine.Disconnected += OnEngineDisconnected; OnConnected (); }
/// <summary> /// Establishes a connection to the specified IMAP server. /// </summary> /// <remarks> /// <para>Establishes a connection to an IMAP or IMAP/S server. If the schema /// in the uri is "imap", a clear-text connection is made and defaults to using /// port 143 if no port is specified in the URI. However, if the schema in the /// uri is "imaps", an SSL connection is made using the /// <see cref="ClientCertificates"/> and defaults to port 993 unless a port /// is specified in the URI.</para> /// <para>It should be noted that when using a clear-text IMAP connection, /// if the server advertizes support for the STARTTLS extension, the client /// will automatically switch into TLS mode before authenticating unless the /// <paramref name="uri"/> contains a query string to disable it.</para> /// <para>If the IMAP server advertizes the COMPRESS extension and either does not /// support the STARTTLS extension or the <paramref name="uri"/> explicitly disabled /// the use of the STARTTLS extension, then the client will automatically opt into /// using a compressed data connection to optimize bandwidth usage unless the /// <paramref name="uri"/> contains a query string to explicitly disable it.</para> /// <para>If a successful connection is made, the <see cref="AuthenticationMechanisms"/> /// and <see cref="Capabilities"/> properties will be populated.</para> /// </remarks> /// <param name="uri">The server URI. The <see cref="System.Uri.Scheme"/> should either /// be "imap" to make a clear-text connection or "imaps" to make an SSL connection.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// The <paramref name="uri"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// The <paramref name="uri"/> is not an absolute URI. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="ImapClient"/> is already connected. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// An IMAP protocol error occurred. /// </exception> public void Connect(Uri uri, CancellationToken cancellationToken) { CheckDisposed(); if (uri == null) { throw new ArgumentNullException("uri"); } if (!uri.IsAbsoluteUri) { throw new ArgumentException("The uri must be absolute.", "uri"); } if (IsConnected) { throw new InvalidOperationException("The ImapClient is already connected."); } var imaps = uri.Scheme.ToLowerInvariant() == "imaps"; var port = uri.Port > 0 ? uri.Port : (imaps ? 993 : 143); var query = uri.ParsedQuery(); Stream stream; string value; var starttls = !imaps && (!query.TryGetValue("starttls", out value) || Convert.ToBoolean(value)); var compress = !imaps && (!query.TryGetValue("compress", out value) || Convert.ToBoolean(value)); #if !NETFX_CORE var ipAddresses = Dns.GetHostAddresses(uri.DnsSafeHost); Socket socket = null; for (int i = 0; i < ipAddresses.Length; i++) { socket = new Socket(ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); cancellationToken.ThrowIfCancellationRequested(); try { socket.Connect(ipAddresses[i], port); break; } catch { if (i + 1 == ipAddresses.Length) { throw; } } } if (imaps) { var ssl = new SslStream(new NetworkStream(socket, true), false, ValidateRemoteCertificate); ssl.AuthenticateAsClient(uri.Host, ClientCertificates, SslProtocols.Default, true); stream = ssl; } else { stream = new NetworkStream(socket, true); } #else socket = new StreamSocket(); cancellationToken.ThrowIfCancellationRequested(); socket.ConnectAsync(new HostName(uri.DnsSafeHost), port.ToString(), imaps ? SocketProtectionLevel.Tls12 : SocketProtectionLevel.PlainSocket) .AsTask(cancellationToken) .GetAwaiter() .GetResult(); stream = new DuplexStream(socket.InputStream.AsStreamForRead(), socket.OutputStream.AsStreamForWrite()); #endif host = uri.Host; logger.LogConnect(uri); engine.Connect(new ImapStream(stream, logger), cancellationToken); // Only query the CAPABILITIES if the greeting didn't include them. if (engine.CapabilitiesVersion == 0) { engine.QueryCapabilities(cancellationToken); } if (starttls && (engine.Capabilities & ImapCapabilities.StartTLS) != 0) { var ic = engine.QueueCommand(cancellationToken, null, "STARTTLS\r\n"); engine.Wait(ic); if (ic.Result == ImapCommandResult.Ok) { #if !NETFX_CORE var tls = new SslStream(stream, false, ValidateRemoteCertificate); tls.AuthenticateAsClient(uri.Host, ClientCertificates, SslProtocols.Tls, true); engine.Stream.Stream = tls; #else socket.UpgradeToSslAsync(SocketProtectionLevel.Tls12, new HostName(uri.DnsSafeHost)) .AsTask(cancellationToken) .GetAwaiter() .GetResult(); #endif // Query the CAPABILITIES again if the server did not include an // untagged CAPABILITIES response to the STARTTLS command. if (engine.CapabilitiesVersion == 1) { engine.QueryCapabilities(cancellationToken); } } } else if (compress && (engine.Capabilities & ImapCapabilities.Compress) != 0) { var ic = engine.QueueCommand(cancellationToken, null, "COMPRESS DEFLATE\r\n"); engine.Wait(ic); if (ic.Result == ImapCommandResult.Ok) { var unzip = new DeflateStream(stream, CompressionMode.Decompress); var zip = new DeflateStream(stream, CompressionMode.Compress); engine.Stream.Stream = new DuplexStream(unzip, zip); // Query the CAPABILITIES again if the server did not include an // untagged CAPABILITIES response to the COMPRESS command. if (engine.CapabilitiesVersion == 1) { engine.QueryCapabilities(cancellationToken); } } } }