authentication resolves #3
This commit is contained in:
parent
4f0d65f184
commit
73ea8b8ee4
@ -113,6 +113,10 @@ func main() {
|
|||||||
if !parseArgs() {
|
if !parseArgs() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(oneCommand) > 0 && (oneCommand[0] == 'h' || oneCommand[0] == 'H') && strings.Split(strings.ToLower(oneCommand), " ")[0] == "help" {
|
||||||
|
showHelp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", hostname, port)
|
addr := fmt.Sprintf("%s:%d", hostname, port)
|
||||||
conn, err := client.Dial(addr)
|
conn, err := client.Dial(addr)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tidwall/tile38/controller"
|
"github.com/tidwall/tile38/controller"
|
||||||
"github.com/tidwall/tile38/controller/log"
|
"github.com/tidwall/tile38/controller/log"
|
||||||
@ -15,23 +16,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dir string
|
dir string
|
||||||
port int
|
port int
|
||||||
host string
|
host string
|
||||||
verbose bool
|
verbose bool
|
||||||
veryVerbose bool
|
veryVerbose bool
|
||||||
devMode bool
|
devMode bool
|
||||||
quiet bool
|
quiet bool
|
||||||
|
protectedMode bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// parse non standard args.
|
||||||
|
nargs := []string{os.Args[0]}
|
||||||
|
for i := 1; i < len(os.Args); i++ {
|
||||||
|
switch os.Args[i] {
|
||||||
|
case "--protected-mode", "-protected-mode":
|
||||||
|
i++
|
||||||
|
if i < len(os.Args) {
|
||||||
|
switch strings.ToLower(os.Args[i]) {
|
||||||
|
case "no":
|
||||||
|
protectedMode = false
|
||||||
|
case "yes":
|
||||||
|
protectedMode = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
|
||||||
|
os.Exit(1)
|
||||||
|
case "--dev", "-dev":
|
||||||
|
devMode = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nargs = append(nargs, os.Args[i])
|
||||||
|
}
|
||||||
|
os.Args = nargs
|
||||||
|
|
||||||
flag.IntVar(&port, "p", 9851, "The listening port.")
|
flag.IntVar(&port, "p", 9851, "The listening port.")
|
||||||
flag.StringVar(&host, "h", "127.0.0.1", "The listening host.")
|
flag.StringVar(&host, "h", "", "The listening host.")
|
||||||
flag.StringVar(&dir, "d", "data", "The data directory.")
|
flag.StringVar(&dir, "d", "data", "The data directory.")
|
||||||
flag.BoolVar(&verbose, "v", false, "Enable verbose logging.")
|
flag.BoolVar(&verbose, "v", false, "Enable verbose logging.")
|
||||||
flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.")
|
flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.")
|
||||||
flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.")
|
flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.")
|
||||||
flag.BoolVar(&devMode, "dev", false, "Activates dev mode. DEV ONLY.")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
var logw io.Writer = os.Stderr
|
var logw io.Writer = os.Stderr
|
||||||
if quiet {
|
if quiet {
|
||||||
@ -43,6 +69,12 @@ func main() {
|
|||||||
})
|
})
|
||||||
core.DevMode = devMode
|
core.DevMode = devMode
|
||||||
core.ShowDebugMessages = veryVerbose
|
core.ShowDebugMessages = veryVerbose
|
||||||
|
core.ProtectedMode = protectedMode
|
||||||
|
|
||||||
|
hostd := ""
|
||||||
|
if host != "" {
|
||||||
|
hostd = "Addr: " + host + ", "
|
||||||
|
}
|
||||||
|
|
||||||
// _____ _ _ ___ ___
|
// _____ _ _ ___ ___
|
||||||
// |_ _|_| |___|_ | . |
|
// |_ _|_| |___|_ | . |
|
||||||
@ -53,11 +85,11 @@ func main() {
|
|||||||
_______ _______
|
_______ _______
|
||||||
| | |
|
| | |
|
||||||
|____ | _ | Tile38 %s (%s) %d bit (%s/%s)
|
|____ | _ | Tile38 %s (%s) %d bit (%s/%s)
|
||||||
| | | Host: %s, Port: %d, PID: %d
|
| | | %sPort: %d, PID: %d
|
||||||
|____ | _ |
|
|____ | _ |
|
||||||
| | | tile38.com
|
| | | tile38.com
|
||||||
|_______|_______|
|
|_______|_______|
|
||||||
`+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, host, port, os.Getpid())
|
`+"\n", core.Version, core.GitSHA, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd, port, os.Getpid())
|
||||||
|
|
||||||
if err := controller.ListenAndServe(host, port, dir); err != nil {
|
if err := controller.ListenAndServe(host, port, dir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
15
controller/auth.go
Normal file
15
controller/auth.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
func (c *Controller) cmdAuth(line string) error {
|
||||||
|
var password string
|
||||||
|
if line, password = token(line); password == "" {
|
||||||
|
return errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
if line != "" {
|
||||||
|
return errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
println(password)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
162
controller/config.go
Normal file
162
controller/config.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a tile38 config
|
||||||
|
type Config struct {
|
||||||
|
FollowHost string `json:"follow_host,omitempty"`
|
||||||
|
FollowPort int `json:"follow_port,omitempty"`
|
||||||
|
FollowID string `json:"follow_id,omitempty"`
|
||||||
|
FollowPos int `json:"follow_pos,omitempty"`
|
||||||
|
ServerID string `json:"server_id,omitempty"`
|
||||||
|
ReadOnly bool `json:"read_only,omitempty"`
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
RequirePassP string `json:"requirepass,omitempty"`
|
||||||
|
RequirePass string `json:"-"`
|
||||||
|
LeaderAuthP string `json:"leaderauth,omitempty"`
|
||||||
|
LeaderAuth string `json:"-"`
|
||||||
|
ProtectedModeP string `json:"protected-mode,omitempty"`
|
||||||
|
ProtectedMode string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) loadConfig() error {
|
||||||
|
data, err := ioutil.ReadFile(c.dir + "/config")
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return c.initConfig()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(data, &c.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// load properties
|
||||||
|
if err := c.setConfigProperty("requirepass", c.config.RequirePassP, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.setConfigProperty("leaderauth", c.config.LeaderAuthP, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.setConfigProperty("protected-mode", c.config.ProtectedModeP, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error {
|
||||||
|
var invalid bool
|
||||||
|
switch name {
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported CONFIG parameter: %s", name)
|
||||||
|
case "requirepass":
|
||||||
|
c.config.RequirePass = value
|
||||||
|
case "leaderauth":
|
||||||
|
c.config.LeaderAuth = value
|
||||||
|
case "protected-mode":
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "":
|
||||||
|
if fromLoad {
|
||||||
|
c.config.ProtectedMode = "yes"
|
||||||
|
} else {
|
||||||
|
invalid = true
|
||||||
|
}
|
||||||
|
case "yes", "no":
|
||||||
|
c.config.ProtectedMode = strings.ToLower(value)
|
||||||
|
default:
|
||||||
|
invalid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if invalid {
|
||||||
|
return fmt.Errorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) getConfigProperty(name string) string {
|
||||||
|
switch name {
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
case "requirepass":
|
||||||
|
return c.config.RequirePass
|
||||||
|
case "leaderauth":
|
||||||
|
return c.config.LeaderAuth
|
||||||
|
case "protected-mode":
|
||||||
|
return c.config.ProtectedMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) initConfig() error {
|
||||||
|
c.config = Config{ServerID: randomKey(16)}
|
||||||
|
return c.writeConfig(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) writeConfig(writeProperties bool) error {
|
||||||
|
var err error
|
||||||
|
bak := c.config
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
// revert changes
|
||||||
|
c.config = bak
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if writeProperties {
|
||||||
|
// save properties
|
||||||
|
c.config.RequirePassP = c.config.RequirePass
|
||||||
|
c.config.LeaderAuthP = c.config.LeaderAuth
|
||||||
|
c.config.ProtectedModeP = c.config.ProtectedMode
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
data, err = json.MarshalIndent(c.config, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(c.dir+"/config", data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) cmdConfig(line string) (string, error) {
|
||||||
|
var start = time.Now()
|
||||||
|
var cmd, name, value string
|
||||||
|
if line, cmd = token(line); cmd == "" {
|
||||||
|
return "", errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(`{"ok":true`)
|
||||||
|
switch strings.ToLower(cmd) {
|
||||||
|
default:
|
||||||
|
return "", errInvalidArgument(cmd)
|
||||||
|
case "get":
|
||||||
|
if line, name = token(line); name == "" || line != "" {
|
||||||
|
return "", errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
value = c.getConfigProperty(name)
|
||||||
|
buf.WriteString(`,"value":` + jsonString(value))
|
||||||
|
case "set":
|
||||||
|
if line, name = token(line); name == "" {
|
||||||
|
return "", errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
value = strings.TrimSpace(line)
|
||||||
|
if err := c.setConfigProperty(name, value, false); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
case "rewrite":
|
||||||
|
if err := c.writeConfig(true); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
@ -4,14 +4,12 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -64,16 +62,6 @@ type Controller struct {
|
|||||||
shrinking bool // aof shrinking flag
|
shrinking bool // aof shrinking flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a tile38 config
|
|
||||||
type Config struct {
|
|
||||||
FollowHost string `json:"follow_host,omitempty"`
|
|
||||||
FollowPort int `json:"follow_port,omitempty"`
|
|
||||||
FollowID string `json:"follow_id,omitempty"`
|
|
||||||
FollowPos int `json:"follow_pos,omitempty"`
|
|
||||||
ServerID string `json:"server_id,omitempty"`
|
|
||||||
ReadOnly bool `json:"read_only,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe starts a new tile38 server
|
// ListenAndServe starts a new tile38 server
|
||||||
func ListenAndServe(host string, port int, dir string) error {
|
func ListenAndServe(host string, port int, dir string) error {
|
||||||
log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA)
|
log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA)
|
||||||
@ -113,8 +101,8 @@ func ListenAndServe(host string, port int, dir string) error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
go c.processLives()
|
go c.processLives()
|
||||||
handler := func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error {
|
handler := func(conn *server.Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error {
|
||||||
err := c.handleInputCommand(string(command), w)
|
err := c.handleInputCommand(conn, string(command), w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "going live" {
|
if err.Error() == "going live" {
|
||||||
return c.goLive(err, conn, rd, websocket)
|
return c.goLive(err, conn, rd, websocket)
|
||||||
@ -123,7 +111,21 @@ func ListenAndServe(host string, port int, dir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return server.ListenAndServe(host, port, handler)
|
protected := func() bool {
|
||||||
|
if !core.ProtectedMode {
|
||||||
|
// --protected-mode no
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if host != "" && host != "127.0.0.1" && host != "::1" && host != "localhost" {
|
||||||
|
// -h address
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.mu.RLock()
|
||||||
|
is := c.config.ProtectedMode != "no" && c.config.RequirePass == ""
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return is
|
||||||
|
}
|
||||||
|
return server.ListenAndServe(host, port, protected, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) setCol(key string, col *collection.Collection) {
|
func (c *Controller) setCol(key string, col *collection.Collection) {
|
||||||
@ -156,12 +158,12 @@ func isReservedFieldName(field string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) handleInputCommand(line string, w io.Writer) error {
|
func (c *Controller) handleInputCommand(conn *server.Conn, line string, w io.Writer) error {
|
||||||
if core.ShowDebugMessages && line != "pInG" {
|
if core.ShowDebugMessages && line != "pInG" {
|
||||||
log.Debug(line)
|
log.Debug(line)
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
// Ping and Help. Just send back the response. No need to put through the pipeline.
|
// Ping. Just send back the response. No need to put through the pipeline.
|
||||||
if len(line) == 4 && (line[0] == 'p' || line[0] == 'P') && lc(line, "ping") {
|
if len(line) == 4 && (line[0] == 'p' || line[0] == 'P') && lc(line, "ping") {
|
||||||
w.Write([]byte(`{"ok":true,"ping":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
w.Write([]byte(`{"ok":true,"ping":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||||
return nil
|
return nil
|
||||||
@ -180,6 +182,26 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
|
|||||||
if cmd == "" {
|
if cmd == "" {
|
||||||
return writeErr(errors.New("empty command"))
|
return writeErr(errors.New("empty command"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !conn.Authenticated {
|
||||||
|
c.mu.RLock()
|
||||||
|
requirePass := c.config.RequirePass
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if requirePass != "" {
|
||||||
|
// This better be an AUTH command.
|
||||||
|
if cmd != "auth" {
|
||||||
|
// Just shut down the pipeline now. The less the client connection knows the better.
|
||||||
|
return writeErr(errors.New("authentication required"))
|
||||||
|
}
|
||||||
|
password, _ := token(line)
|
||||||
|
if requirePass == strings.TrimSpace(password) {
|
||||||
|
conn.Authenticated = true
|
||||||
|
} else {
|
||||||
|
return writeErr(errors.New("invalid password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// choose the locking strategy
|
// choose the locking strategy
|
||||||
switch cmd {
|
switch cmd {
|
||||||
default:
|
default:
|
||||||
@ -203,7 +225,7 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
|
|||||||
if c.config.FollowHost != "" && !c.fcup {
|
if c.config.FollowHost != "" && !c.fcup {
|
||||||
return writeErr(errors.New("catching up to leader"))
|
return writeErr(errors.New("catching up to leader"))
|
||||||
}
|
}
|
||||||
case "follow", "readonly":
|
case "follow", "readonly", "config":
|
||||||
// system operations
|
// system operations
|
||||||
// does not write to aof, but requires a write lock.
|
// does not write to aof, but requires a write lock.
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@ -235,37 +257,6 @@ func (c *Controller) handleInputCommand(line string, w io.Writer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) loadConfig() error {
|
|
||||||
data, err := ioutil.ReadFile(c.dir + "/config")
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return c.initConfig()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(data, &c.config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) initConfig() error {
|
|
||||||
c.config = Config{ServerID: randomKey(16)}
|
|
||||||
return c.writeConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) writeConfig() error {
|
|
||||||
data, err := json.MarshalIndent(c.config, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(c.dir+"/config", data, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomKey(n int) string {
|
func randomKey(n int) string {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
nn, err := rand.Read(b)
|
nn, err := rand.Read(b)
|
||||||
@ -322,9 +313,14 @@ func (c *Controller) command(line string, w io.Writer) (resp string, d commandDe
|
|||||||
case "follow":
|
case "follow":
|
||||||
err = c.cmdFollow(nline)
|
err = c.cmdFollow(nline)
|
||||||
resp = okResp()
|
resp = okResp()
|
||||||
|
case "config":
|
||||||
|
resp, err = c.cmdConfig(nline)
|
||||||
case "readonly":
|
case "readonly":
|
||||||
err = c.cmdReadOnly(nline)
|
err = c.cmdReadOnly(nline)
|
||||||
resp = okResp()
|
resp = okResp()
|
||||||
|
case "auth":
|
||||||
|
err = c.cmdAuth(nline)
|
||||||
|
resp = okResp()
|
||||||
case "stats":
|
case "stats":
|
||||||
resp, err = c.cmdServer(nline)
|
resp, err = c.cmdServer(nline)
|
||||||
case "server":
|
case "server":
|
||||||
|
@ -71,7 +71,7 @@ func (c *Controller) cmdFollow(line string) error {
|
|||||||
c.config.FollowHost = host
|
c.config.FollowHost = host
|
||||||
c.config.FollowPort = port
|
c.config.FollowPort = port
|
||||||
}
|
}
|
||||||
if err := c.writeConfig(); err != nil {
|
if err := c.writeConfig(false); err != nil {
|
||||||
c.config = pconfig // revert
|
c.config = pconfig // revert
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func (c *Controller) cmdReadOnly(line string) error {
|
|||||||
c.config.ReadOnly = false
|
c.config.ReadOnly = false
|
||||||
log.Info("read write")
|
log.Info("read write")
|
||||||
}
|
}
|
||||||
err := c.writeConfig()
|
err := c.writeConfig(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.config = backup
|
c.config = backup
|
||||||
return err
|
return err
|
||||||
|
@ -14,12 +14,44 @@ import (
|
|||||||
"github.com/tidwall/tile38/core"
|
"github.com/tidwall/tile38/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This phrase is copied nearly verbatim from Redis.
|
||||||
|
// https://github.com/antirez/redis/blob/cf42c48adcea05c1bd4b939fcd36a01f23ec6303/src/networking.c
|
||||||
|
var deniedMessage = []byte(strings.TrimSpace(`
|
||||||
|
ACCESS DENIED
|
||||||
|
Tile38 is running in protected mode because protected mode is enabled, no host
|
||||||
|
address was specified, no authentication password is requested to clients. In
|
||||||
|
this mode connections are only accepted from the loopback interface. If you
|
||||||
|
want to connect from external computers to Tile38 you may adopt one of the
|
||||||
|
following solutions:
|
||||||
|
|
||||||
|
1) Disable protected mode sending the command 'CONFIG SET protected-mode no'
|
||||||
|
from the loopback interface by connecting to Tile38 from the same host
|
||||||
|
the server is running, however MAKE SURE Tile38 is not publicly accessible
|
||||||
|
from internet if you do so. Use CONFIG REWRITE to make this change
|
||||||
|
permanent.
|
||||||
|
2) Alternatively you can just disable the protected mode by editing the Tile38
|
||||||
|
configuration file, and setting the protected mode option to 'no', and then
|
||||||
|
restarting the server.
|
||||||
|
3) If you started the server manually just for testing, restart it with the
|
||||||
|
'--protected-mode no' option.
|
||||||
|
4) Setup a host address or an authentication password.
|
||||||
|
|
||||||
|
NOTE: You only need to do one of the above things in order for the server
|
||||||
|
to start accepting connections from the outside.
|
||||||
|
`) + "\r\n")
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
Authenticated bool
|
||||||
|
}
|
||||||
|
|
||||||
var errCloseHTTP = errors.New("close http")
|
var errCloseHTTP = errors.New("close http")
|
||||||
|
|
||||||
// ListenAndServe starts a tile38 server at the specified address.
|
// ListenAndServe starts a tile38 server at the specified address.
|
||||||
func ListenAndServe(
|
func ListenAndServe(
|
||||||
host string, port int,
|
host string, port int,
|
||||||
handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error,
|
protected func() bool,
|
||||||
|
handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error,
|
||||||
) error {
|
) error {
|
||||||
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
|
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -32,13 +64,14 @@ func ListenAndServe(
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleConn(conn, handler)
|
go handleConn(&Conn{Conn: conn}, protected, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(
|
func handleConn(
|
||||||
conn net.Conn,
|
conn *Conn,
|
||||||
handler func(command []byte, conn net.Conn, rd *bufio.Reader, w io.Writer, websocket bool) error,
|
protected func() bool,
|
||||||
|
handler func(conn *Conn, command []byte, rd *bufio.Reader, w io.Writer, websocket bool) error,
|
||||||
) {
|
) {
|
||||||
if core.ShowDebugMessages {
|
if core.ShowDebugMessages {
|
||||||
addr := conn.RemoteAddr().String()
|
addr := conn.RemoteAddr().String()
|
||||||
@ -46,6 +79,14 @@ func handleConn(
|
|||||||
defer func() {
|
defer func() {
|
||||||
log.Debugf("closed connection: %s", addr)
|
log.Debugf("closed connection: %s", addr)
|
||||||
}()
|
}()
|
||||||
|
if !strings.HasPrefix(addr, "127.0.0.1:") && !strings.HasPrefix(addr, "[::1]:") {
|
||||||
|
if protected() {
|
||||||
|
// This is a protected server. Only loopback is allowed.
|
||||||
|
conn.Write(deniedMessage)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
rd := bufio.NewReader(conn)
|
rd := bufio.NewReader(conn)
|
||||||
@ -59,7 +100,8 @@ func handleConn(
|
|||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := handler(command, conn, rd, &b, proto == client.WebSocket); err != nil {
|
|
||||||
|
if err := handler(conn, command, rd, &b, proto == client.WebSocket); err != nil {
|
||||||
if proto == client.HTTP {
|
if proto == client.HTTP {
|
||||||
conn.Write([]byte(`HTTP/1.1 500 ` + err.Error() + "\r\nConnection: close\r\n\r\n"))
|
conn.Write([]byte(`HTTP/1.1 500 ` + err.Error() + "\r\nConnection: close\r\n\r\n"))
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ func (c Command) String() string {
|
|||||||
func (c Command) TermOutput(indent string) string {
|
func (c Command) TermOutput(indent string) string {
|
||||||
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
|
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
|
||||||
line2 := yellow + "summary: " + clear + c.Summary
|
line2 := yellow + "summary: " + clear + c.Summary
|
||||||
line3 := yellow + "since: " + clear + c.Since
|
//line3 := yellow + "since: " + clear + c.Since
|
||||||
return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n"
|
return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnumArg struct {
|
type EnumArg struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Arguments []Argument `json:"arguments`
|
Arguments []Argument `json:"arguments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a EnumArg) String() string {
|
func (a EnumArg) String() string {
|
||||||
@ -846,12 +846,43 @@ var commandsJSON = `{
|
|||||||
"since": "1.0.0",
|
"since": "1.0.0",
|
||||||
"group": "search"
|
"group": "search"
|
||||||
},
|
},
|
||||||
"PING": {
|
"CONFIG GET": {
|
||||||
"summary":"Ping the server",
|
"summary": "Authenticate to the server",
|
||||||
"complexity": "O(1)",
|
"arguments": [
|
||||||
"arguments": [],
|
{
|
||||||
"since": "1.0.0",
|
"name": "which",
|
||||||
"group": "server"
|
"enumargs": [
|
||||||
|
{
|
||||||
|
"name": "GET",
|
||||||
|
"arguments":[
|
||||||
|
{
|
||||||
|
"name": "parameter",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SET",
|
||||||
|
"arguments":[
|
||||||
|
{
|
||||||
|
"name": "parameter",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "string",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "REWRITE",
|
||||||
|
"arguments":[]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"group": "connection"
|
||||||
},
|
},
|
||||||
"SERVER": {
|
"SERVER": {
|
||||||
"summary":"Show server stats and details",
|
"summary":"Show server stats and details",
|
||||||
@ -931,10 +962,25 @@ var commandsJSON = `{
|
|||||||
},
|
},
|
||||||
"AOFSHRINK": {
|
"AOFSHRINK": {
|
||||||
"summary": "Shrinks the aof in the background",
|
"summary": "Shrinks the aof in the background",
|
||||||
"complexity": "O(1)",
|
|
||||||
"arguments": [],
|
|
||||||
"since": "1.0.0",
|
|
||||||
"group": "replication"
|
"group": "replication"
|
||||||
|
},
|
||||||
|
"PING": {
|
||||||
|
"summary": "Ping the server",
|
||||||
|
"group": "connection"
|
||||||
|
},
|
||||||
|
"QUIT": {
|
||||||
|
"summary": "Close the connection",
|
||||||
|
"group": "connection"
|
||||||
|
},
|
||||||
|
"AUTH": {
|
||||||
|
"summary": "Authenticate to the server",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"group": "connection"
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -960,6 +1006,8 @@ var commandsJSON = `{
|
|||||||
// HELP -- Prints this menu. -- O(1) -- F()
|
// HELP -- Prints this menu. -- O(1) -- F()
|
||||||
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
|
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
|
||||||
// FLUSHDB -- Removes all keys. -- O(1) -- F()
|
// FLUSHDB -- Removes all keys. -- O(1) -- F()
|
||||||
|
// CONFIG SET property value -- Set a config property. Is not yet permanent.
|
||||||
|
// CONFIG REWRITE -- Make config changes permanent.
|
||||||
|
|
||||||
// --- Replication ---
|
// --- Replication ---
|
||||||
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
|
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
|
||||||
@ -990,3 +1038,6 @@ var commandsJSON = `{
|
|||||||
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
|
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
|
||||||
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
|
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
|
||||||
// GET... GET key id -- An internal object. -- F(key string, id string)
|
// GET... GET key id -- An internal object. -- F(key string, id string)
|
||||||
|
|
||||||
|
// --- Security ---
|
||||||
|
// AUTH password -- Authenticate to server.
|
||||||
|
@ -701,12 +701,43 @@
|
|||||||
"since": "1.0.0",
|
"since": "1.0.0",
|
||||||
"group": "search"
|
"group": "search"
|
||||||
},
|
},
|
||||||
"PING": {
|
"CONFIG": {
|
||||||
"summary":"Ping the server",
|
"summary": "Authenticate to the server",
|
||||||
"complexity": "O(1)",
|
"arguments": [
|
||||||
"arguments": [],
|
{
|
||||||
"since": "1.0.0",
|
"name": "which",
|
||||||
"group": "server"
|
"enumargs": [
|
||||||
|
{
|
||||||
|
"name": "GET",
|
||||||
|
"arguments":[
|
||||||
|
{
|
||||||
|
"name": "parameter",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SET",
|
||||||
|
"arguments":[
|
||||||
|
{
|
||||||
|
"name": "parameter",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "string",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "REWRITE",
|
||||||
|
"arguments":[]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"group": "connection"
|
||||||
},
|
},
|
||||||
"SERVER": {
|
"SERVER": {
|
||||||
"summary":"Show server stats and details",
|
"summary":"Show server stats and details",
|
||||||
@ -786,9 +817,24 @@
|
|||||||
},
|
},
|
||||||
"AOFSHRINK": {
|
"AOFSHRINK": {
|
||||||
"summary": "Shrinks the aof in the background",
|
"summary": "Shrinks the aof in the background",
|
||||||
"complexity": "O(1)",
|
|
||||||
"arguments": [],
|
|
||||||
"since": "1.0.0",
|
|
||||||
"group": "replication"
|
"group": "replication"
|
||||||
|
},
|
||||||
|
"PING": {
|
||||||
|
"summary": "Ping the server",
|
||||||
|
"group": "connection"
|
||||||
|
},
|
||||||
|
"QUIT": {
|
||||||
|
"summary": "Close the connection",
|
||||||
|
"group": "connection"
|
||||||
|
},
|
||||||
|
"AUTH": {
|
||||||
|
"summary": "Authenticate to the server",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"group": "connection"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,13 +35,13 @@ func (c Command) String() string {
|
|||||||
func (c Command) TermOutput(indent string) string {
|
func (c Command) TermOutput(indent string) string {
|
||||||
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
|
line1 := bright + strings.Replace(c.String(), " ", " "+clear+gray, 1) + clear
|
||||||
line2 := yellow + "summary: " + clear + c.Summary
|
line2 := yellow + "summary: " + clear + c.Summary
|
||||||
line3 := yellow + "since: " + clear + c.Since
|
//line3 := yellow + "since: " + clear + c.Since
|
||||||
return indent + line1 + "\n" + indent + line2 + "\n" + indent + line3 + "\n"
|
return indent + line1 + "\n" + indent + line2 + "\n" //+ indent + line3 + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnumArg struct {
|
type EnumArg struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Arguments []Argument `json:"arguments`
|
Arguments []Argument `json:"arguments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a EnumArg) String() string {
|
func (a EnumArg) String() string {
|
||||||
@ -169,6 +169,8 @@ var commandsJSON = `{{.CommandsJSON}}`
|
|||||||
// HELP -- Prints this menu. -- O(1) -- F()
|
// HELP -- Prints this menu. -- O(1) -- F()
|
||||||
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
|
// READONLY value -- Turn on or off readonly mode. -- O(1) -- F(value boolean)
|
||||||
// FLUSHDB -- Removes all keys. -- O(1) -- F()
|
// FLUSHDB -- Removes all keys. -- O(1) -- F()
|
||||||
|
// CONFIG SET property value -- Set a config property. Is not yet permanent.
|
||||||
|
// CONFIG REWRITE -- Make config changes permanent.
|
||||||
|
|
||||||
// --- Replication ---
|
// --- Replication ---
|
||||||
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
|
// FOLLOW host port -- Follows a leader host. -- O(1) F(host string, port integer)
|
||||||
@ -199,3 +201,6 @@ var commandsJSON = `{{.CommandsJSON}}`
|
|||||||
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
|
// QUADKEY... QUADKEY key -- Quadkey. -- F(key quadkey)
|
||||||
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
|
// TILE... TILE x y z -- Google XYZ tile. -- F(x double, y double, z double)
|
||||||
// GET... GET key id -- An internal object. -- F(key string, id string)
|
// GET... GET key id -- An internal object. -- F(key string, id string)
|
||||||
|
|
||||||
|
// --- Security ---
|
||||||
|
// AUTH password -- Authenticate to server.
|
||||||
|
@ -5,3 +5,5 @@ var DevMode = false
|
|||||||
|
|
||||||
// ShowDebugMessages allows for log.Debug to print to console.
|
// ShowDebugMessages allows for log.Debug to print to console.
|
||||||
var ShowDebugMessages = false
|
var ShowDebugMessages = false
|
||||||
|
|
||||||
|
var ProtectedMode = true
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version string
|
Version = "0.0.0"
|
||||||
BuildTime string
|
BuildTime = ""
|
||||||
GitSHA string
|
GitSHA = "0000000"
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user