You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Cloak/internal/cli_client/config.go

155 lines
4.0 KiB
Go

package cli_client
import (
"encoding/json"
"fmt"
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net"
"strings"
"time"
)
type CLIConfig struct {
client.Config
// LocalHost is the hostname or IP address to listen for incoming proxy client connections
LocalHost string // jsonOptional
// LocalPort is the port to listen for incomig proxy client connections
LocalPort string // jsonOptional
// AlternativeNames is a list of ServerName Cloak may randomly pick from for different sessions
// Optional
AlternativeNames []string
// StreamTimeout is the duration, in seconds, for an incoming connection to be automatically closed after the last
// piece of incoming data .
// Optional, Defaults to 300
StreamTimeout int
}
// semi-colon separated value. This is for Android plugin options
func ssvToJson(ssv string) (ret []byte) {
elem := func(val string, lst []string) bool {
for _, v := range lst {
if val == v {
return true
}
}
return false
}
unescape := func(s string) string {
r := strings.Replace(s, `\\`, `\`, -1)
r = strings.Replace(r, `\=`, `=`, -1)
r = strings.Replace(r, `\;`, `;`, -1)
return r
}
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
lines := strings.Split(unescape(ssv), ";")
ret = []byte("{")
for _, ln := range lines {
if ln == "" {
break
}
sp := strings.SplitN(ln, "=", 2)
if len(sp) < 2 {
log.Errorf("Malformed config option: %v", ln)
continue
}
key := sp[0]
value := sp[1]
if strings.HasPrefix(key, "AlternativeNames") {
switch strings.Contains(value, ",") {
case true:
domains := strings.Split(value, ",")
for index, domain := range domains {
domains[index] = `"` + domain + `"`
}
value = strings.Join(domains, ",")
ret = append(ret, []byte(`"`+key+`":[`+value+`],`)...)
case false:
ret = append(ret, []byte(`"`+key+`":["`+value+`"],`)...)
}
continue
}
// JSON doesn't like quotation marks around int and bool
// This is extremely ugly but it's still better than writing a tokeniser
if elem(key, unquoted) {
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
} else {
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
}
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
func ParseConfig(conf string) (raw *CLIConfig, err error) {
var content []byte
// Checking if it's a path to json or a ssv string
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
return
}
}
raw = new(CLIConfig)
err = json.Unmarshal(content, &raw)
if err != nil {
return
}
return
}
type LocalConnConfig struct {
LocalAddr string
Timeout time.Duration
MockDomainList []string
Singleplex bool
}
func (raw *CLIConfig) ProcessCLIConfig(worldState common.WorldState) (local LocalConnConfig, remote client.RemoteConnConfig, auth client.AuthInfo, err error) {
remote, auth, err = raw.Config.Process(worldState)
if err != nil {
return
}
if raw.AlternativeNames != nil && len(raw.AlternativeNames) > 0 {
var filteredAlternativeNames []string
for _, alternativeName := range raw.AlternativeNames {
if len(alternativeName) > 0 {
filteredAlternativeNames = append(filteredAlternativeNames, alternativeName)
}
}
local.MockDomainList = raw.AlternativeNames
} else {
local.MockDomainList = []string{}
}
local.MockDomainList = append(local.MockDomainList, auth.MockDomain)
if raw.LocalHost == "" {
err = fmt.Errorf("LocalHost cannot be empty")
return
}
if raw.LocalPort == "" {
err = fmt.Errorf("LocalPort cannot be empty")
return
}
local.LocalAddr = net.JoinHostPort(raw.LocalHost, raw.LocalPort)
// stream no write timeout
if raw.StreamTimeout == 0 {
local.Timeout = 300 * time.Second
} else {
local.Timeout = time.Duration(raw.StreamTimeout) * time.Second
}
local.Singleplex = raw.NumConn != nil && *raw.NumConn == 0
return
}