1
0

provider.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package smtp
  2. import (
  3. "net/smtp"
  4. "net/textproto"
  5. "strings"
  6. "github.com/cockroachdb/errors"
  7. log "unknwon.dev/clog/v2"
  8. "gogs.io/gogs/internal/auth"
  9. )
  10. // Provider contains configuration of an SMTP authentication provider.
  11. type Provider struct {
  12. config *Config
  13. }
  14. // NewProvider creates a new SMTP authentication provider.
  15. func NewProvider(cfg *Config) auth.Provider {
  16. return &Provider{
  17. config: cfg,
  18. }
  19. }
  20. // Authenticate queries if login/password is valid against the SMTP server,
  21. // and returns queried information when succeeded.
  22. func (p *Provider) Authenticate(login, password string) (*auth.ExternalAccount, error) {
  23. // Verify allowed domains
  24. if p.config.AllowedDomains != "" {
  25. fields := strings.SplitN(login, "@", 3)
  26. if len(fields) != 2 {
  27. return nil, auth.ErrBadCredentials{Args: map[string]any{"login": login}}
  28. }
  29. domain := fields[1]
  30. isAllowed := false
  31. for _, allowed := range strings.Split(p.config.AllowedDomains, ",") {
  32. if domain == allowed {
  33. isAllowed = true
  34. break
  35. }
  36. }
  37. if !isAllowed {
  38. return nil, auth.ErrBadCredentials{Args: map[string]any{"login": login}}
  39. }
  40. }
  41. var smtpAuth smtp.Auth
  42. switch p.config.Auth {
  43. case Plain:
  44. smtpAuth = smtp.PlainAuth("", login, password, p.config.Host)
  45. case Login:
  46. smtpAuth = &smtpLoginAuth{login, password}
  47. default:
  48. return nil, errors.Errorf("unsupported SMTP authentication type %q", p.config.Auth)
  49. }
  50. if err := p.config.doAuth(smtpAuth); err != nil {
  51. log.Trace("SMTP: Authentication failed: %v", err)
  52. // Check standard error format first, then fallback to the worse case.
  53. tperr, ok := err.(*textproto.Error)
  54. if (ok && tperr.Code == 535) ||
  55. strings.Contains(err.Error(), "Username and Password not accepted") {
  56. return nil, auth.ErrBadCredentials{Args: map[string]any{"login": login}}
  57. }
  58. return nil, err
  59. }
  60. username := login
  61. // NOTE: It is not required to have "@" in `login` for a successful SMTP authentication.
  62. idx := strings.Index(login, "@")
  63. if idx > -1 {
  64. username = login[:idx]
  65. }
  66. return &auth.ExternalAccount{
  67. Login: login,
  68. Name: username,
  69. Email: login,
  70. }, nil
  71. }
  72. func (p *Provider) Config() any {
  73. return p.config
  74. }
  75. func (*Provider) HasTLS() bool {
  76. return true
  77. }
  78. func (p *Provider) UseTLS() bool {
  79. return p.config.TLS
  80. }
  81. func (p *Provider) SkipTLSVerify() bool {
  82. return p.config.SkipVerify
  83. }
  84. const (
  85. Plain = "PLAIN"
  86. Login = "LOGIN"
  87. )
  88. var AuthTypes = []string{Plain, Login}
  89. type smtpLoginAuth struct {
  90. username, password string
  91. }
  92. func (auth *smtpLoginAuth) Start(_ *smtp.ServerInfo) (string, []byte, error) {
  93. return "LOGIN", []byte(auth.username), nil
  94. }
  95. func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  96. if more {
  97. switch string(fromServer) {
  98. case "Username:":
  99. return []byte(auth.username), nil
  100. case "Password:":
  101. return []byte(auth.password), nil
  102. }
  103. }
  104. return nil, nil
  105. }