mirror of https://github.com/ginuerzh/gost
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
4.0 KiB
199 lines
4.0 KiB
package gost
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-log/log"
|
|
)
|
|
|
|
// 防止 OTP 重放
|
|
var usedOTP sync.Map
|
|
|
|
// Authenticator is an interface for user authentication.
|
|
type Authenticator interface {
|
|
Authenticate(user, password string) bool
|
|
IFAuthenticate(ip net.IP, user, password string) bool
|
|
}
|
|
|
|
func (au *LocalAuthenticator) IFAuthenticate(ip net.IP, user, password string) bool {
|
|
if ip == nil || user == "" || password == "" {
|
|
return false
|
|
}
|
|
|
|
// if isWhiteIP(ip) {
|
|
if len(password) == 64 {
|
|
expected := GeneratePassword(ip.String(), user)
|
|
if expected == password {
|
|
return true
|
|
} else {
|
|
log.Logf("user pass %s/%s, expect pass %s", user, password, expected)
|
|
}
|
|
} else {
|
|
// if !ip.IsLoopback() && !ip.IsPrivate() { // 存的时候已经判断.
|
|
secret := generateSecret(ip.String(), user)
|
|
// ok, _ := verifyOTP(secret, password)
|
|
ok := VerifyShortOTP(secret, password, config.Auth.DynamicPeriod)
|
|
|
|
if !ok {
|
|
log.Logf("otp verify fail user=%s ip=%s pass=%s", user, ip, password)
|
|
return false
|
|
}
|
|
// todo: 备用.
|
|
// 防止 OTP 重放
|
|
// key := user + ":" + ip.String() + ":" + password
|
|
// if _, exists := usedOTP.Load(key); exists {
|
|
// log.Logf("otp replay attack user=%s ip=%s", user, ip)
|
|
// return false
|
|
// }
|
|
// usedOTP.Store(key, _)
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LocalAuthenticator is an Authenticator that authenticates client by local key-value pairs.
|
|
type LocalAuthenticator struct {
|
|
kvs map[string]string
|
|
period time.Duration
|
|
stopped chan struct{}
|
|
mux sync.RWMutex
|
|
}
|
|
|
|
// NewLocalAuthenticator creates an Authenticator that authenticates client by local infos.
|
|
func NewLocalAuthenticator(kvs map[string]string) *LocalAuthenticator {
|
|
return &LocalAuthenticator{
|
|
kvs: kvs,
|
|
stopped: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Authenticate checks the validity of the provided user-password pair.
|
|
func (au *LocalAuthenticator) Authenticate(user, password string) bool {
|
|
if au == nil {
|
|
return true
|
|
}
|
|
|
|
au.mux.RLock()
|
|
defer au.mux.RUnlock()
|
|
|
|
if len(au.kvs) == 0 {
|
|
return true
|
|
}
|
|
|
|
v, ok := au.kvs[user]
|
|
return ok && (v == "" || password == v)
|
|
}
|
|
|
|
// Add adds a key-value pair to the Authenticator.
|
|
func (au *LocalAuthenticator) Add(k, v string) {
|
|
au.mux.Lock()
|
|
defer au.mux.Unlock()
|
|
if au.kvs == nil {
|
|
au.kvs = make(map[string]string)
|
|
}
|
|
au.kvs[k] = v
|
|
}
|
|
|
|
// Reload parses config from r, then live reloads the Authenticator.
|
|
func (au *LocalAuthenticator) Reload(r io.Reader) error {
|
|
var period time.Duration
|
|
kvs := make(map[string]string)
|
|
|
|
if r == nil || au.Stopped() {
|
|
return nil
|
|
}
|
|
|
|
// splitLine splits a line text by white space.
|
|
// A line started with '#' will be ignored, otherwise it is valid.
|
|
split := func(line string) []string {
|
|
if line == "" {
|
|
return nil
|
|
}
|
|
line = strings.Replace(line, "\t", " ", -1)
|
|
line = strings.TrimSpace(line)
|
|
|
|
if strings.IndexByte(line, '#') == 0 {
|
|
return nil
|
|
}
|
|
|
|
var ss []string
|
|
for _, s := range strings.Split(line, " ") {
|
|
if s = strings.TrimSpace(s); s != "" {
|
|
ss = append(ss, s)
|
|
}
|
|
}
|
|
return ss
|
|
}
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
ss := split(line)
|
|
if len(ss) == 0 {
|
|
continue
|
|
}
|
|
|
|
switch ss[0] {
|
|
case "reload": // reload option
|
|
if len(ss) > 1 {
|
|
period, _ = time.ParseDuration(ss[1])
|
|
}
|
|
default:
|
|
var k, v string
|
|
k = ss[0]
|
|
if len(ss) > 1 {
|
|
v = ss[1]
|
|
}
|
|
kvs[k] = v
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
au.mux.Lock()
|
|
defer au.mux.Unlock()
|
|
|
|
au.period = period
|
|
au.kvs = kvs
|
|
|
|
return nil
|
|
}
|
|
|
|
// Period returns the reload period.
|
|
func (au *LocalAuthenticator) Period() time.Duration {
|
|
if au.Stopped() {
|
|
return -1
|
|
}
|
|
|
|
au.mux.RLock()
|
|
defer au.mux.RUnlock()
|
|
|
|
return au.period
|
|
}
|
|
|
|
// Stop stops reloading.
|
|
func (au *LocalAuthenticator) Stop() {
|
|
select {
|
|
case <-au.stopped:
|
|
default:
|
|
close(au.stopped)
|
|
}
|
|
}
|
|
|
|
// Stopped checks whether the reloader is stopped.
|
|
func (au *LocalAuthenticator) Stopped() bool {
|
|
select {
|
|
case <-au.stopped:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|