smtp_transport_authenticate_sync (CamelService *service,
                                  const gchar *mechanism,
                                  GCancellable *cancellable,
                                  GError **error)
{
	CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
	CamelAuthenticationResult result;
	CamelSasl *sasl;
	CamelStreamBuffer *istream;
	CamelStream *ostream;
	gchar *cmdbuf, *respbuf = NULL, *challenge, *restore_password = NULL;
	gboolean auth_challenge = FALSE, can_try_again = TRUE;
	GError *local_error = NULL;

	if (mechanism == NULL) {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("No SASL mechanism was specified"));
		return CAMEL_AUTHENTICATION_ERROR;
	}

 try_again:
	sasl = camel_sasl_new ("smtp", mechanism, service);
	if (sasl == NULL) {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("No support for %s authentication"), mechanism);
		return CAMEL_AUTHENTICATION_ERROR;
	}

	challenge = camel_sasl_challenge_base64_sync (
		sasl, NULL, cancellable, &local_error);
	if (challenge) {
		auth_challenge = TRUE;
		cmdbuf = g_strdup_printf (
			"AUTH %s %s\r\n", camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism, challenge);
		g_free (challenge);
	} else if (local_error) {
		d (fprintf (stderr, "[SMTP] SASL challenge failed: %s", local_error->message));
		g_propagate_error (error, local_error);
		g_object_unref (sasl);
		return CAMEL_AUTHENTICATION_ERROR;
	} else {
		cmdbuf = g_strdup_printf (
			"AUTH %s\r\n", mechanism);
	}

	istream = smtp_ref_istream (transport);
	ostream = smtp_ref_ostream (transport);

	if (!istream || !ostream) {
		g_free (cmdbuf);
		g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_NOT_CONNECTED,
			_("AUTH command failed: Not connected."));
		goto lose;
	}

	d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
	if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
		g_free (cmdbuf);
		g_prefix_error (error, _("AUTH command failed: "));
		goto lose;
	}
	g_free (cmdbuf);

	respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
	d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));

	while (!camel_sasl_get_authenticated (sasl)) {
		if (!respbuf) {
			/* It's an EOF state on the input stream. */
			if (error && !*error)
				g_set_error (error, CAMEL_SMTP_TRANSPORT_ERROR,
					CAMEL_SMTP_TRANSPORT_ERROR_CONNECTION_LOST, _("Connection cancelled"));
			g_prefix_error (error, _("AUTH command failed: "));
			transport->connected = FALSE;
			goto lose;
		}

		/* the server may have accepted our initial response */
		if (strncmp (respbuf, "235", 3) == 0)
			break;

		/* the server challenge/response should follow a 334 code */
		if (strncmp (respbuf, "334", 3) != 0) {
			if (strncmp (respbuf, "535", 3) == 0) {
				goto rejected;
			}

			smtp_set_error (transport, istream, respbuf, cancellable, error);
			g_prefix_error (error, _("AUTH command failed: "));
			goto lose;
		}

		if (FALSE) {
		broken_smtp_server:
			d (fprintf (
				stderr, "[SMTP] Your SMTP server's implementation "
				"of the %s SASL\nauthentication mechanism is "
				"broken. Please report this to the\n"
				"appropriate vendor and suggest that they "
				"re-read rfc2554 again\nfor the first time "
				"(specifically Section 4).\n",
				mechanism));
		}

		/* eat whtspc */
		for (challenge = respbuf + 4; isspace (*challenge); challenge++);

		challenge = camel_sasl_challenge_base64_sync (
			sasl, challenge, cancellable, error);
		if (challenge == NULL)
			goto break_and_lose;

		g_free (respbuf);
		respbuf = NULL;

		/* send our challenge */
		cmdbuf = g_strdup_printf ("%s\r\n", challenge);
		g_free (challenge);
		d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
		if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
			g_free (cmdbuf);
			goto lose;
		}
		g_free (cmdbuf);

		/* get the server's response */
		respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
		d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
	}

	if (respbuf == NULL)
		goto lose;

	/* Work around broken SASL implementations. */
	if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
		goto broken_smtp_server;

	/* If our authentication data was rejected, destroy the
	 * password so that the user gets prompted to try again. */
	if (strncmp (respbuf, "535", 3) == 0) {
 rejected:
		result = CAMEL_AUTHENTICATION_REJECTED;

		/* Read the continuation, if the server returned it. */
		while (respbuf && respbuf[3] == '-') {
			g_free (respbuf);
			respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
			d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
		}

		if (can_try_again) {
			can_try_again = FALSE;

			if (smtp_password_contains_nonascii (service)) {
				const gchar *password;
				gchar *password_latin1;

				password = camel_service_get_password (service);
				password_latin1 = g_convert_with_fallback (password, -1, "ISO-8859-1", "UTF-8", "", NULL, NULL, NULL);

				if (password_latin1 && g_strcmp0 (password, password_latin1) != 0) {
					restore_password = g_strdup (password);

					camel_service_set_password (service, password_latin1);

					g_clear_object (&istream);
					g_clear_object (&ostream);
					g_object_unref (sasl);
					g_free (password_latin1);
					g_free (respbuf);

					goto try_again;
				} else {
					g_free (password_latin1);
				}
			}
		}
	} else if (strncmp (respbuf, "235", 3) == 0)
		result = CAMEL_AUTHENTICATION_ACCEPTED;
	/* Catch any other errors. */
	else {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("Bad authentication response from server."));
		goto lose;
	}

	goto exit;

break_and_lose:
	/* Get the server out of "waiting for continuation data" mode. */
	d (fprintf (stderr, "[SMTP] sending: *\n"));
	camel_stream_write (ostream, "*\r\n", 3, cancellable, NULL);
	respbuf = camel_stream_buffer_read_line (istream, cancellable, NULL);
	d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));

lose:
	result = CAMEL_AUTHENTICATION_ERROR;

exit:
	if (restore_password) {
		camel_service_set_password (service, restore_password);
		g_free (restore_password);
	}

	g_clear_object (&istream);
	g_clear_object (&ostream);
	g_object_unref (sasl);
	g_free (respbuf);

	return result;
}
smtp_transport_authenticate_sync (CamelService *service,
                                  const gchar *mechanism,
                                  GCancellable *cancellable,
                                  GError **error)
{
	CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
	CamelAuthenticationResult result;
	CamelSasl *sasl;
	CamelStreamBuffer *istream;
	CamelStream *ostream;
	gchar *cmdbuf, *respbuf = NULL, *challenge, *restore_password = NULL;
	gboolean auth_challenge = FALSE, can_try_again = TRUE;
	GError *local_error = NULL;

	if (mechanism == NULL) {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("No SASL mechanism was specified"));
		return CAMEL_AUTHENTICATION_ERROR;
	}

 try_again:
	sasl = camel_sasl_new ("smtp", mechanism, service);
	if (sasl == NULL) {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("No support for %s authentication"), mechanism);
		return CAMEL_AUTHENTICATION_ERROR;
	}

	challenge = camel_sasl_challenge_base64_sync (
		sasl, NULL, cancellable, &local_error);
	if (challenge) {
		auth_challenge = TRUE;
		cmdbuf = g_strdup_printf (
			"AUTH %s %s\r\n", camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism, challenge);
		g_free (challenge);
	} else if (local_error) {
		d (fprintf (stderr, "[SMTP] SASL challenge failed: %s", local_error->message));
		g_propagate_error (error, local_error);
		g_object_unref (sasl);
		return CAMEL_AUTHENTICATION_ERROR;
	} else {
		cmdbuf = g_strdup_printf (
			"AUTH %s\r\n", mechanism);
	}

	istream = smtp_ref_istream (transport);
	ostream = smtp_ref_ostream (transport);

	if (!istream || !ostream) {
		g_free (cmdbuf);
		g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_NOT_CONNECTED,
			_("AUTH command failed: Not connected."));
		goto lose;
	}

	d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
	if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
		g_free (cmdbuf);
		g_prefix_error (error, _("AUTH command failed: "));
		goto lose;
	}
	g_free (cmdbuf);

	respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
	d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));

	while (!camel_sasl_get_authenticated (sasl)) {
		if (!respbuf) {
			 
			if (error && !*error)
				g_set_error (error, CAMEL_SMTP_TRANSPORT_ERROR,
					CAMEL_SMTP_TRANSPORT_ERROR_CONNECTION_LOST, _("Connection cancelled"));
			g_prefix_error (error, _("AUTH command failed: "));
			transport->connected = FALSE;
			goto lose;
		}

		 
		if (strncmp (respbuf, "235", 3) == 0)
			break;

		 
		if (strncmp (respbuf, "334", 3) != 0) {
			if (strncmp (respbuf, "535", 3) == 0) {
				goto rejected;
			}

			smtp_set_error (transport, istream, respbuf, cancellable, error);
			g_prefix_error (error, _("AUTH command failed: "));
			goto lose;
		}

		if (FALSE) {
		broken_smtp_server:
			d (fprintf (
				stderr, "[SMTP] Your SMTP server's implementation "
				"of the %s SASL\nauthentication mechanism is "
				"broken. Please report this to the\n"
				"appropriate vendor and suggest that they "
				"re-read rfc2554 again\nfor the first time "
				"(specifically Section 4).\n",
				mechanism));
		}

		 
		for (challenge = respbuf + 4; isspace (*challenge); challenge++);

		challenge = camel_sasl_challenge_base64_sync (
			sasl, challenge, cancellable, error);
		if (challenge == NULL)
			goto break_and_lose;

		g_free (respbuf);
		respbuf = NULL;

		 
		cmdbuf = g_strdup_printf ("%s\r\n", challenge);
		g_free (challenge);
		d (fprintf (stderr, "[SMTP] sending: %s", cmdbuf));
		if (camel_stream_write_string (ostream, cmdbuf, cancellable, error) == -1) {
			g_free (cmdbuf);
			goto lose;
		}
		g_free (cmdbuf);

		 
		respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
		d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
	}

	if (respbuf == NULL)
		goto lose;

	 
	if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
		goto broken_smtp_server;

	 
	if (strncmp (respbuf, "535", 3) == 0) {
 rejected:
		result = CAMEL_AUTHENTICATION_REJECTED;

		 
		while (respbuf && respbuf[3] == '-') {
			g_free (respbuf);
			respbuf = camel_stream_buffer_read_line (istream, cancellable, error);
			d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));
		}

		if (can_try_again) {
			can_try_again = FALSE;

			if (smtp_password_contains_nonascii (service)) {
				const gchar *password;
				gchar *password_latin1;

				password = camel_service_get_password (service);
				password_latin1 = g_convert_with_fallback (password, -1, "ISO-8859-1", "UTF-8", "", NULL, NULL, NULL);

				if (password_latin1 && g_strcmp0 (password, password_latin1) != 0) {
					restore_password = g_strdup (password);

					camel_service_set_password (service, password_latin1);

					g_clear_object (&istream);
					g_clear_object (&ostream);
					g_object_unref (sasl);
					g_free (password_latin1);
					g_free (respbuf);

					goto try_again;
				} else {
					g_free (password_latin1);
				}
			}
		}
	} else if (strncmp (respbuf, "235", 3) == 0)
		result = CAMEL_AUTHENTICATION_ACCEPTED;
	 
	else {
		g_set_error (
			error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			_("Bad authentication response from server."));
		goto lose;
	}

	goto exit;

break_and_lose:
	 
	d (fprintf (stderr, "[SMTP] sending: *\n"));
	camel_stream_write (ostream, "*\r\n", 3, cancellable, NULL);
	respbuf = camel_stream_buffer_read_line (istream, cancellable, NULL);
	d (fprintf (stderr, "[SMTP] received: %s\n", respbuf ? respbuf : "(null)"));

lose:
	result = CAMEL_AUTHENTICATION_ERROR;

exit:
	if (restore_password) {
		camel_service_set_password (service, restore_password);
		g_free (restore_password);
	}

	g_clear_object (&istream);
	g_clear_object (&ostream);
	g_object_unref (sasl);
	g_free (respbuf);

	return result;
}