| 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 smtpimport (	"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]any{"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]any{"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]any{"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() any {	return p.config}func (*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(_ *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}
 |