mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 20:12:26 -05:00 
			
		
		
		
	[bug] Send plaintext emails to fix "message refused: Message is not RFC 2822 compliant" (#366)
* trying to fix "message refused: Message is not RFC 2822 compliant" * fix "message refused: Message is not RFC 2822 compliant" 550 5.7.1 Delivery not authorized, message refused: Message is not RFC 2822 compliant * remove silly regex * lint * fix tests * we should use text/template instead of html/template now
This commit is contained in:
		
					parent
					
						
							
								959e38ac5c
							
						
					
				
			
			
				commit
				
					
						5be8a7a7ea
					
				
			
		
					 11 changed files with 78 additions and 28 deletions
				
			
		|  | @ -21,11 +21,15 @@ package email | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"net/smtp" | 	"net/smtp" | ||||||
|  | 
 | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	confirmTemplate = "email_confirm.tmpl" | 	confirmTemplate = "email_confirm_text.tmpl" | ||||||
| 	confirmSubject  = "Subject: GoToSocial Email Confirmation" | 	confirmSubject  = "GoToSocial Email Confirmation" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { | func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { | ||||||
|  | @ -35,7 +39,11 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { | ||||||
| 	} | 	} | ||||||
| 	confirmBody := buf.String() | 	confirmBody := buf.String() | ||||||
| 
 | 
 | ||||||
| 	msg := assembleMessage(confirmSubject, confirmBody, toAddress, s.from) | 	msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, s.from) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	logrus.WithField("func", "SendConfirmEmail").Trace(s.hostAddress + "\n" + viper.GetString(config.Keys.SMTPUsername) + ":password" + "\n" + s.from + "\n" + toAddress + "\n\n" + string(msg) + "\n") | ||||||
| 	return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) | 	return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ package email | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"html/template" | 	"text/template" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
|  | @ -57,7 +57,10 @@ func (s *noopSender) SendConfirmEmail(toAddress string, data ConfirmData) error | ||||||
| 	} | 	} | ||||||
| 	confirmBody := buf.String() | 	confirmBody := buf.String() | ||||||
| 
 | 
 | ||||||
| 	msg := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") | 	msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, "test@example.org") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	logrus.Tracef("NOT SENDING confirmation email to %s with contents: %s", toAddress, msg) | 	logrus.Tracef("NOT SENDING confirmation email to %s with contents: %s", toAddress, msg) | ||||||
| 
 | 
 | ||||||
|  | @ -74,7 +77,10 @@ func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error { | ||||||
| 	} | 	} | ||||||
| 	resetBody := buf.String() | 	resetBody := buf.String() | ||||||
| 
 | 
 | ||||||
| 	msg := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") | 	msg, err := assembleMessage(resetSubject, resetBody, toAddress, "test@example.org") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	logrus.Tracef("NOT SENDING reset email to %s with contents: %s", toAddress, msg) | 	logrus.Tracef("NOT SENDING reset email to %s with contents: %s", toAddress, msg) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,8 +24,8 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	resetTemplate = "email_reset.tmpl" | 	resetTemplate = "email_reset_text.tmpl" | ||||||
| 	resetSubject  = "Subject: GoToSocial Password Reset" | 	resetSubject  = "GoToSocial Password Reset" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (s *sender) SendResetEmail(toAddress string, data ResetData) error { | func (s *sender) SendResetEmail(toAddress string, data ResetData) error { | ||||||
|  | @ -35,7 +35,10 @@ func (s *sender) SendResetEmail(toAddress string, data ResetData) error { | ||||||
| 	} | 	} | ||||||
| 	resetBody := buf.String() | 	resetBody := buf.String() | ||||||
| 
 | 
 | ||||||
| 	msg := assembleMessage(resetSubject, resetBody, toAddress, s.from) | 	msg, err := assembleMessage(resetSubject, resetBody, toAddress, s.from) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) | 	return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,8 +20,8 @@ package email | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"html/template" |  | ||||||
| 	"net/smtp" | 	"net/smtp" | ||||||
|  | 	"text/template" | ||||||
| 
 | 
 | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
|  |  | ||||||
|  | @ -19,15 +19,12 @@ | ||||||
| package email | package email | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"html/template" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| ) | 	"strings" | ||||||
| 
 | 	"text/template" | ||||||
| const ( |  | ||||||
| 	mime = `MIME-version: 1.0; |  | ||||||
| Content-Type: text/html;` |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func loadTemplates(templateBaseDir string) (*template.Template, error) { | func loadTemplates(templateBaseDir string) (*template.Template, error) { | ||||||
|  | @ -41,16 +38,34 @@ func loadTemplates(templateBaseDir string) (*template.Template, error) { | ||||||
| 	return template.ParseGlob(tmPath) | 	return template.ParseGlob(tmPath) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func assembleMessage(mailSubject string, mailBody string, mailTo string, mailFrom string) []byte { | // https://datatracker.ietf.org/doc/html/rfc2822 | ||||||
| 	from := fmt.Sprintf("From: GoToSocial <%s>", mailFrom) | // I did not read the RFC, I just copy and pasted from | ||||||
| 	to := fmt.Sprintf("To: %s", mailTo) | // https://pkg.go.dev/net/smtp#SendMail | ||||||
|  | // and it did seem to work. | ||||||
|  | func assembleMessage(mailSubject string, mailBody string, mailTo string, mailFrom string) ([]byte, error) { | ||||||
|  | 
 | ||||||
|  | 	if strings.Contains(mailSubject, "\r") || strings.Contains(mailSubject, "\n") { | ||||||
|  | 		return nil, errors.New("email subject must not contain newline characters") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if strings.Contains(mailFrom, "\r") || strings.Contains(mailFrom, "\n") { | ||||||
|  | 		return nil, errors.New("email from address must not contain newline characters") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if strings.Contains(mailTo, "\r") || strings.Contains(mailTo, "\n") { | ||||||
|  | 		return nil, errors.New("email to address must not contain newline characters") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// normalize the message body to use CRLF line endings | ||||||
|  | 	mailBody = strings.ReplaceAll(mailBody, "\r\n", "\n") | ||||||
|  | 	mailBody = strings.ReplaceAll(mailBody, "\n", "\r\n") | ||||||
| 
 | 
 | ||||||
| 	msg := []byte( | 	msg := []byte( | ||||||
| 		mailSubject + "\r\n" + | 		"To: " + mailTo + "\r\n" + | ||||||
| 			from + "\r\n" + | 			"Subject: " + mailSubject + "\r\n" + | ||||||
| 			to + "\r\n" + | 			"\r\n" + | ||||||
| 			mime + "\r\n" + | 			mailBody + "\r\n", | ||||||
| 			mailBody + "\r\n") | 	) | ||||||
| 
 | 
 | ||||||
| 	return msg | 	return msg, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ func (suite *UtilTestSuite) TestTemplateConfirm() { | ||||||
| 
 | 
 | ||||||
| 	suite.sender.SendConfirmEmail("user@example.org", confirmData) | 	suite.sender.SendConfirmEmail("user@example.org", confirmData) | ||||||
| 	suite.Len(suite.sentEmails, 1) | 	suite.Len(suite.sentEmails, 1) | ||||||
| 	suite.Equal("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial <test@example.org>\r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n    </head>\n    <body>\n        <div>\n            <h1>\n                Hello test!\n            </h1>\n        </div>\n        <div>\n            <p>\n                You are receiving this mail because you've requested an account on <a href=\"https://example.org\">Test Instance</a>.\n            </p>\n            <p>\n                We just need to confirm that this is your email address. To confirm your email, <a href=\"https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\">click here</a> or paste the following in your browser's address bar:\n            </p>\n            <p>\n                <code>\n                    https://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n                </code>\n            </p>\n        </div>\n        <div>\n            <p>\n                If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"https://example.org\">Test Instance</a>.\n            </p>\n        </div>\n    </body>\n</html>\r\n", suite.sentEmails["user@example.org"]) | 	suite.Equal("To: user@example.org\r\nSubject: GoToSocial Email Confirmation\r\n\r\nHello test!\r\n\r\nYou are receiving this mail because you've requested an account on https://example.org.\r\n\r\nWe just need to confirm that this is your email address. To confirm your email, paste the following in your browser's address bar:\r\n\r\nhttps://example.org/confirm_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org\r\n\r\n", suite.sentEmails["user@example.org"]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *UtilTestSuite) TestTemplateReset() { | func (suite *UtilTestSuite) TestTemplateReset() { | ||||||
|  | @ -52,7 +52,7 @@ func (suite *UtilTestSuite) TestTemplateReset() { | ||||||
| 
 | 
 | ||||||
| 	suite.sender.SendResetEmail("user@example.org", resetData) | 	suite.sender.SendResetEmail("user@example.org", resetData) | ||||||
| 	suite.Len(suite.sentEmails, 1) | 	suite.Len(suite.sentEmails, 1) | ||||||
| 	suite.Equal("Subject: GoToSocial Password Reset\r\nFrom: GoToSocial <test@example.org>\r\nTo: user@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n    </head>\n    <body>\n        <div>\n            <h1>\n                Hello test!\n            </h1>\n        </div>\n        <div>\n            <p>\n                You are receiving this mail because a password reset has been requested for your account on <a href=\"https://example.org\">Test Instance</a>.\n            </p>\n            <p>\n                To reset your password, <a href=\"https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\">click here</a> or paste the following in your browser's address bar:\n            </p>\n            <p>\n                <code>\n                    https://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\n                </code>\n            </p>\n        </div>\n        <div>\n            <p>\n                If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"https://example.org\">Test Instance</a>.\n            </p>\n        </div>\n    </body>\n</html>\r\n", suite.sentEmails["user@example.org"]) | 	suite.Equal("To: user@example.org\r\nSubject: GoToSocial Password Reset\r\n\r\nHello test!\r\n\r\nYou are receiving this mail because a password reset has been requested for your account on https://example.org.\r\n\r\nTo reset your password, paste the following in your browser's address bar:\r\n\r\nhttps://example.org/reset_email?token=ee24f71d-e615-43f9-afae-385c0799b7fa\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of https://example.org.\r\n\r\n", suite.sentEmails["user@example.org"]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestUtilTestSuite(t *testing.T) { | func TestUtilTestSuite(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ func (suite *EmailConfirmTestSuite) TestSendConfirmEmail() { | ||||||
| 	suite.NotEmpty(token) | 	suite.NotEmpty(token) | ||||||
| 
 | 
 | ||||||
| 	// email should contain the token | 	// email should contain the token | ||||||
| 	emailShould := fmt.Sprintf("Subject: GoToSocial Email Confirmation\r\nFrom: GoToSocial <test@example.org>\r\nTo: some.email@example.org\r\nMIME-version: 1.0;\nContent-Type: text/html;\r\n<!DOCTYPE html>\n<html>\n    </head>\n    <body>\n        <div>\n            <h1>\n                Hello the_mighty_zork!\n            </h1>\n        </div>\n        <div>\n            <p>\n                You are receiving this mail because you've requested an account on <a href=\"http://localhost:8080\">localhost:8080</a>.\n            </p>\n            <p>\n                We just need to confirm that this is your email address. To confirm your email, <a href=\"http://localhost:8080/confirm_email?token=%s\">click here</a> or paste the following in your browser's address bar:\n            </p>\n            <p>\n                <code>\n                    http://localhost:8080/confirm_email?token=%s\n                </code>\n            </p>\n        </div>\n        <div>\n            <p>\n                If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of <a href=\"http://localhost:8080\">localhost:8080</a>.\n            </p>\n        </div>\n    </body>\n</html>\r\n", token, token) | 	emailShould := fmt.Sprintf("To: some.email@example.org\r\nSubject: GoToSocial Email Confirmation\r\n\r\nHello the_mighty_zork!\r\n\r\nYou are receiving this mail because you've requested an account on http://localhost:8080.\r\n\r\nWe just need to confirm that this is your email address. To confirm your email, paste the following in your browser's address bar:\r\n\r\nhttp://localhost:8080/confirm_email?token=%s\r\n\r\nIf you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of http://localhost:8080\r\n\r\n", token) | ||||||
| 	suite.Equal(emailShould, email) | 	suite.Equal(emailShould, email) | ||||||
| 
 | 
 | ||||||
| 	// confirmationSentAt should be recent | 	// confirmationSentAt should be recent | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								web/template/email_confirm_text.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/template/email_confirm_text.tmpl
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | Hello {{.Username}}! | ||||||
|  | 
 | ||||||
|  | You are receiving this mail because you've requested an account on {{.InstanceURL}}. | ||||||
|  | 
 | ||||||
|  | We just need to confirm that this is your email address. To confirm your email, paste the following in your browser's address bar: | ||||||
|  | 
 | ||||||
|  | {{.ConfirmLink}} | ||||||
|  | 
 | ||||||
|  | If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of {{.InstanceURL}} | ||||||
							
								
								
									
										9
									
								
								web/template/email_reset_text.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/template/email_reset_text.tmpl
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | Hello {{.Username}}! | ||||||
|  | 
 | ||||||
|  | You are receiving this mail because a password reset has been requested for your account on {{.InstanceURL}}. | ||||||
|  | 
 | ||||||
|  | To reset your password, paste the following in your browser's address bar: | ||||||
|  | 
 | ||||||
|  | {{.ResetLink}} | ||||||
|  | 
 | ||||||
|  | If you believe you've been sent this email in error, feel free to ignore it, or contact the administrator of {{.InstanceURL}}. | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue