1
0

provider.go 2.7 KB

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