예제 #1
0
        /// <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);
                }
            }
        }
예제 #2
0
		/// <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 ();
		}
예제 #3
0
        /// <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);
                    }
                }
            }
        }