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