provider.go 3.2 KB

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