mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 00:02:20 -05:00 
			
		
		
		
	Make it posible to let mails show e.g.: `Max Musternam (via gitea.kithara.com) <gitea@kithara.com>` Docs: https://gitea.com/gitea/docs/pulls/23 --- *Sponsored by Kithara Software GmbH*
		
			
				
	
	
		
			302 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package setting
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"net"
 | 
						|
	"net/mail"
 | 
						|
	"strings"
 | 
						|
	"text/template"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
 | 
						|
	shellquote "github.com/kballard/go-shellquote"
 | 
						|
)
 | 
						|
 | 
						|
// Mailer represents mail service.
 | 
						|
type Mailer struct {
 | 
						|
	// Mailer
 | 
						|
	Name                 string              `ini:"NAME"`
 | 
						|
	From                 string              `ini:"FROM"`
 | 
						|
	EnvelopeFrom         string              `ini:"ENVELOPE_FROM"`
 | 
						|
	OverrideEnvelopeFrom bool                `ini:"-"`
 | 
						|
	FromName             string              `ini:"-"`
 | 
						|
	FromEmail            string              `ini:"-"`
 | 
						|
	SendAsPlainText      bool                `ini:"SEND_AS_PLAIN_TEXT"`
 | 
						|
	SubjectPrefix        string              `ini:"SUBJECT_PREFIX"`
 | 
						|
	OverrideHeader       map[string][]string `ini:"-"`
 | 
						|
 | 
						|
	// SMTP sender
 | 
						|
	Protocol             string `ini:"PROTOCOL"`
 | 
						|
	SMTPAddr             string `ini:"SMTP_ADDR"`
 | 
						|
	SMTPPort             string `ini:"SMTP_PORT"`
 | 
						|
	User                 string `ini:"USER"`
 | 
						|
	Passwd               string `ini:"PASSWD"`
 | 
						|
	EnableHelo           bool   `ini:"ENABLE_HELO"`
 | 
						|
	HeloHostname         string `ini:"HELO_HOSTNAME"`
 | 
						|
	ForceTrustServerCert bool   `ini:"FORCE_TRUST_SERVER_CERT"`
 | 
						|
	UseClientCert        bool   `ini:"USE_CLIENT_CERT"`
 | 
						|
	ClientCertFile       string `ini:"CLIENT_CERT_FILE"`
 | 
						|
	ClientKeyFile        string `ini:"CLIENT_KEY_FILE"`
 | 
						|
 | 
						|
	// Sendmail sender
 | 
						|
	SendmailPath        string        `ini:"SENDMAIL_PATH"`
 | 
						|
	SendmailArgs        []string      `ini:"-"`
 | 
						|
	SendmailTimeout     time.Duration `ini:"SENDMAIL_TIMEOUT"`
 | 
						|
	SendmailConvertCRLF bool          `ini:"SENDMAIL_CONVERT_CRLF"`
 | 
						|
 | 
						|
	// Customization
 | 
						|
	FromDisplayNameFormat         string             `ini:"FROM_DISPLAY_NAME_FORMAT"`
 | 
						|
	FromDisplayNameFormatTemplate *template.Template `ini:"-"`
 | 
						|
}
 | 
						|
 | 
						|
// MailService the global mailer
 | 
						|
var MailService *Mailer
 | 
						|
 | 
						|
func loadMailsFrom(rootCfg ConfigProvider) {
 | 
						|
	loadMailerFrom(rootCfg)
 | 
						|
	loadRegisterMailFrom(rootCfg)
 | 
						|
	loadNotifyMailFrom(rootCfg)
 | 
						|
	loadIncomingEmailFrom(rootCfg)
 | 
						|
}
 | 
						|
 | 
						|
func loadMailerFrom(rootCfg ConfigProvider) {
 | 
						|
	sec := rootCfg.Section("mailer")
 | 
						|
	// Check mailer setting.
 | 
						|
	if !sec.Key("ENABLED").MustBool() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Handle Deprecations and map on to new configuration
 | 
						|
	// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
 | 
						|
	// if these are removed, the warning will not be shown
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0")
 | 
						|
	if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
 | 
						|
		if sec.Key("MAILER_TYPE").String() == "sendmail" {
 | 
						|
			sec.Key("PROTOCOL").MustString("sendmail")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0")
 | 
						|
	if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
 | 
						|
		givenHost := sec.Key("HOST").String()
 | 
						|
		addr, port, err := net.SplitHostPort(givenHost)
 | 
						|
		if err != nil && strings.Contains(err.Error(), "missing port in address") {
 | 
						|
			addr = givenHost
 | 
						|
		} else if err != nil {
 | 
						|
			log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
 | 
						|
		}
 | 
						|
		if addr == "" {
 | 
						|
			addr = "127.0.0.1"
 | 
						|
		}
 | 
						|
		sec.Key("SMTP_ADDR").MustString(addr)
 | 
						|
		sec.Key("SMTP_PORT").MustString(port)
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0")
 | 
						|
	if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
 | 
						|
		if sec.Key("IS_TLS_ENABLED").MustBool() {
 | 
						|
			sec.Key("PROTOCOL").MustString("smtps")
 | 
						|
		} else {
 | 
						|
			sec.Key("PROTOCOL").MustString("smtp+starttls")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0")
 | 
						|
	if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
 | 
						|
		sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool())
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0")
 | 
						|
	if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
 | 
						|
		sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool())
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0")
 | 
						|
	if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
 | 
						|
		sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool())
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0")
 | 
						|
	if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
 | 
						|
		sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String())
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0")
 | 
						|
	if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
 | 
						|
		sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String())
 | 
						|
	}
 | 
						|
 | 
						|
	deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0")
 | 
						|
	if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
 | 
						|
		sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false))
 | 
						|
	}
 | 
						|
 | 
						|
	if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" {
 | 
						|
		log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0")
 | 
						|
		sec.Key("PROTOCOL").SetValue("smtp+starttls")
 | 
						|
	}
 | 
						|
 | 
						|
	// Set default values & validate
 | 
						|
	sec.Key("NAME").MustString(AppName)
 | 
						|
	sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"})
 | 
						|
	sec.Key("ENABLE_HELO").MustBool(true)
 | 
						|
	sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false)
 | 
						|
	sec.Key("USE_CLIENT_CERT").MustBool(false)
 | 
						|
	sec.Key("SENDMAIL_PATH").MustString("sendmail")
 | 
						|
	sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute)
 | 
						|
	sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true)
 | 
						|
	sec.Key("FROM").MustString(sec.Key("USER").String())
 | 
						|
 | 
						|
	// Now map the values on to the MailService
 | 
						|
	MailService = &Mailer{}
 | 
						|
	if err := sec.MapTo(MailService); err != nil {
 | 
						|
		log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	overrideHeader := rootCfg.Section("mailer.override_header").Keys()
 | 
						|
	MailService.OverrideHeader = make(map[string][]string)
 | 
						|
	for _, key := range overrideHeader {
 | 
						|
		MailService.OverrideHeader[key.Name()] = key.Strings(",")
 | 
						|
	}
 | 
						|
 | 
						|
	// Infer SMTPPort if not set
 | 
						|
	if MailService.SMTPPort == "" {
 | 
						|
		switch MailService.Protocol {
 | 
						|
		case "smtp":
 | 
						|
			MailService.SMTPPort = "25"
 | 
						|
		case "smtps":
 | 
						|
			MailService.SMTPPort = "465"
 | 
						|
		case "smtp+starttls":
 | 
						|
			MailService.SMTPPort = "587"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Infer Protocol
 | 
						|
	if MailService.Protocol == "" {
 | 
						|
		if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
 | 
						|
			MailService.Protocol = "smtp+unix"
 | 
						|
		} else {
 | 
						|
			switch MailService.SMTPPort {
 | 
						|
			case "25":
 | 
						|
				MailService.Protocol = "smtp"
 | 
						|
			case "465":
 | 
						|
				MailService.Protocol = "smtps"
 | 
						|
			case "587":
 | 
						|
				MailService.Protocol = "smtp+starttls"
 | 
						|
			default:
 | 
						|
				log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
 | 
						|
				MailService.Protocol = "smtps"
 | 
						|
				if MailService.SMTPPort == "" {
 | 
						|
					MailService.SMTPPort = "465"
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// we want to warn if users use SMTP on a non-local IP;
 | 
						|
	// we might as well take the opportunity to check that it has an IP at all
 | 
						|
	// This check is not needed for sendmail
 | 
						|
	switch MailService.Protocol {
 | 
						|
	case "sendmail":
 | 
						|
		var err error
 | 
						|
		MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
 | 
						|
		if err != nil {
 | 
						|
			log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
 | 
						|
		}
 | 
						|
	case "smtp", "smtps", "smtp+starttls", "smtp+unix":
 | 
						|
		ips := tryResolveAddr(MailService.SMTPAddr)
 | 
						|
		if MailService.Protocol == "smtp" {
 | 
						|
			for _, ip := range ips {
 | 
						|
				if !ip.IP.IsLoopback() {
 | 
						|
					log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case "dummy": // just mention and do nothing
 | 
						|
	}
 | 
						|
 | 
						|
	if MailService.From != "" {
 | 
						|
		parsed, err := mail.ParseAddress(MailService.From)
 | 
						|
		if err != nil {
 | 
						|
			log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
 | 
						|
		}
 | 
						|
		MailService.FromName = parsed.Name
 | 
						|
		MailService.FromEmail = parsed.Address
 | 
						|
	} else {
 | 
						|
		log.Error("no mailer.FROM provided, email system may not work.")
 | 
						|
	}
 | 
						|
 | 
						|
	MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}")
 | 
						|
	if MailService.FromDisplayNameFormat != "" {
 | 
						|
		template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat)
 | 
						|
		if err != nil {
 | 
						|
			log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err)
 | 
						|
		} else {
 | 
						|
			MailService.FromDisplayNameFormatTemplate = template
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch MailService.EnvelopeFrom {
 | 
						|
	case "":
 | 
						|
		MailService.OverrideEnvelopeFrom = false
 | 
						|
	case "<>":
 | 
						|
		MailService.EnvelopeFrom = ""
 | 
						|
		MailService.OverrideEnvelopeFrom = true
 | 
						|
	default:
 | 
						|
		parsed, err := mail.ParseAddress(MailService.EnvelopeFrom)
 | 
						|
		if err != nil {
 | 
						|
			log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err)
 | 
						|
		}
 | 
						|
		MailService.OverrideEnvelopeFrom = true
 | 
						|
		MailService.EnvelopeFrom = parsed.Address
 | 
						|
	}
 | 
						|
 | 
						|
	log.Info("Mail Service Enabled")
 | 
						|
}
 | 
						|
 | 
						|
func loadRegisterMailFrom(rootCfg ConfigProvider) {
 | 
						|
	if !rootCfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
 | 
						|
		return
 | 
						|
	} else if MailService == nil {
 | 
						|
		log.Warn("Register Mail Service: Mail Service is not enabled")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	Service.RegisterEmailConfirm = true
 | 
						|
	log.Info("Register Mail Service Enabled")
 | 
						|
}
 | 
						|
 | 
						|
func loadNotifyMailFrom(rootCfg ConfigProvider) {
 | 
						|
	if !rootCfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
 | 
						|
		return
 | 
						|
	} else if MailService == nil {
 | 
						|
		log.Warn("Notify Mail Service: Mail Service is not enabled")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	Service.EnableNotifyMail = true
 | 
						|
	log.Info("Notify Mail Service Enabled")
 | 
						|
}
 | 
						|
 | 
						|
func tryResolveAddr(addr string) []net.IPAddr {
 | 
						|
	if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
 | 
						|
		addr = addr[1 : len(addr)-1]
 | 
						|
	}
 | 
						|
	ip := net.ParseIP(addr)
 | 
						|
	if ip != nil {
 | 
						|
		return []net.IPAddr{{IP: ip}}
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 | 
						|
	defer cancel()
 | 
						|
	ips, err := net.DefaultResolver.LookupIPAddr(ctx, addr)
 | 
						|
	if err != nil {
 | 
						|
		log.Warn("could not look up mailer.SMTP_ADDR: %v", err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return ips
 | 
						|
}
 |