code.gitea.io/gitea@v1.19.3/modules/setting/mailer.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"net"
     8  	"net/mail"
     9  	"strings"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/modules/log"
    13  
    14  	shellquote "github.com/kballard/go-shellquote"
    15  )
    16  
    17  // Mailer represents mail service.
    18  type Mailer struct {
    19  	// Mailer
    20  	Name                 string `ini:"NAME"`
    21  	From                 string `ini:"FROM"`
    22  	EnvelopeFrom         string `ini:"ENVELOPE_FROM"`
    23  	OverrideEnvelopeFrom bool   `ini:"-"`
    24  	FromName             string `ini:"-"`
    25  	FromEmail            string `ini:"-"`
    26  	SendAsPlainText      bool   `ini:"SEND_AS_PLAIN_TEXT"`
    27  	SubjectPrefix        string `ini:"SUBJECT_PREFIX"`
    28  
    29  	// SMTP sender
    30  	Protocol             string `ini:"PROTOCOL"`
    31  	SMTPAddr             string `ini:"SMTP_ADDR"`
    32  	SMTPPort             string `ini:"SMTP_PORT"`
    33  	User                 string `ini:"USER"`
    34  	Passwd               string `ini:"PASSWD"`
    35  	EnableHelo           bool   `ini:"ENABLE_HELO"`
    36  	HeloHostname         string `ini:"HELO_HOSTNAME"`
    37  	ForceTrustServerCert bool   `ini:"FORCE_TRUST_SERVER_CERT"`
    38  	UseClientCert        bool   `ini:"USE_CLIENT_CERT"`
    39  	ClientCertFile       string `ini:"CLIENT_CERT_FILE"`
    40  	ClientKeyFile        string `ini:"CLIENT_KEY_FILE"`
    41  
    42  	// Sendmail sender
    43  	SendmailPath        string        `ini:"SENDMAIL_PATH"`
    44  	SendmailArgs        []string      `ini:"-"`
    45  	SendmailTimeout     time.Duration `ini:"SENDMAIL_TIMEOUT"`
    46  	SendmailConvertCRLF bool          `ini:"SENDMAIL_CONVERT_CRLF"`
    47  }
    48  
    49  // MailService the global mailer
    50  var MailService *Mailer
    51  
    52  func loadMailsFrom(rootCfg ConfigProvider) {
    53  	loadMailerFrom(rootCfg)
    54  	loadRegisterMailFrom(rootCfg)
    55  	loadNotifyMailFrom(rootCfg)
    56  	loadIncomingEmailFrom(rootCfg)
    57  }
    58  
    59  func loadMailerFrom(rootCfg ConfigProvider) {
    60  	sec := rootCfg.Section("mailer")
    61  	// Check mailer setting.
    62  	if !sec.Key("ENABLED").MustBool() {
    63  		return
    64  	}
    65  
    66  	// Handle Deprecations and map on to new configuration
    67  	// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
    68  	// if these are removed, the warning will not be shown
    69  	deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0")
    70  	if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
    71  		if sec.Key("MAILER_TYPE").String() == "sendmail" {
    72  			sec.Key("PROTOCOL").MustString("sendmail")
    73  		}
    74  	}
    75  
    76  	deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0")
    77  	if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
    78  		givenHost := sec.Key("HOST").String()
    79  		addr, port, err := net.SplitHostPort(givenHost)
    80  		if err != nil && strings.Contains(err.Error(), "missing port in address") {
    81  			addr = givenHost
    82  		} else if err != nil {
    83  			log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
    84  		}
    85  		if addr == "" {
    86  			addr = "127.0.0.1"
    87  		}
    88  		sec.Key("SMTP_ADDR").MustString(addr)
    89  		sec.Key("SMTP_PORT").MustString(port)
    90  	}
    91  
    92  	deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0")
    93  	if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
    94  		if sec.Key("IS_TLS_ENABLED").MustBool() {
    95  			sec.Key("PROTOCOL").MustString("smtps")
    96  		} else {
    97  			sec.Key("PROTOCOL").MustString("smtp+starttls")
    98  		}
    99  	}
   100  
   101  	deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0")
   102  	if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
   103  		sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool())
   104  	}
   105  
   106  	deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0")
   107  	if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
   108  		sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool())
   109  	}
   110  
   111  	deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0")
   112  	if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
   113  		sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool())
   114  	}
   115  
   116  	deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0")
   117  	if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
   118  		sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String())
   119  	}
   120  
   121  	deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0")
   122  	if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
   123  		sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String())
   124  	}
   125  
   126  	deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0")
   127  	if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
   128  		sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false))
   129  	}
   130  
   131  	if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" {
   132  		log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0")
   133  		sec.Key("PROTOCOL").SetValue("smtp+starttls")
   134  	}
   135  
   136  	// Set default values & validate
   137  	sec.Key("NAME").MustString(AppName)
   138  	sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"})
   139  	sec.Key("ENABLE_HELO").MustBool(true)
   140  	sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false)
   141  	sec.Key("USE_CLIENT_CERT").MustBool(false)
   142  	sec.Key("SENDMAIL_PATH").MustString("sendmail")
   143  	sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute)
   144  	sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true)
   145  	sec.Key("FROM").MustString(sec.Key("USER").String())
   146  
   147  	// Now map the values on to the MailService
   148  	MailService = &Mailer{}
   149  	if err := sec.MapTo(MailService); err != nil {
   150  		log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
   151  	}
   152  
   153  	// Infer SMTPPort if not set
   154  	if MailService.SMTPPort == "" {
   155  		switch MailService.Protocol {
   156  		case "smtp":
   157  			MailService.SMTPPort = "25"
   158  		case "smtps":
   159  			MailService.SMTPPort = "465"
   160  		case "smtp+starttls":
   161  			MailService.SMTPPort = "587"
   162  		}
   163  	}
   164  
   165  	// Infer Protocol
   166  	if MailService.Protocol == "" {
   167  		if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
   168  			MailService.Protocol = "smtp+unix"
   169  		} else {
   170  			switch MailService.SMTPPort {
   171  			case "25":
   172  				MailService.Protocol = "smtp"
   173  			case "465":
   174  				MailService.Protocol = "smtps"
   175  			case "587":
   176  				MailService.Protocol = "smtp+starttls"
   177  			default:
   178  				log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
   179  				MailService.Protocol = "smtps"
   180  				if MailService.SMTPPort == "" {
   181  					MailService.SMTPPort = "465"
   182  				}
   183  			}
   184  		}
   185  	}
   186  
   187  	// we want to warn if users use SMTP on a non-local IP;
   188  	// we might as well take the opportunity to check that it has an IP at all
   189  	// This check is not needed for sendmail
   190  	switch MailService.Protocol {
   191  	case "sendmail":
   192  		var err error
   193  		MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
   194  		if err != nil {
   195  			log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
   196  		}
   197  	case "smtp", "smtps", "smtp+starttls", "smtp+unix":
   198  		ips := tryResolveAddr(MailService.SMTPAddr)
   199  		if MailService.Protocol == "smtp" {
   200  			for _, ip := range ips {
   201  				if !ip.IsLoopback() {
   202  					log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
   203  					break
   204  				}
   205  			}
   206  		}
   207  	case "dummy": // just mention and do nothing
   208  	}
   209  
   210  	if MailService.From != "" {
   211  		parsed, err := mail.ParseAddress(MailService.From)
   212  		if err != nil {
   213  			log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
   214  		}
   215  		MailService.FromName = parsed.Name
   216  		MailService.FromEmail = parsed.Address
   217  	} else {
   218  		log.Error("no mailer.FROM provided, email system may not work.")
   219  	}
   220  
   221  	switch MailService.EnvelopeFrom {
   222  	case "":
   223  		MailService.OverrideEnvelopeFrom = false
   224  	case "<>":
   225  		MailService.EnvelopeFrom = ""
   226  		MailService.OverrideEnvelopeFrom = true
   227  	default:
   228  		parsed, err := mail.ParseAddress(MailService.EnvelopeFrom)
   229  		if err != nil {
   230  			log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err)
   231  		}
   232  		MailService.OverrideEnvelopeFrom = true
   233  		MailService.EnvelopeFrom = parsed.Address
   234  	}
   235  
   236  	log.Info("Mail Service Enabled")
   237  }
   238  
   239  func loadRegisterMailFrom(rootCfg ConfigProvider) {
   240  	if !rootCfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
   241  		return
   242  	} else if MailService == nil {
   243  		log.Warn("Register Mail Service: Mail Service is not enabled")
   244  		return
   245  	}
   246  	Service.RegisterEmailConfirm = true
   247  	log.Info("Register Mail Service Enabled")
   248  }
   249  
   250  func loadNotifyMailFrom(rootCfg ConfigProvider) {
   251  	if !rootCfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
   252  		return
   253  	} else if MailService == nil {
   254  		log.Warn("Notify Mail Service: Mail Service is not enabled")
   255  		return
   256  	}
   257  	Service.EnableNotifyMail = true
   258  	log.Info("Notify Mail Service Enabled")
   259  }
   260  
   261  func tryResolveAddr(addr string) []net.IP {
   262  	if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
   263  		addr = addr[1 : len(addr)-1]
   264  	}
   265  	ip := net.ParseIP(addr)
   266  	if ip != nil {
   267  		ips := make([]net.IP, 1)
   268  		ips[0] = ip
   269  		return ips
   270  	}
   271  	ips, err := net.LookupIP(addr)
   272  	if err != nil {
   273  		log.Warn("could not look up mailer.SMTP_ADDR: %v", err)
   274  		return make([]net.IP, 0)
   275  	}
   276  	return ips
   277  }