diff --git a/conf/frpc.ini b/conf/frpc.ini index 38101d1..3e4d36f 100644 --- a/conf/frpc.ini +++ b/conf/frpc.ini @@ -9,6 +9,8 @@ log_level = info log_max_days = 3 # for authentication auth_token = 123 +# for privilege mode +privilege_key = 12345678 # ssh is the proxy name same as server's configuration [ssh] @@ -32,3 +34,21 @@ use_gzip = true type = http local_ip = 127.0.0.1 local_port = 8000 + +[privilege_ssh] +# if privilege_mode is enabled, this proxy will be created automatically +privilege_mode = true +type = tcp +local_ip = 127.0.0.1 +local_port = 22 +use_encryption = true +use_gzip = false +remote_port = 6001 + +[privilege_web] +privilege_mode = true +type = http +local_ip = 127.0.0.1 +local_port = 80 +use_gzip = true +custom_domains = web03.yourdomain.com diff --git a/conf/frps.ini b/conf/frps.ini index b4307e5..5c96939 100644 --- a/conf/frps.ini +++ b/conf/frps.ini @@ -12,6 +12,9 @@ log_file = ./frps.log # debug, info, warn, error log_level = info log_max_days = 3 +# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_key is correct +privilege_mode = true +privilege_key = 12345678 # ssh is the proxy name, client will use this name and auth_token to connect to server [ssh] diff --git a/src/frp/cmd/frpc/control.go b/src/frp/cmd/frpc/control.go index 57ec52f..fe22fc5 100644 --- a/src/frp/cmd/frpc/control.go +++ b/src/frp/cmd/frpc/control.go @@ -144,8 +144,15 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { AuthKey: authKey, UseEncryption: cli.UseEncryption, UseGzip: cli.UseGzip, + PrivilegeMode: cli.PrivilegeMode, + ProxyType: cli.Type, Timestamp: nowTime, } + if cli.PrivilegeMode { + req.RemotePort = cli.RemotePort + req.CustomDomains = cli.CustomDomains + } + buf, _ := json.Marshal(req) err = c.Write(string(buf) + "\n") if err != nil { diff --git a/src/frp/cmd/frps/control.go b/src/frp/cmd/frps/control.go index 489b286..53a976a 100644 --- a/src/frp/cmd/frps/control.go +++ b/src/frp/cmd/frps/control.go @@ -194,30 +194,68 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{} // if success, ret equals 0, otherwise greater than 0 func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { ret = 1 - // check if proxy name exist - s, ok := server.ProxyServers[req.ProxyName] - if !ok { - info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) - log.Warn(info) + if req.PrivilegeMode && !server.PrivilegeMode { + info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName) + log.Warn("info") return } - // check authKey + var ( + s *server.ProxyServer + ok bool + ) + s, ok = server.ProxyServers[req.ProxyName] + if req.PrivilegeMode && req.Type == consts.NewCtlConn { + log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName) + } else { + if !ok { + info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) + log.Warn(info) + return + } + } + + // check authKey or privilegeKey nowTime := time.Now().Unix() - authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp)) - // authKey avaiable in 15 minutes - if nowTime-req.Timestamp > 15*60 { - info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName) - log.Warn(info) - return - } else if req.AuthKey != authKey { - info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName) - log.Warn(info) - return + if req.PrivilegeMode { + privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeKey + fmt.Sprintf("%d", req.Timestamp)) + // privilegeKey avaiable in 15 minutes + if nowTime-req.Timestamp > 15*60 { + info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName) + log.Warn(info) + return + } else if req.AuthKey != privilegeKey { + log.Debug("%s %s", req.AuthKey, privilegeKey) + info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName) + log.Warn(info) + return + } + } else { + authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp)) + // authKey avaiable in 15 minutes + if nowTime-req.Timestamp > 15*60 { + info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName) + log.Warn(info) + return + } else if req.AuthKey != authKey { + info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName) + log.Warn(info) + return + } } // control conn if req.Type == consts.NewCtlConn { + if req.PrivilegeMode { + s = server.NewProxyServerFromCtlMsg(req) + err := server.CreateProxy(s) + if err != nil { + info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err) + log.Warn(info) + return + } + } + if s.Status == consts.Working { info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) log.Warn(info) @@ -248,6 +286,9 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { return } log.Info("ProxyName [%s], start proxy success", req.ProxyName) + if req.PrivilegeMode { + log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName) + } } else if req.Type == consts.NewWorkConn { // work conn if s.Status != consts.Working { diff --git a/src/frp/cmd/frps/main.go b/src/frp/cmd/frps/main.go index de7f7be..a9d3f7e 100644 --- a/src/frp/cmd/frps/main.go +++ b/src/frp/cmd/frps/main.go @@ -172,5 +172,8 @@ func main() { } log.Info("Start frps success") + if server.PrivilegeMode == true { + log.Info("PrivilegeMode is enabled, you should pay more attention to security issues") + } ProcessControlConn(l) } diff --git a/src/frp/models/client/client.go b/src/frp/models/client/client.go index 95c5931..d59c5ec 100644 --- a/src/frp/models/client/client.go +++ b/src/frp/models/client/client.go @@ -31,6 +31,9 @@ type ProxyClient struct { config.BaseConf LocalIp string LocalPort int64 + + RemotePort int64 + CustomDomains []string } func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { @@ -57,10 +60,11 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err nowTime := time.Now().Unix() authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime)) req := &msg.ControlReq{ - Type: consts.NewWorkConn, - ProxyName: p.Name, - AuthKey: authKey, - Timestamp: nowTime, + Type: consts.NewWorkConn, + ProxyName: p.Name, + AuthKey: authKey, + PrivilegeMode: p.PrivilegeMode, + Timestamp: nowTime, } buf, _ := json.Marshal(req) diff --git a/src/frp/models/client/config.go b/src/frp/models/client/config.go index 132daf6..accdf1e 100644 --- a/src/frp/models/client/config.go +++ b/src/frp/models/client/config.go @@ -17,6 +17,7 @@ package client import ( "fmt" "strconv" + "strings" ini "github.com/vaughan0/go-ini" ) @@ -29,6 +30,7 @@ var ( LogWay string = "console" LogLevel string = "info" LogMaxDays int64 = 3 + PrivilegeKey string = "" HeartBeatInterval int64 = 20 HeartBeatTimeout int64 = 90 ) @@ -75,12 +77,15 @@ func LoadConf(confFile string) (err error) { LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64) } + tmpStr, ok = conf.Get("common", "privilege_key") + if ok { + PrivilegeKey = tmpStr + } + var authToken string tmpStr, ok = conf.Get("common", "auth_token") if ok { authToken = tmpStr - } else { - return fmt.Errorf("auth_token not found") } // proxies @@ -90,9 +95,6 @@ func LoadConf(confFile string) (err error) { // name proxyClient.Name = name - // auth_token - proxyClient.AuthToken = authToken - // local_ip proxyClient.LocalIp, ok = section["local_ip"] if !ok { @@ -101,46 +103,101 @@ func LoadConf(confFile string) (err error) { } // local_port - portStr, ok := section["local_port"] + tmpStr, ok = section["local_port"] if ok { - proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) + proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64) if err != nil { - return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name) + return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name) } } else { - return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name) + return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name) } // type proxyClient.Type = "tcp" - typeStr, ok := section["type"] + tmpStr, ok = section["type"] if ok { - if typeStr != "tcp" && typeStr != "http" && typeStr != "https" { - return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name) + if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" { + return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name) } - proxyClient.Type = typeStr + proxyClient.Type = tmpStr } // use_encryption proxyClient.UseEncryption = false - useEncryptionStr, ok := section["use_encryption"] - if ok && useEncryptionStr == "true" { + tmpStr, ok = section["use_encryption"] + if ok && tmpStr == "true" { proxyClient.UseEncryption = true } // use_gzip proxyClient.UseGzip = false - useGzipStr, ok := section["use_gzip"] - if ok && useGzipStr == "true" { + tmpStr, ok = section["use_gzip"] + if ok && tmpStr == "true" { proxyClient.UseGzip = true } + // privilege_mode + proxyClient.PrivilegeMode = false + tmpStr, ok = section["privilege_mode"] + if ok && tmpStr == "true" { + proxyClient.PrivilegeMode = true + } + + // configures used in privilege mode + if proxyClient.PrivilegeMode == true { + // auth_token + proxyClient.AuthToken = PrivilegeKey + + if proxyClient.Type == "tcp" { + // remote_port + tmpStr, ok = section["remote_port"] + if ok { + proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name) + } + } else { + return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name) + } + } else if proxyClient.Type == "http" { + domainStr, ok := section["custom_domains"] + if ok { + proxyClient.CustomDomains = strings.Split(domainStr, ",") + if len(proxyClient.CustomDomains) == 0 { + return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name) + } + for i, domain := range proxyClient.CustomDomains { + proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + } + } else { + return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name) + } + } else if proxyClient.Type == "https" { + domainStr, ok := section["custom_domains"] + if ok { + proxyClient.CustomDomains = strings.Split(domainStr, ",") + if len(proxyClient.CustomDomains) == 0 { + return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyClient.Name) + } + for i, domain := range proxyClient.CustomDomains { + proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + } + } else { + return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name) + } + } + } else /* proxyClient.PrivilegeMode == false */ { + // authToken + proxyClient.AuthToken = authToken + } + ProxyClients[proxyClient.Name] = proxyClient } } if len(ProxyClients) == 0 { - return fmt.Errorf("Parse ini file error: no proxy config found") + return fmt.Errorf("Parse conf error: no proxy config found") } return nil diff --git a/src/frp/models/config/config.go b/src/frp/models/config/config.go index 3ef4889..1e281ca 100644 --- a/src/frp/models/config/config.go +++ b/src/frp/models/config/config.go @@ -20,4 +20,5 @@ type BaseConf struct { Type string UseEncryption bool UseGzip bool + PrivilegeMode bool } diff --git a/src/frp/models/msg/msg.go b/src/frp/models/msg/msg.go index e3ea2b2..d659007 100644 --- a/src/frp/models/msg/msg.go +++ b/src/frp/models/msg/msg.go @@ -22,11 +22,17 @@ type GeneralRes struct { // messages between control connections of frpc and frps type ControlReq struct { Type int64 `json:"type"` - ProxyName string `json:"proxy_name,omitempty"` - AuthKey string `json:"auth_key, omitempty"` - UseEncryption bool `json:"use_encryption, omitempty"` - UseGzip bool `json:"use_gzip, omitempty"` - Timestamp int64 `json:"timestamp, omitempty"` + ProxyName string `json:"proxy_name"` + AuthKey string `json:"auth_key"` + UseEncryption bool `json:"use_encryption"` + UseGzip bool `json:"use_gzip"` + + // configures used if privilege_mode is enabled + PrivilegeMode bool `json:"privilege_mode"` + ProxyType string `json:"proxy_type"` + RemotePort int64 `json:"remote_port"` + CustomDomains []string `json:"custom_domains, omitempty"` + Timestamp int64 `json:"timestamp"` } type ControlRes struct { diff --git a/src/frp/models/msg/process.go b/src/frp/models/msg/process.go index b845269..19a1d40 100644 --- a/src/frp/models/msg/process.go +++ b/src/frp/models/msg/process.go @@ -141,7 +141,6 @@ func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) { } // gzip if conf.UseGzip { - log.Warn("%x", res) res, err = laes.Decompression(res) if err != nil { log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err) diff --git a/src/frp/models/server/config.go b/src/frp/models/server/config.go index b426018..d586cd0 100644 --- a/src/frp/models/server/config.go +++ b/src/frp/models/server/config.go @@ -22,6 +22,7 @@ import ( ini "github.com/vaughan0/go-ini" + "frp/models/consts" "frp/utils/log" "frp/utils/vhost" ) @@ -38,6 +39,8 @@ var ( LogWay string = "console" // console or file LogLevel string = "info" LogMaxDays int64 = 3 + PrivilegeMode bool = false + PrivilegeKey string = "" HeartBeatTimeout int64 = 90 UserConnTimeout int64 = 10 @@ -132,6 +135,22 @@ func loadCommonConf(confFile string) error { LogMaxDays = v } } + + tmpStr, ok = conf.Get("common", "privilege_mode") + if ok { + if tmpStr == "true" { + PrivilegeMode = true + } + } + + if PrivilegeMode == true { + tmpStr, ok = conf.Get("common", "privilege_key") + if ok { + PrivilegeKey = tmpStr + } else { + return fmt.Errorf("Parse conf error: privilege_key must be set if privilege_mode is enabled") + } + } return nil } @@ -189,6 +208,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e for i, domain := range proxyServer.CustomDomains { proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) } + } else { + return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name) } } else if proxyServer.Type == "https" { // for https @@ -201,6 +222,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e for i, domain := range proxyServer.CustomDomains { proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) } + } else { + return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyServer.Name) } } proxyServers[proxyServer.Name] = proxyServer @@ -234,14 +257,37 @@ func ReloadConf(confFile string) (err error) { } } + // proxies created by PrivilegeMode won't be deleted for name, oldProxyServer := range ProxyServers { _, ok := loadProxyServers[name] if !ok { - oldProxyServer.Close() - delete(ProxyServers, name) - log.Info("ProxyName [%s] deleted, close it", name) + if !oldProxyServer.PrivilegeMode { + oldProxyServer.Close() + delete(ProxyServers, name) + log.Info("ProxyName [%s] deleted, close it", name) + } else { + log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name) + } } } ProxyServersMutex.Unlock() return nil } + +func CreateProxy(s *ProxyServer) error { + ProxyServersMutex.Lock() + defer ProxyServersMutex.Unlock() + oldServer, ok := ProxyServers[s.Name] + if ok { + if oldServer.Status == consts.Working { + return fmt.Errorf("this proxy is already working now") + } + oldServer.Close() + if oldServer.PrivilegeMode { + delete(ProxyServers, s.Name) + } + } + s.Init() + ProxyServers[s.Name] = s + return nil +} diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index 65154af..9e14e03 100644 --- a/src/frp/models/server/server.go +++ b/src/frp/models/server/server.go @@ -52,6 +52,20 @@ func NewProxyServer() (p *ProxyServer) { return p } +func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) { + p = &ProxyServer{} + p.Name = req.ProxyName + p.Type = req.ProxyType + p.UseEncryption = req.UseEncryption + p.UseGzip = req.UseGzip + p.PrivilegeMode = req.PrivilegeMode + p.BindAddr = BindAddr + p.ListenPort = req.RemotePort + p.CustomDomains = req.CustomDomains + p.AuthToken = PrivilegeKey + return +} + func (p *ProxyServer) Init() { p.Lock() p.Status = consts.Idle @@ -161,11 +175,9 @@ func (p *ProxyServer) Close() { p.Lock() if p.Status != consts.Closed { p.Status = consts.Closed - if len(p.listeners) != 0 { - for _, l := range p.listeners { - if l != nil { - l.Close() - } + for _, l := range p.listeners { + if l != nil { + l.Close() } } close(p.ctlMsgChan) diff --git a/src/frp/utils/version/version.go b/src/frp/utils/version/version.go index 842028d..9559803 100644 --- a/src/frp/utils/version/version.go +++ b/src/frp/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.6.0" +var version string = "0.7.0" func Full() string { return version