123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- // Copyright 2020 The Gogs Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
- package smtp
- import (
- "net/smtp"
- "net/textproto"
- "strings"
- "github.com/pkg/errors"
- log "unknwon.dev/clog/v2"
- "gogs.io/gogs/internal/auth"
- )
- // Provider contains configuration of an SMTP authentication provider.
- type Provider struct {
- config *Config
- }
- // NewProvider creates a new SMTP authentication provider.
- func NewProvider(cfg *Config) auth.Provider {
- return &Provider{
- config: cfg,
- }
- }
- // Authenticate queries if login/password is valid against the SMTP server,
- // and returns queried information when succeeded.
- func (p *Provider) Authenticate(login, password string) (*auth.ExternalAccount, error) {
- // Verify allowed domains
- if p.config.AllowedDomains != "" {
- fields := strings.SplitN(login, "@", 3)
- if len(fields) != 2 {
- return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}
- }
- domain := fields[1]
- isAllowed := false
- for _, allowed := range strings.Split(p.config.AllowedDomains, ",") {
- if domain == allowed {
- isAllowed = true
- break
- }
- }
- if !isAllowed {
- return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}
- }
- }
- var smtpAuth smtp.Auth
- switch p.config.Auth {
- case Plain:
- smtpAuth = smtp.PlainAuth("", login, password, p.config.Host)
- case Login:
- smtpAuth = &smtpLoginAuth{login, password}
- default:
- return nil, errors.Errorf("unsupported SMTP authentication type %q", p.config.Auth)
- }
- if err := p.config.doAuth(smtpAuth); err != nil {
- log.Trace("SMTP: Authentication failed: %v", err)
- // Check standard error format first, then fallback to the worse case.
- tperr, ok := err.(*textproto.Error)
- if (ok && tperr.Code == 535) ||
- strings.Contains(err.Error(), "Username and Password not accepted") {
- return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}
- }
- return nil, err
- }
- username := login
- // NOTE: It is not required to have "@" in `login` for a successful SMTP authentication.
- idx := strings.Index(login, "@")
- if idx > -1 {
- username = login[:idx]
- }
- return &auth.ExternalAccount{
- Login: login,
- Name: username,
- Email: login,
- }, nil
- }
- func (p *Provider) Config() interface{} {
- return p.config
- }
- func (p *Provider) HasTLS() bool {
- return true
- }
- func (p *Provider) UseTLS() bool {
- return p.config.TLS
- }
- func (p *Provider) SkipTLSVerify() bool {
- return p.config.SkipVerify
- }
- const (
- Plain = "PLAIN"
- Login = "LOGIN"
- )
- var AuthTypes = []string{Plain, Login}
- type smtpLoginAuth struct {
- username, password string
- }
- func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
- return "LOGIN", []byte(auth.username), nil
- }
- func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
- if more {
- switch string(fromServer) {
- case "Username:":
- return []byte(auth.username), nil
- case "Password:":
- return []byte(auth.password), nil
- }
- }
- return nil, nil
- }
|