provider.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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 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. "gogs.io/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]interface{}{"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]interface{}{"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 == 535) ||
  58. strings.Contains(err.Error(), "Username and Password not accepted") {
  59. return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}
  60. }
  61. return nil, err
  62. }
  63. username := login
  64. // NOTE: It is not required to have "@" in `login` for a successful SMTP authentication.
  65. idx := strings.Index(login, "@")
  66. if idx > -1 {
  67. username = login[:idx]
  68. }
  69. return &auth.ExternalAccount{
  70. Login: login,
  71. Name: username,
  72. Email: login,
  73. }, nil
  74. }
  75. func (p *Provider) Config() interface{} {
  76. return p.config
  77. }
  78. func (p *Provider) HasTLS() bool {
  79. return true
  80. }
  81. func (p *Provider) UseTLS() bool {
  82. return p.config.TLS
  83. }
  84. func (p *Provider) SkipTLSVerify() bool {
  85. return p.config.SkipVerify
  86. }
  87. const (
  88. Plain = "PLAIN"
  89. Login = "LOGIN"
  90. )
  91. var AuthTypes = []string{Plain, Login}
  92. type smtpLoginAuth struct {
  93. username, password string
  94. }
  95. func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  96. return "LOGIN", []byte(auth.username), nil
  97. }
  98. func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  99. if more {
  100. switch string(fromServer) {
  101. case "Username:":
  102. return []byte(auth.username), nil
  103. case "Password:":
  104. return []byte(auth.password), nil
  105. }
  106. }
  107. return nil, nil
  108. }