mirror of https://github.com/gogs/gogs.git
133 lines
3.0 KiB
Go
133 lines
3.0 KiB
Go
// 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]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
|
|
}
|