Jump to content
View in the app

A better way to browse. Learn more.

IPBMafia.ru - поддержка Invision Community, релизы, темы, плагины и приложения

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Неожиданное поведение SMTP с настройкой AUTH PLAIN и SMTPS в принципе

Featured Replies

Posted

Перенастраивал почтовый сервер, дошла очередь перевести на него свой форум. Оказалось, что выключение устаревшего механизма LOGIN ломает работу SMTP-рассылки средствами форума. Механизм PLAIN ему непонятен. Нашёл тему на официальном форуме по этой проблеме. https://invisioncommunity.com/forums/topic/475170-535-578-error-authentication-failed-invalid-authentication-mechanism/

«Пропатчил» класс IPS\Email\Outgoing\Smtp, чтобы добавить поддержку PLAIN. На всякий случай, продублирую код сюда:

 if ( $this->smtpUser )
 {
-    $responseCode = $this->_sendCommand( 'AUTH LOGIN', 334 );
+    $responseCode = $this->_sendCommand( 'AUTH PLAIN', 334 );
-    $responseCode = $this->_sendCommand( base64_encode( $this->smtpUser ), 334 );
-    $responseCode = $this->_sendCommand( base64_encode( $this->smtpPass ), 235 );
+    $responseCode = $this->_sendCommand( base64_encode("\0" . $this->smtpUser . "\0" . $this->smtpPass), 235);
 }

Отправка через порт 587 (SMTP) заработала, и, казалось бы, можно было на этом закончить. Но вот мне этого недостаточно. Я хочу переключить это на порт 465 (SMTPS), где у меня соединение начинается сразу в TLS-режиме без необходимости кидать STARTTLS и отключить ненужный мне порт SMTP. Когда я пытаюсь протестировать соединение через SMTPS отправкой тестового письма, Invision Community 4.7.22 зависает и по таймауту nginx отбивает код 504.

Есть мысли, кто-нибудь «учил» IPS4 неявному TLS?

Ваша задача — заставить Invision Community (IPS4) корректно работать с SMTPS (TLS на порту 465, implicit TLS), а не только с SMTP+STARTTLS (обычно порт 587). Из того, что вы описали:

- Вы успешно «пропатчили» механизм авторизации SMTP, заменив LOGIN на PLAIN — это решило вопрос аутентификации.

- Теперь вы хотите перейти на SMTPS (implicit TLS) на порту 465.

- При попытке отправки через SMTPS происходит зависание и таймаут.

---

### Почему возникает проблема?

Большинство современных PHP-библиотек и реализаций SMTP-клиентов (включая стандартный stream_socket_client и частично PHPMailer, которые может использовать IPS4) по умолчанию поддерживают STARTTLS, но implicit TLS (где TLS начинается сразу при подключении) требует особой настройки соединения.

Если пытаться подключаться к SMTPS на 465 порт без правильного указания SSL-контекста в stream_socket_client, то соединение либо не устанавливается, либо "зависает".

---

### Что проверить и как решить

1. Проверка способа соединения

Убедитесь, что в IPS4 при использовании порта 465 действительно используется SSL/TLS-соединение с самого начала, а не plain TCP, и не пытается запускать STARTTLS.

2. Настройка параметров соединения

В IPS4 (его классе SMTP отправки), скорее всего, используется stream_socket_client(). Для implicit SSL вам нужно использовать транспорт ssl:// или tls:// в URL подключения.

Например:

$transport = ($this->smtpPort == 465) ? 'ssl://' : 'tcp://';
$connection = stream_socket_client($transport . $this->smtpHost . ':' . $this->smtpPort, $errno, $errstr, $timeout);

Часто ssl:// используется для implicit TLS (порт 465), а tcp:// — для обычного TCP (порт 587 с STARTTLS).

3. Патчинг IPS4

Возможно, в исходном коде IPS4 в классе SMTP нет поддержки ssl:// транспорта, или она автоматически не выбирается по порту.

Попробуйте поискать и изменить логику открытия соединения так, чтобы для порта 465 использовать ssl://.

Обычно в IPS4 путь к классу — IPS\Email\Outgoing\Smtp или похожий.

4. Пример патча для подключения:

Найдите метод, который открывает сокет, может называться _openSocket() или что-то подобное. Добавьте условие:

$transport = 'tcp://';
if ( $this->smtpPort == 465 )
{
$transport = 'ssl://';
}
$this->_socket = stream_socket_client(
$transport . $this->smtpHost . ':' . $this->smtpPort,
$errno,
$errstr,
$timeout
);

5. Выключите STARTTLS

При использовании SMTPS на 465 не нужно запускать команду STARTTLS — TLS уже активен. Проверьте, что IPS4 не пытается выполнить STARTTLS после подключения, иначе возникнет конфликт.

6. Проверка настроек почтового сервера

Убедитесь, что ваш SMTP сервер корректно слушает 465 порт с implicit TLS.

7. Логирование и отладка

Включите расширенное логирование SMTP соединения в IPS4, чтобы увидеть, на каком этапе происходит зависание.

8. Альтернативы

Если IPS4 не поддерживает implicit TLS из коробки, можно:

- Использовать порт 587 с STARTTLS (если это возможно).

- Проксировать SMTP через stunnel на сервере, переводя plain 587 в 465 SSL.

- Использовать внешний SMTP-клиент/шлюз.

---

### Краткое резюме

- SMTPS (порт 465) требует подключения с ssl:// transport в PHP.

- Нужно исправить код создания сокета, чтобы для порта 465 использовать ssl://.

- Убедиться, что не запускается STARTTLS, когда используешь implicit TLS.

- Проверьте, что аутентификацию и отправку сообщений IPS4 выполняет после установления TLS.

- Логируйте SMTP-сессию для диагностики.

---

Если хотите, могу помочь более конкретно с кодом, если вы предоставите исходный код класса, где открывается SMTP-соединение у вас в IPS4.

Ваш GPT-5

Спасибо за предоставленный код класса SMTP. Теперь гораздо яснее, как идёт подключение и какая логика используется.

---

### Ваша задача — заставить Invision Community корректно работать с implicit TLS (SMTPS на порту 465).

---

## Анализ текущей реализации подключения

В методе connect() есть эта строка:

$connection = @fsockopen( ( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . $this->smtpHost, $this->smtpPort, $errno, $errstr );

То есть протокол ssl:// добавляется к хосту только, если $this->smtpProtocol == 'ssl'. Это позволяет открывать implicit TLS соединение, которое как раз используется на порту 465.

Если $this->smtpProtocol == 'tls', то соединение открывается как plain TCP, и затем вызывается команда STARTTLS.

Всё выглядит правильно, но:

- Вы должны убедиться, что в конфигурации IPS4 при использовании порта 465 указано $smtpProtocol = 'ssl'.

- Если у вас $smtpProtocol == 'tls' на 465 порту — соединение зависнет, так как сервер ожидает TLS с самого начала, а клиент пытается сделать plain connection.

---

## Рекомендации и исправления

1. **Проверьте значение $smtpProtocol в вашей конфигурации.**

Для implicit TLS (порт 465) оно должно быть точно 'ssl', а не 'tls'!

2. **Убедитесь, что вы не запускаете STARTTLS при $smtpProtocol == 'ssl'.**

В вашем методе connect() есть такой код:

   if( $this->smtpProtocol == 'tls' )
   {
       // starttls и активация шифрования
   }

Следовательно при 'ssl' этот блок пропускается, что правильно.

3. **Ваш патч с AUTH PLAIN**

Ваш код авторизации должен выглядеть примерно так, чтобы включать поддержку PLAIN, например заменить этот участок:

     /* Authenticate */
   if ( $this->smtpUser )
   {
       // вместо AUTH LOGIN, сделать:
       $responseCode = $this->_sendCommand( 'AUTH PLAIN', 334 );
       $responseCode = $this->_sendCommand( base64_encode("\0" . $this->smtpUser . "\0" . $this->smtpPass), 235 );
   }

4. **Добавьте логирование**

Метод getLog() есть, так что вызовите его после неудачных попыток, чтобы получить подробный лог SMTP сессии. Это поможет отловить, на каком этапе подвисает.

5. **Пример конфигурации для port 465**

В конфиге форума/почты должно быть так:

- SMTP host: ваш сервер

- SMTP port: 465

- SMTP protocol: ssl

- SMTP user/pass: ваши данные

---

## Можно протестировать -- пример быстрого скрипта для проверки соединения (outside IPS4)

<?php
$host = 'ssl://smtp.yourserver.com';
$port = 465;
$user = 'username';
$pass = 'password';
$fp = fsockopen($host, $port, $errno, $errstr, 10);
if (!$fp) {
    die("Ошибка подключения: $errstr ($errno)\n");
}
echo fgets($fp, 515); // ждем готовности сервера
fputs($fp, "EHLO yourdomain.com\r\n");
echo fgets($fp, 515);
fputs($fp, "AUTH PLAIN " . base64_encode("\0$user\0$pass") . "\r\n");
echo fgets($fp, 515);
// Далее команда MAIL, RCPT и DATA ...

Если тестовый скрипт работает, а IPS4 нет — проблема в настройках (smtpProtocol) или нюансах работы класса.

---

## Резюме

- В IPS4 для implicit TLS на 465 порт должно быть $smtpProtocol = 'ssl'

- В этом случае fsockopen() использует ssl:// префикс, TLS устанавливается сразу

- STARTTLS в этом случае не вызывается

- Используйте AUTH PLAIN вместо AUTH LOGIN для аутентификации

- Проверьте лог сессии для диагностики проблем

- Убедитесь, что порт 465 на сервере корректно работает с implicit TLS

---

Если будете испытывать проблемы — пришлите лог из $smtp->getLog() после неудачной попытки. Помогу проанализировать дальше.

Edited by Zero108

Вот полный исправленный класс _SMTP, в котором:

При $smtpProtocol == 'ssl' используется явное подключение через ssl:// (implicit TLS, порт 465).

При $smtpProtocol == 'tls' — соединение plain с последующим STARTTLS.

В блоке аутентификации заменён метод на AUTH PLAIN с корректной отправкой base64.

Логирование осталось без изменений.

Важно:

При использовании порта 465 и implicit SSL/TLS нужно указывать $smtpProtocol = 'ssl'.

Для порта 587 и startTLS — $smtpProtocol = 'tls'.

Аутентификация реализована через AUTH PLAIN с корректным форматом.

Если хотите, помогу и с примером настройки или тестовым скриптом — дайте знать!

<?php
/**
 * @brief		SMTP Email Class
 * @author		<a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright	(c) Invision Power Services, Inc.
 * @license		https://www.invisioncommunity.com/legal/standards/
 * @package		Invision Community
 * @since		17 Apr 2013
 */
namespace IPS\Email\Outgoing;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}
/**
 * @brief	SMTP Email Class
 */
class _SMTP extends \IPS\Email
{
	/**
	 * @brief	SMTP Protocol ("tls", "ssl" or "plain")
	 */
	protected $smtpProtocol;
	
	/**
	 * @brief	SMTP Host
	 */
	protected $smtpHost;
	
	/**
	 * @brief	SMTP Port
	 */
	protected $smtpPort;
	
	/**
	 * @brief	SMTP Username
	 */
	protected $smtpUser;
	
	/**
	 * @brief	SMTP Password
	 */
	protected $smtpPass;
	
	/**
	 * @brief	SMTP Connections
	 */
	protected static $smtp = array();
	
	/**
	 * @brief	Connection Key
	 */
	protected $connectionKey;
	
	/**
	 * @brief	Log
	 */
	protected $log = '';
	
	/**
	 * Constructor
	 *
	 * @param	string	$smtpProtocol	Protocol to use
	 * @param	string	$smtpHost		Hostname to connect to
	 * @param	int		$smtpPort		Port to connect to
	 * @param	string	$smtpUser		Username
	 * @param	string	$smtpPass		Password
	 * @return	void
	 */
	public function __construct( $smtpProtocol, $smtpHost, $smtpPort, $smtpUser, $smtpPass )
	{
		$this->smtpProtocol = $smtpProtocol;
		$this->smtpHost = $smtpHost;
		$this->smtpPort = $smtpPort;
		$this->smtpUser = $smtpUser;
		$this->smtpPass = $smtpPass;
		$this->connectionKey = md5( $smtpProtocol . $smtpHost . $smtpPort . $smtpUser . $smtpPass );
	}
	
	/**
	 * Connect to server
	 *
	 * @param	bool	$checkSsl	If set to FALSE, will skip peer certificate verification for TLS connections
	 * @return void
	 */
	public function connect( $checkSsl=TRUE )
	{
		/* Do we already have a connection? */
		if( array_key_exists( $this->connectionKey, static::$smtp ) )
		{
			return;
		}
		/* Connect */
		$connection = @fsockopen( 
			( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . 
			$this->smtpHost, $this->smtpPort, $errno, $errstr );
		if ( !$connection )
		{
			throw new \IPS\Email\Outgoing\Exception( $errstr, $errno );
		}
		static::$smtp[ $this->connectionKey ] = $connection;
		register_shutdown_function(function( $object ){
			$object->_sendCommand( 'quit' );
			@fclose( static::$smtp[ $object->connectionKey ] );
			unset( static::$smtp[ $object->connectionKey ] );
		}, $this );
		/* Check the initial response is okay */
		$announce		= $this->_getResponse();
		$responseCode	= mb_substr( $announce, 0, 3 );
		if ( $responseCode != 220 )
		{
			throw new \IPS\Email\Outgoing\Exception( 'smtpmail_fsock_error_initial', 0, NULL, array( $responseCode ) );
		}
		/* HELO/EHLO */
		try
		{
			$helo = 'EHLO';
			$responseCode = $this->_sendCommand( 'EHLO ' . $this->smtpHost, 250 );
		}
		catch ( \IPS\Email\Outgoing\Exception $e )
		{
			$helo = 'HELO';
			$responseCode = $this->_sendCommand( 'HELO ' . $this->smtpHost, 250 );
		}
		/* Is TLS being used? */
		if( $this->smtpProtocol == 'tls' )
		{
			if ( $checkSsl )
			{
				@stream_context_set_option( static::$smtp[ $this->connectionKey ], 'ssl', 'verify_peer', false );
			}
			
			$this->_sendCommand( 'STARTTLS', 220 );
			if ( !@\stream_socket_enable_crypto( static::$smtp[ $this->connectionKey ], TRUE, STREAM_CRYPTO_METHOD_SSLv23_CLIENT ) )
			{
				if ( $checkSsl )
				{
					/* Try again, but ignore SSL checks in case the certificate was self-signed, which will fail when initializing TLS. This will be slightly slower to connect, but will avoid an error in most instances. */
					$this->connect( FALSE );
				}
				else
				{
					/* If it still failed on the second connection attempt, throw the exception */
					throw new \IPS\Email\Outgoing\Exception( 'smtpmail_tls_failed' );
				}
			}
			/* Exchange server (at least) wants EHLO resending for STARTTLS */
			$this->_sendCommand( $helo . ' ' . $this->smtpHost, 250 );
		}
		/* Authenticate */
		if ( $this->smtpUser )
		{
			// Используем AUTH PLAIN вместо AUTH LOGIN для лучшей совместимости
			$authString = base64_encode("\0" . $this->smtpUser . "\0" . $this->smtpPass);
			$this->_sendCommand('AUTH PLAIN', 334);
			$this->_sendCommand($authString, 235);
		}
	}
		
	/**
	 * Send the email
	 * 
	 * @param	mixed	$to					The member or email address, or array of members or email addresses, to send to
	 * @param	mixed	$cc					Addresses to CC (can also be email, member or array of either)
	 * @param	mixed	$bcc				Addresses to BCC (can also be email, member or array of either)
	 * @param	mixed	$fromEmail			The email address to send from. If NULL, default setting is used
	 * @param	mixed	$fromName			The name the email should appear from. If NULL, default setting is used
	 * @param	array	$additionalHeaders	Additional headers to send
	 * @return	void
	 * @throws	\IPS\Email\Outgoing\Exception
	 */
	public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
	{
		/* Get the from email */
		$fromEmail = $fromEmail ?: \IPS\Settings::i()->email_out;
		
		/* SMTP requires you to do CC/BCC by sending a RCPT TO command for each recipient. We'll hide BCC by not actually setting that header */ 
		$recipieintsForSmtp = explode( ',', static::_parseRecipients( $to, TRUE ) );
		if ( $cc )
		{
			$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $cc, TRUE ) ) );
		}
		if ( $bcc )
		{
			$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $bcc, TRUE ) ) );
		}
		$recipieintsForSmtp = array_unique( array_map( 'trim', $recipieintsForSmtp ) );
		
		/* Send */
		$this->_sendCompiled( $fromEmail, $recipieintsForSmtp, $this->compileFullEmail( $to, $cc, array(), $fromEmail, $fromName, $additionalHeaders ) );
	}
	
	/**
	 * Send an email
	 * 
	 * @param	string	$fromEmail			The email address to send from
	 * @param	array	$toEmails			Array of email addresses to send to
	 * @param	string	$email				The full email (with headers, etc.) except the Bcc header
	 * @return	void
	 * @throws	\IPS\Email\Outgoing\Exception
	 */
	public function _sendCompiled( $fromEmail, $toEmails, $email )
	{
		$this->connect();
		
		$this->_sendCommand( "MAIL FROM:<{$fromEmail}>", 250 );
		
		foreach ( $toEmails as $toEmail )
		{
			$this->_sendCommand( "RCPT TO:<{$toEmail}>", 250, TRUE );
		}
				
		$this->_sendCommand( 'DATA', 354 );
		$this->_sendCommand( $email . "\r\n.", 250 );
	}
	
	/**
	 * Send SMTP Command
	 *
	 * @param	string		$command			The command
	 * @param	int|NULL	$expectedResponse	The expected response code. Will throw an exception if different.
	 * @param	bool		$resetOnFailure		If the command fails, issue a RSET (reset) command
	 * @return	string	Response
	 * @throws	\IPS\Email\Outgoing\Exception
	 */
	protected function _sendCommand( $command, $expectedResponse=NULL, $resetOnFailure=FALSE )
	{
		/* Log */
		$this->log .= "> {$command}\r\n";
		
		/* Send */
		fputs( static::$smtp[ $this->connectionKey ], $command . "\r\n" );
		
		/* Read */
		$response = $this->_getResponse();
		
		/* Get response code */
		$code = \intval( mb_substr( $response, 0, 3 ) );
		if ( $expectedResponse !== NULL and $code !== $expectedResponse )
		{
			if( $resetOnFailure === TRUE )
			{
				$this->_sendCommand( 'RSET' );
			}
			throw new \IPS\Email\Outgoing\Exception( $response, $code );
		}
		
		/* Return */
		return $response;
	}
	/**
	 * Get response
	 *
	 * @return	string	Response
	 */
	protected function _getResponse()
	{
		/* Read */
		$response = '';
		while ( $line = @fgets( static::$smtp[ $this->connectionKey ], 515 ) )
		{			
			$response .= $line;
			if ( mb_substr($line, 3, 1) == " " )
			{
				break;
			}
		}
		
		/* Log */
		$this->log .= mb_convert_encoding( $response, 'UTF-8', 'ASCII' );
		
		/* Return */
		return $response;
	}
	
	/**
	 * Return the SMTP log
	 *
	 * @return string
	 */
	public function getLog()
	{
		return $this->log;
	}
}

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Ответить в этой теме...

Последние посетители 1

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.