Я использую компоненты Indy в C++Builder 11.3, чтобы попытаться отправить электронное письмо из нашего проекта RAD Server. В целях тестирования я написал приведенный ниже 64-битный код и получаю ошибку, которая уже рассматривалась Реми в его ответе на «Ошибка согласования SSL» в Delphi 11.
Я перенес код точно из поста Реми, за исключением предостережений по C++, и получаю следующую ошибку:
Имя пользователя и пароль не принимаются
и URL-адрес, указывающий на «support.google.com.../mail/?p=Bad Credentials».
Мне интересно, связана ли проблема с версиями SSL/TLS?
Если кто-то заметит в коде что-то не так, пожалуйста, дайте мне знать. Если кто-то считает, что это проблема SSL/TLS, и знает, где я могу найти правильные версии для 64-битной Windows/Windows Server 2019, я был бы очень признателен, если бы указал мне правильное направление.
void __fastcall TMainForm::SendBtnClick(TObject *Sender)
{
try
{
// Point to SSL
IdOpenSSLSetLibPath(L"C:\\openssl");
// Set up SMTP
TIdSMTP *IdSMTP = new TIdSMTP(NULL);
IdSMTP->Host = "smtp.gmail.com";
IdSMTP->Port = 587;
IdSMTP->Username = "[email protected]";
IdSMTP->Password = "my_password_phrase";
IdSMTP->AuthType = satDefault;
// Set up SSL/TLS
TIdSSLIOHandlerSocketOpenSSL *IdSSL = new TIdSSLIOHandlerSocketOpenSSL(IdSMTP);
IdSSL->SSLOptions->SSLVersions << sslvTLSv1 << sslvTLSv1_1 << sslvTLSv1_2;
IdSSL->SSLOptions->Mode = sslmUnassigned;
IdSSL->SSLOptions->VerifyMode = TIdSSLVerifyModeSet();
IdSSL->SSLOptions->VerifyDepth = 0;
IdSMTP->IOHandler = IdSSL;
// Had to move the IdSMTP TLS property below the IOHandler as
// it appears that in C++ you should have your IOHandler set
// up before setting the IdSMPT property, or you will get,
// "SSL IOHandler is required for this setting" exception.
IdSMTP->UseTLS = utUseExplicitTLS;
// Set up Message
TIdMessage *IdMessage = new TIdMessage(NULL);
IdMessage->From->Address = "[email protected]";
IdMessage->Recipients->Add()->Address = "[email protected]";
IdMessage->Subject = "Test Email";
IdMessage->Body->Text = "Super Cool Test...";
try {
IdSMTP->Connect();
IdSMTP->Send(IdMessage);
IdSMTP->Disconnect();
} catch (const Exception &e) {
ErrorMemo->Lines->Add("[Connect Exception]: " + e.Message);
}
// Tidy up
delete IdSMTP;
delete IdSSL;
delete IdMessage;
ErrorMemo->Lines->Add("No leaks");
}
catch (const Exception &exception)
{
ErrorMemo->Lines->Add("[Method Exception]: " + exception.Message);
}
}
Я попробовал использовать свойства компонента времени разработки и очень мало кода, который вы видите выше. Кроме того, я установил несколько различных двоичных версий пакетов SSL из результатов поиска, но, похоже, не смог заставить их работать.
Спасибо, Кен, за то, что указал мне на ссылку на политику, я понял и удалил изображения.
Вам следовало заменить их текстовыми сообщениями об ошибках, как я упоминал в своем предыдущем комментарии. Без этих сообщений ваш вопрос не так ясен.
Я просто переписал сообщение и написал сообщение об ошибке. Я также отредактировал содержание, чтобы оно было более понятным. Другое изображение не должно было быть прикреплено, как это описано в коде. Еще раз спасибо за ваши комментарии.
SSL/TLS и аутентификация — это две разные вещи. Рассматриваемая ошибка не имеет ничего общего с SSL/TLS.
Поскольку вы пытаетесь использовать имя пользователя + пароль, убедитесь, что ваша учетная запись Gmail настроена на это. Если у вас включена двухфакторная аутентификация, вам необходимо создать Пароль приложения вместо использования настоящего пароля. В противном случае вам придется включить «менее безопасные приложения», чтобы использовать ваш настоящий пароль.
Альтернативой является использование аутентификации OAuth, которую в настоящее время предпочитает Gmail.
В настоящее время Indy официально не поддерживает OAuth, однако в репозитории Indy GitHub доступна ветка sasl-oauth (она еще не объединена с основным кодом), которая добавляет новые TIdSASL...
компоненты для OAuth. Например, TIdSASLXOAuth2
можно использовать с Gmail. Вы можете установить для свойства TIdSMTP::AuthType
значение satSASL
, а затем включить объект TIdSASLXOAuth2
в коллекцию TIdSMTP::SASLMechanisms
, например:
__fastcall TMainForm::TMainForm(TComponent *Owner)
: TForm(Owner)
{
// Point to SSL
IdOpenSSLSetLibPath(_D("C:\\openssl"));
}
void __fastcall TMainForm::SendBtnClick(TObject *Sender)
{
try
{
String OAuthToken = ...; // see further below...
// Set up SMTP
TIdSMTP *IdSMTP = new TIdSMTP(NULL);
IdSMTP->Host = _D("smtp.gmail.com");
IdSMTP->Port = 587;
IdSMTP->AuthType = satSASL;
// Set up SSL/TLS
TIdSSLIOHandlerSocketOpenSSL *IdSSL = new TIdSSLIOHandlerSocketOpenSSL(IdSMTP);
IdSSL->SSLOptions->SSLVersions = TIdSSLVersions() << sslvTLSv1 << sslvTLSv1_1 << sslvTLSv1_2;
IdSSL->SSLOptions->Mode = sslmUnassigned;
IdSSL->SSLOptions->VerifyMode = TIdSSLVerifyModeSet();
IdSSL->SSLOptions->VerifyDepth = 0;
IdSMTP->IOHandler = IdSSL;
// Had to move the IdSMTP TLS property below the IOHandler as
// it appears that you should have your IOHandler set up
// before setting the UseTLS property, or you will get a
// "SSL IOHandler is required for this setting" exception.
IdSMTP->UseTLS = utUseExplicitTLS;
// Set up SASL
TIdSASLXOAuth2 *IdOAuth = new TIdSASLXOAuth2(IdSMTP);
IdOAuth->Username = _D("[email protected]");
IdOAuth->Password = OAuthToken;
IdSMTP->SASLMechanisms->Add()->SASL = IdOAuth;
// Set up Message
TIdMessage *IdMessage = new TIdMessage(IdSMTP);
IdMessage->From->Address = _D("[email protected]");
IdMessage->Recipients->Add()->Address = _D("[email protected]");
IdMessage->Subject = _D("Test Email");
IdMessage->Body->Text = _D("Super Cool Test...");
try {
IdSMTP->Connect();
try {
IdSMTP->Send(IdMessage);
} __finally {
IdSMTP->Disconnect();
}
} catch (const Exception &e) {
ErrorMemo->Lines->Add("[SMTP Exception]: " + e.Message);
}
// Tidy up
delete IdSMTP;
ErrorMemo->Lines->Add("No leaks");
}
catch (const Exception &exception)
{
ErrorMemo->Lines->Add("[Method Exception]: " + exception.Message);
}
}
Вам просто придется вручную получить токен доступа OAuth из Gmail, например, через HTTP (более подробную информацию см. в разделе Использование OAuth 2.0 для доступа к API Google), а затем вы сможете назначить этот токен свойству TIdSASLXOAuth2::Password
, как показано выше. Или вместо этого вы можете вернуть его из события TIdSASLXOAuth2::OnGetAccessToken
, например:
void __fastcall TMainForm::GetAccessToken(TObject* Sender, String &AccessToken)
{
AccessToken = ...;
}
...
// Set up SASL
TIdSASLXOAuth2 *IdOAuth = new TIdSASLXOAuth2(IdSMTP);
IdOAuth->Username = _D("[email protected]");
IdOAuth->OnGetAccessToken = &GetAccessToken;
При этом, если вы не хотите устанавливать код филиала, вы можете просто использовать метод TIdSMTP::SendCmd()
, чтобы вручную отправить необходимую AUTH XOAUTH2
SMTP-команду (подробности см. в Механизм OAuth 2.0) перед вызовом TIdSMTP::Send()
(только обязательно заранее установите TIdSMTP::AuthType
на satNone
), например:
IdSMTP->Connect();
try {
String user = _D("[email protected]");
IdSMTP->SendCmd(_D("AUTH XOAUTH2 ") + TIdEncoderMIME::EncodeString(_D("user = ") + user + _D("\x01auth=Bearer ") + OAuthToken + _D("\x01\x01")), 235);
IdSMTP->Send(IdMessage);
} __finally {
IdSMTP->Disconnect();
}
Прочитав новые требования Google к использованию сторонних приложений с GSuite, они заявили, что к осени 2024 года OAuth будет требоваться для всего стороннего доступа, поэтому имя пользователя + пароль, а также менее безопасные приложения больше не будут доступны. Ссылку оставлю во втором комментарии, она здесь слишком длинная. Прочитав документацию об обновлении Indy до более новой ветки, они упоминают о возможности взлома EMSEdge Обновление Indy. Поэтому я попробую маршрут TIdSMTP::SendCmd(). Спасибо.
Ссылка Google про OAuth Переход с менее безопасных приложений на OAuth
Если вы прочитаете объявление Google еще раз более внимательно, то увидите, что исчезнут только «менее безопасные приложения». Пароли приложений по-прежнему будут работать, а это означает, что имя пользователя + пароль по-прежнему будут работать. Вы просто не можете использовать свой настоящий пароль, вам придется генерировать пароль в настройках Gmail для каждого экземпляра приложения. Я уже давно использую пароли приложений с Indy и Gmail, и все работает нормально. Но да, в конечном итоге код OAuth SASL также будет объединен с основным кодом Indy.
Я заметил опечатку в примере с SendCmd, исправил ее.
Спасибо. И то, что вы сказали, очень приятно знать. Да, я прочитаю документацию еще раз. Меня это не все связало.
наконец-то получилось с вашими комментариями.