2018-10-07 17:09:45 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2020-04-08 21:07:11 +00:00
|
|
|
"crypto"
|
2018-10-07 17:09:45 +00:00
|
|
|
"encoding/json"
|
2020-04-06 12:07:16 +00:00
|
|
|
"fmt"
|
2020-04-09 21:11:12 +00:00
|
|
|
"github.com/cbeuw/Cloak/internal/common"
|
2020-07-27 15:33:01 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2018-10-07 17:09:45 +00:00
|
|
|
"io/ioutil"
|
2020-04-06 12:07:16 +00:00
|
|
|
"net"
|
2018-10-07 17:09:45 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2018-10-14 19:32:54 +00:00
|
|
|
|
2019-01-25 00:24:47 +00:00
|
|
|
"github.com/cbeuw/Cloak/internal/ecdh"
|
2019-08-20 21:43:04 +00:00
|
|
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
2018-10-07 17:09:45 +00:00
|
|
|
)
|
|
|
|
|
2020-04-09 21:11:12 +00:00
|
|
|
// RawConfig represents the fields in the config json file
|
2020-04-06 12:07:16 +00:00
|
|
|
// nullable means if it's empty, a default value will be chosen in SplitConfigs
|
|
|
|
// jsonOptional means if the json's empty, its value will be set from environment variables or commandline args
|
|
|
|
// but it mustn't be empty when SplitConfigs is called
|
2020-04-09 21:11:12 +00:00
|
|
|
type RawConfig struct {
|
2019-06-09 11:05:41 +00:00
|
|
|
ServerName string
|
|
|
|
ProxyMethod string
|
|
|
|
EncryptionMethod string
|
2019-12-29 16:55:21 +00:00
|
|
|
UID []byte
|
|
|
|
PublicKey []byte
|
2019-08-12 21:43:16 +00:00
|
|
|
NumConn int
|
2020-04-06 12:07:16 +00:00
|
|
|
LocalHost string // jsonOptional
|
|
|
|
LocalPort string // jsonOptional
|
|
|
|
RemoteHost string // jsonOptional
|
|
|
|
RemotePort string // jsonOptional
|
|
|
|
|
|
|
|
// defaults set in SplitConfigs
|
2020-04-06 12:11:19 +00:00
|
|
|
UDP bool // nullable
|
2020-04-06 12:07:16 +00:00
|
|
|
BrowserSig string // nullable
|
|
|
|
Transport string // nullable
|
|
|
|
StreamTimeout int // nullable
|
|
|
|
KeepAlive int // nullable
|
2018-10-07 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 13:09:48 +00:00
|
|
|
type RemoteConnConfig struct {
|
2020-10-15 20:32:38 +00:00
|
|
|
Singleplex bool
|
2020-04-08 21:07:11 +00:00
|
|
|
NumConn int
|
|
|
|
KeepAlive time.Duration
|
|
|
|
RemoteAddr string
|
|
|
|
TransportMaker func() Transport
|
|
|
|
}
|
|
|
|
|
2020-04-10 13:09:48 +00:00
|
|
|
type LocalConnConfig struct {
|
2020-04-06 12:07:16 +00:00
|
|
|
LocalAddr string
|
|
|
|
Timeout time.Duration
|
2018-10-07 17:09:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 13:09:48 +00:00
|
|
|
type AuthInfo struct {
|
2020-04-08 21:07:11 +00:00
|
|
|
UID []byte
|
|
|
|
SessionId uint32
|
|
|
|
ProxyMethod string
|
|
|
|
EncryptionMethod byte
|
|
|
|
Unordered bool
|
|
|
|
ServerPubKey crypto.PublicKey
|
|
|
|
MockDomain string
|
2020-04-09 21:11:12 +00:00
|
|
|
WorldState common.WorldState
|
2020-04-08 21:07:11 +00:00
|
|
|
}
|
|
|
|
|
2018-10-07 17:09:45 +00:00
|
|
|
// semi-colon separated value. This is for Android plugin options
|
|
|
|
func ssvToJson(ssv string) (ret []byte) {
|
2020-04-06 12:07:16 +00:00
|
|
|
elem := func(val string, lst []string) bool {
|
|
|
|
for _, v := range lst {
|
|
|
|
if val == v {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2018-10-07 17:09:45 +00:00
|
|
|
unescape := func(s string) string {
|
2018-12-17 22:12:38 +00:00
|
|
|
r := strings.Replace(s, `\\`, `\`, -1)
|
|
|
|
r = strings.Replace(r, `\=`, `=`, -1)
|
|
|
|
r = strings.Replace(r, `\;`, `;`, -1)
|
2018-10-07 17:09:45 +00:00
|
|
|
return r
|
|
|
|
}
|
2020-04-06 12:11:19 +00:00
|
|
|
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
|
2018-10-07 17:09:45 +00:00
|
|
|
lines := strings.Split(unescape(ssv), ";")
|
|
|
|
ret = []byte("{")
|
|
|
|
for _, ln := range lines {
|
|
|
|
if ln == "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
sp := strings.SplitN(ln, "=", 2)
|
2020-07-27 15:33:01 +00:00
|
|
|
if len(sp) < 2 {
|
|
|
|
log.Errorf("Malformed config option: %v", ln)
|
|
|
|
continue
|
|
|
|
}
|
2018-10-07 17:09:45 +00:00
|
|
|
key := sp[0]
|
|
|
|
value := sp[1]
|
2019-08-20 21:43:04 +00:00
|
|
|
// JSON doesn't like quotation marks around int and bool
|
|
|
|
// This is extremely ugly but it's still better than writing a tokeniser
|
2020-04-06 12:07:16 +00:00
|
|
|
if elem(key, unquoted) {
|
2018-12-17 22:12:38 +00:00
|
|
|
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
2018-10-07 17:09:45 +00:00
|
|
|
} else {
|
2018-12-17 22:12:38 +00:00
|
|
|
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
2018-10-07 17:09:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ret = ret[:len(ret)-1] // remove the last comma
|
|
|
|
ret = append(ret, '}')
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2020-04-09 21:11:12 +00:00
|
|
|
func ParseConfig(conf string) (raw *RawConfig, err error) {
|
2018-10-07 17:09:45 +00:00
|
|
|
var content []byte
|
2019-08-20 21:43:04 +00:00
|
|
|
// Checking if it's a path to json or a ssv string
|
2018-10-07 17:09:45 +00:00
|
|
|
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
|
|
|
content = ssvToJson(conf)
|
|
|
|
} else {
|
|
|
|
content, err = ioutil.ReadFile(conf)
|
|
|
|
if err != nil {
|
2020-04-06 12:07:16 +00:00
|
|
|
return
|
2018-10-07 17:09:45 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-06 12:07:16 +00:00
|
|
|
|
2020-04-09 21:11:12 +00:00
|
|
|
raw = new(RawConfig)
|
2020-04-06 12:07:16 +00:00
|
|
|
err = json.Unmarshal(content, &raw)
|
2018-10-07 17:09:45 +00:00
|
|
|
if err != nil {
|
2020-04-06 12:07:16 +00:00
|
|
|
return
|
2018-10-07 17:09:45 +00:00
|
|
|
}
|
2020-04-06 12:07:16 +00:00
|
|
|
return
|
|
|
|
}
|
2019-06-09 11:05:41 +00:00
|
|
|
|
2020-04-10 13:09:48 +00:00
|
|
|
func (raw *RawConfig) SplitConfigs(worldState common.WorldState) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
|
|
|
|
nullErr := func(field string) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
|
2020-04-06 12:07:16 +00:00
|
|
|
err = fmt.Errorf("%v cannot be empty", field)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-06 12:11:19 +00:00
|
|
|
auth.UID = raw.UID
|
|
|
|
auth.Unordered = raw.UDP
|
2020-04-06 12:07:16 +00:00
|
|
|
if raw.ServerName == "" {
|
|
|
|
return nullErr("ServerName")
|
|
|
|
}
|
|
|
|
auth.MockDomain = raw.ServerName
|
|
|
|
if raw.ProxyMethod == "" {
|
|
|
|
return nullErr("ServerName")
|
|
|
|
}
|
|
|
|
auth.ProxyMethod = raw.ProxyMethod
|
|
|
|
if len(raw.UID) == 0 {
|
|
|
|
return nullErr("UID")
|
|
|
|
}
|
|
|
|
|
|
|
|
// static public key
|
|
|
|
if len(raw.PublicKey) == 0 {
|
|
|
|
return nullErr("PublicKey")
|
|
|
|
}
|
|
|
|
pub, ok := ecdh.Unmarshal(raw.PublicKey)
|
|
|
|
if !ok {
|
|
|
|
err = fmt.Errorf("failed to unmarshal Public key")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
auth.ServerPubKey = pub
|
2020-04-09 21:11:12 +00:00
|
|
|
auth.WorldState = worldState
|
2020-04-06 12:07:16 +00:00
|
|
|
|
|
|
|
// Encryption method
|
|
|
|
switch strings.ToLower(raw.EncryptionMethod) {
|
2019-06-09 11:05:41 +00:00
|
|
|
case "plain":
|
2020-04-06 12:07:16 +00:00
|
|
|
auth.EncryptionMethod = mux.E_METHOD_PLAIN
|
2019-06-14 12:28:14 +00:00
|
|
|
case "aes-gcm":
|
2020-04-06 12:07:16 +00:00
|
|
|
auth.EncryptionMethod = mux.E_METHOD_AES_GCM
|
2019-06-14 10:26:26 +00:00
|
|
|
case "chacha20-poly1305":
|
2020-04-06 12:07:16 +00:00
|
|
|
auth.EncryptionMethod = mux.E_METHOD_CHACHA20_POLY1305
|
2019-06-09 11:05:41 +00:00
|
|
|
default:
|
2020-04-06 12:07:16 +00:00
|
|
|
err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod)
|
|
|
|
return
|
2019-06-09 11:05:41 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 12:07:16 +00:00
|
|
|
if raw.RemoteHost == "" {
|
|
|
|
return nullErr("RemoteHost")
|
|
|
|
}
|
|
|
|
if raw.RemotePort == "" {
|
|
|
|
return nullErr("RemotePort")
|
2019-08-02 15:02:25 +00:00
|
|
|
}
|
2020-04-06 12:07:16 +00:00
|
|
|
remote.RemoteAddr = net.JoinHostPort(raw.RemoteHost, raw.RemotePort)
|
2020-07-06 16:33:52 +00:00
|
|
|
if raw.NumConn <= 0 {
|
2020-10-15 20:32:38 +00:00
|
|
|
remote.NumConn = 1
|
|
|
|
remote.Singleplex = true
|
|
|
|
} else {
|
|
|
|
remote.NumConn = raw.NumConn
|
|
|
|
remote.Singleplex = false
|
2020-04-06 12:07:16 +00:00
|
|
|
}
|
2019-08-02 15:02:25 +00:00
|
|
|
|
2020-04-06 12:07:16 +00:00
|
|
|
// Transport and (if TLS mode), browser
|
|
|
|
switch strings.ToLower(raw.Transport) {
|
2019-09-02 13:03:10 +00:00
|
|
|
case "cdn":
|
2020-04-08 19:53:09 +00:00
|
|
|
remote.TransportMaker = func() Transport {
|
|
|
|
return &WSOverTLS{
|
|
|
|
cdnDomainPort: remote.RemoteAddr,
|
|
|
|
}
|
|
|
|
}
|
2020-04-06 12:07:16 +00:00
|
|
|
case "direct":
|
|
|
|
fallthrough
|
2019-08-31 17:01:39 +00:00
|
|
|
default:
|
2020-04-06 12:07:16 +00:00
|
|
|
var browser browser
|
|
|
|
switch strings.ToLower(raw.BrowserSig) {
|
|
|
|
case "firefox":
|
|
|
|
browser = &Firefox{}
|
|
|
|
case "chrome":
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
browser = &Chrome{}
|
|
|
|
}
|
2020-04-08 19:53:09 +00:00
|
|
|
remote.TransportMaker = func() Transport {
|
|
|
|
return &DirectTLS{
|
|
|
|
browser: browser,
|
|
|
|
}
|
|
|
|
}
|
2019-08-31 17:01:39 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 12:07:16 +00:00
|
|
|
// KeepAlive
|
|
|
|
if raw.KeepAlive <= 0 {
|
|
|
|
remote.KeepAlive = -1
|
2020-01-23 20:31:08 +00:00
|
|
|
} else {
|
2020-04-06 12:07:16 +00:00
|
|
|
remote.KeepAlive = remote.KeepAlive * time.Second
|
2020-01-23 20:31:08 +00:00
|
|
|
}
|
2019-06-09 11:05:41 +00:00
|
|
|
|
2020-04-06 12:07:16 +00:00
|
|
|
if raw.LocalHost == "" {
|
|
|
|
return nullErr("LocalHost")
|
|
|
|
}
|
|
|
|
if raw.LocalPort == "" {
|
|
|
|
return nullErr("LocalPort")
|
|
|
|
}
|
|
|
|
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
|
2020-02-12 06:56:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 12:07:16 +00:00
|
|
|
return
|
2018-10-14 19:32:54 +00:00
|
|
|
}
|