Discourse SMTP envía "EHLO localhost" en lugar del dominio, rompiendo Google smtp-relay

Algunos datos de contexto: Emails have stopped sending - end of file reached

Aproximadamente hace una semana (13 de enero de 2021), el envío de correos electrónicos comenzó a fallar a través del servidor smtp-relay.gmail.com de Google (que es un uso permitido e intencionado para los usuarios de Google Apps).

Sidekiq reportó los fallos con errores EOFErrors:

Jobs::HandledExceptionWrapper: Wrapped EOFError: end of file reached

Y /logs también reportó las tareas fallidas:

Job exception: end of file reached

El backtrace está disponible en el otro post.

===================

La investigación reveló que las instalaciones actualizadas de Discourse se conectan a los retransmisores SMTP con ‘EHLO localhost’, y Google comenzó a rechazar estas conexiones hace aproximadamente una semana.

Desde tcpdump en una instancia de producción:

0x0030:  d10f f8e4 4548 4c4f 206c 6f63 616c 686f  ....EHLO.localho
	0x0040:  7374 0d0a                                st..
...
	0x0030:  de62 f0c3 3432 3120 342e 372e 3020 5472  .b..421.4.7.0.Tr
	0x0040:  7920 6167 6169 6e20 6c61 7465 722c 2063  y.again.later,.c
	0x0050:  6c6f 7369 6e67 2063 6f6e 6e65 6374 696f  losing.connectio
	0x0060:  6e2e 2028 4548 4c4f 2920 6a31 3673 6d34  n..(EHLO).j16sm4
	0x0070:  3831 3932 3976 736d 2e31 202d 2067 736d  81929vsm.1.-.gsm
	0x0080:  7470 0d0a                                tp..

Y replicando con telnet se obtiene el mismo resultado:

root@conversation:~# telnet smtp-relay.gmail.com 587
Trying 74.125.137.28...
Connected to smtp-relay.gmail.com.
Escape character is '^]'.
220 smtp-relay.gmail.com ESMTP ls8sm507258pjb.6 - gsmtp
ehlo localhost.localdomain
421 4.7.0 Try again later, closing connection. (EHLO) ls8sm507258pjb.6 - gsmtp
Connection closed by foreign host.

Sin embargo, un ehlo específico del dominio funciona correctamente:

root@conversation:~# telnet smtp-relay.gmail.com 587
Trying 74.125.137.28...
Connected to smtp-relay.gmail.com.
Escape character is '^]'.
220 smtp-relay.gmail.com ESMTP p10sm668563uaw.3 - gsmtp
ehlo conversation.sevarg.net
250-smtp-relay.gmail.com at your service, [64.227.96.27]
250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8

======

Basándome en los registros, identifiqué el archivo que debía modificar para probar la solución (en la imagen de Docker):

/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb

Cambiar

DEFAULTS = {
      :address              => 'localhost',
      :port                 => 25,
      :domain               => 'localhost.localdomain',

por

    DEFAULTS = {
      :address              => 'conversation.sevarg.net',
      :port                 => 25,
      :domain               => 'conversation.sevarg.net',

resolvió el problema (tras reiniciar la instancia). El EHLO ahora se envía con la cadena del dominio, y los correos electrónicos se envían correctamente desde mi instancia.

================

Comportamiento deseado: Al enviar correos electrónicos, la instalación predeterminada de Discourse debe usar el nombre de dominio configurado para la conexión inicial al servidor SMTP. Alternativamente, debería existir una opción de configuración para anular el dominio enviado. Si esta opción existe, no pude encontrarla mediante búsquedas.

5 Me gusta

Creo que he visto este mismo error en otras personas (que quizás tampoco estaban usando Google Domains).

Una solución a largo plazo es agregar algo de magia a tu app.yml que realice esa reescritura por ti. Pero espero que llegue una solución real en su lugar.

Si hay una forma de solucionarlo mediante app.yml, sin duda estoy interesado: codificar mi dominio directamente en el código para que el correo funcione claramente no es una solución “adecuada”, pero sí demuestra dónde se debe resolver el problema de forma más permanente.

¿Hay alguna razón por la que no utilice simplemente el dominio configurado para el EHLO? Eso sería “más correcto” que localhost.

¡Gran trabajo de investigación @Syonyk!

¿Podrías compartirnos la configuración SMTP de tu archivo app.yml?

2 Me gusta

No hay nada más allá de la configuración estándar requerida.

  DISCOURSE_SMTP_ADDRESS: smtp-relay.gmail.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: [nombre de usuario del correo]
  DISCOURSE_SMTP_PASSWORD: [contraseña]

¿Podrías intentar agregar una nueva línea con

DISCOURSE_SMTP_DOMAIN: conversation.sevarg.net

y volver a intentarlo?

3 Me gusta

Añadí esa línea y volví a compilar la aplicación (¿hay alguna forma de evitar ese paso?).

Ahora veo “domain” en la configuración de correo electrónico, el archivo smtp.rb se ha revertido a tener localhost como valor predeterminado y los correos parecen enviarse correctamente: puedo enviar mensajes de prueba y se transmiten sin problemas.

Por lo tanto, esto resuelve el problema, al menos hasta donde puedo ver. ¿Sería posible añadir esto a la documentación o al flujo de configuración en algún lugar? Busqué durante un buen rato esa configuración y no pude encontrar la opción; incluso sabiendo que existe esa opción de configuración, hay muy poca mención al respecto.

2 Me gusta

Puede agregarse a este bloque en el archivo de muestra app.yml predeterminado:

¿Consideras que esto es útil?

1 me gusta

Mientras esté documentado en algún lugar, me parece bien.

Sin embargo, si no está establecido, ¿podría el código utilizar el valor de DISCOURSE_HOSTNAME (lo cual sería correcto en casi todos los casos simples)? Enviar ‘localhost’ en EHLO generalmente es incorrecto (al menos al comunicarse con un servidor que no está en localhost).

Creo que añadirlo a standalone.yml y web_only.yml es una buena idea.

Estoy de acuerdo en que probablemente debería tener como valor predeterminado DISCOURSE_HOSTNAME. Eso sin duda parece mejor que localhost. ¿Ha cambiado esto recientemente?

El problema aquí es que funciona para todas las decenas de miles de instancias de Discourse que actualmente no dependen de Google para el correo electrónico. Cambiar un valor predeterminado así puede afectar a todos mientras se soluciona el problema para los usuarios de Google Apps, que son comparativamente pocos.

Lo agregaré al ejemplo, y también deberíamos tener un tema howto titulado “Uso de Google Apps para correo saliente” que documente esto. ¿Alguien puede encargarse de esto?

4 Me gusta

Vale. Estoy de acuerdo en que probablemente tenga sentido ser cauteloso, pero mi suposición es que cualquier servidor de correo que acepte localhost también aceptaría DISCOURSE_HOSTNAME, aunque no tengo datos al respecto. :wink: Tenerlo en las plantillas estándar probablemente sea suficiente.

1 me gusta

Sí, pero enviar ‘localhost’ (a un host remoto) también es incorrecto según el RFC.

Énfasis mío.

Los RFC anteriores indican que el servidor no debe rechazar clientes basándose en la cadena EHLO, lo cual parece estar haciendo Google, pero no veo esa redacción en el 5321.

Esperaría que cualquier servidor de correo remoto que tolere localhost también tolere (y prefiera) un FQDN, como lo exige el RFC. Entiendo el deseo de no romper nada, pero según mi lectura de los RFC relevantes, Discourse simplemente es incorrecto por defecto, y que funcione es resultado de servidores SMTP remotos excesivamente permisivos.

1 me gusta

Estaré encantado de fusionar un PR en ./discourse-setup que lo establezca por defecto igual que DISCOURSE_HOSTNAME, siempre que se demuestre que es inofensivo con los servicios SMTP más comunes que recomendamos que la gente utilice.

2 Me gusta

No puedo probar la entrega completa de correo de extremo a extremo porque no tengo cuentas, pero:

Mailgun

% nc smtp.mailgun.com 587
220 Mailgun Influx listo
heo conversation.sevarg.net
250-smtp-out-n04.prod.us-west-2.postgun.com
250-AUTH PLAIN LOGIN
...

Sendgrid

% nc smtp.sendgrid.net 587
220 SG ESMTP service listo en ismtpd0021p1las1.sendgrid.net
heo conversation.sevarg.net
250-smtp.sendgrid.net
250-8BITMIME
...

Mailjet

 % nc smtp.mailjet.com 587
220 in.mailjet.com ESMTP Mailjet
heo conversation.sevarg.net
250-smtpin.mailjet.com
250-PIPELINING
...

ElasticMail

… realmente no responde a ningún tipo de helo o ehlo. o.O Ni localhost ni ningún dominio real.

Creo que configurarlo durante la instalación es la respuesta correcta, porque al menos estará disponible para que la gente lo conozca y lo modifique si es necesario.

6 Me gusta

Hay otro problema relacionado: discourse-doctor no parece establecer correctamente el dominio y seguirá fallando incluso cuando la instalación real pueda enviar correos.

Con la configuración funcional, discourse-doctor sigue reportando el error de fin de archivo.

======================================== ERROR ========================================
                                    ERROR INESPERADO

fin de archivo alcanzado

====================================== SOLUCIÓN =======================================
Este no es un error común. ¡No existe una solución recomendada!

Por favor, reporta el mensaje de error exacto anterior en https://meta.discourse.org/
(¡Y una solución, si la encuentras!)
=======================================================================================

No hay ninguna mención a SMTP_DOMAIN en el script de prueba.

root@conversation:/var/discourse# grep SMTP_DOMAIN discourse-doctor
root@conversation:/var/discourse#

Además, tcpdump indica que ejecutar discourse-doctor sigue enviando ‘localhost’ en el EHLO. Esto también necesita ser corregido.

	0x0030:  cccd b12c 4548 4c4f 206c 6f63 616c 686f  ...,EHLO.localho
	0x0040:  7374 0d0a                                st..
...
	0x0030:  e247 1aa5 3432 3120 342e 372e 3020 5472  .G..421.4.7.0.Tr
	0x0040:  7920 6167 6169 6e20 6c61 7465 722c 2063  y.again.later,.c
	0x0050:  6c6f 7369 6e67 2063 6f6e 6e65 6374 696f  losing.connectio
	0x0060:  6e2e 2028 4548 4c4f 2920 6e6d 3773 6d31  n..(EHLO).nm7sm1
	0x0070:  3032 3832 3139 706a 622e 3620 2d20 6773  028219pjb.6.-.gs
	0x0080:  6d74 700d 0a                             mtp..

Eso no es discourse-doctor, sino emails.rake:

Ah, y parece que usa localhost. Supongo que debería referirse a #{ENV["DISCOURSE_SMTP_PORT"]}… o, por ahora, a DISCOURSE_SMTP_DOMAIN.

@falco ¿estamos seguros de que esto siempre ha sido así y no es una regresión reciente?

Recuerdo problemas relacionados con el envío de

ehlo {dominio inválido}

desde hace muchísimo tiempo, así que me sorprendería mucho que hubiéramos estado enviando incorrectamente

ehlo localhost

durante mucho tiempo.

1 me gusta

Lo siento, solo estoy informando lo que veo. Estoy bastante seguro de que ‘localhost’ allí también es incorrecto. Mi desarrollo web es en gran parte antiguo; ha pasado más de una década desde que trabajé profesionalmente con estas cosas. Llegar tan lejos como lo hice tomó tiempo; Docker, Ruby y cosas así son bastante nuevos para mí. tcpdump, por otro lado… conozco esa herramienta. :wink:

Aun así, sigo pensando que enviar ‘localhost’ a un servidor remoto, en cualquier situación, es un comportamiento incorrecto.

1 me gusta

Por lo que vale, estamos experimentando exactamente el mismo problema desde el 13 de enero. El último correo electrónico enviado con éxito fue el 13 de enero y también estamos usando smtp-relay.gmail.com; aún no hemos logrado resolver esto (sin modificar el código fuente).