package controller import ( "fmt" plugin "github.com/fatedier/frp/pkg/plugin/server" "github.com/gin-gonic/gin" "gopkg.in/ini.v1" "log" "net/http" "os" "path/filepath" "strconv" "strings" ) type HandleController struct { CommonInfo CommonInfo Tokens map[string]TokenInfo Ports map[string][]string Domains map[string][]string Subdomains map[string][]string ConfigFile string IniFile *ini.File Version string } func NewHandleController(config *HandleController) *HandleController { return config } func (c *HandleController) Register(rootDir string, engine *gin.Engine) { assets := filepath.Join(rootDir, "assets") _, err := os.Stat(assets) if err != nil && !os.IsExist(err) { assets = "./assets" } engine.Delims("${", "}") engine.LoadHTMLGlob(filepath.Join(assets, "templates/*")) engine.POST("/handler", c.MakeHandlerFunc()) engine.Static("/static", filepath.Join(assets, "static")) engine.GET("/login", c.MakeLoginFunc()) engine.GET("/lang.json", c.MakeLangFunc()) var group *gin.RouterGroup if len(c.CommonInfo.User) != 0 { //group = engine.Group("/", gin.BasicAuthForRealm(gin.Accounts{ // c.CommonInfo.User: c.CommonInfo.Pwd, //}, "Restricted")) group = engine.Group("/", c.BasicAuth()) } else { group = engine.Group("/") } group.POST("/login", c.MakeLoginFunc()) group.GET("/", c.MakeIndexFunc()) group.GET("/tokens", c.MakeQueryTokensFunc()) group.POST("/add", c.MakeAddTokenFunc()) group.POST("/update", c.MakeUpdateTokensFunc()) group.POST("/remove", c.MakeRemoveTokensFunc()) group.POST("/disable", c.MakeDisableTokensFunc()) group.POST("/enable", c.MakeEnableTokensFunc()) group.GET("/proxy/*serverApi", c.MakeProxyFunc()) } func (c *HandleController) BasicAuth() gin.HandlerFunc { return func(context *gin.Context) { username, password, _ := context.Request.BasicAuth() usernameMatch := username == c.CommonInfo.User passwordMatch := password == c.CommonInfo.Pwd if usernameMatch && passwordMatch { context.Next() return } if context.Request.RequestURI == "/" { context.Header("WWW-Authenticate", `Basic realm="Restricted", charset="UTF-8"`) context.AbortWithStatus(http.StatusUnauthorized) } else { context.Redirect(http.StatusTemporaryRedirect, "/login") } } } func (c *HandleController) HandleLogin(content *plugin.LoginContent) plugin.Response { token := content.Metas["token"] user := content.User return c.JudgeToken(user, token) } func (c *HandleController) HandleNewProxy(content *plugin.NewProxyContent) plugin.Response { token := content.User.Metas["token"] user := content.User.User judgeToken := c.JudgeToken(user, token) if judgeToken.Reject { return judgeToken } return c.JudgePort(content) } func (c *HandleController) HandlePing(content *plugin.PingContent) plugin.Response { token := content.User.Metas["token"] user := content.User.User return c.JudgeToken(user, token) } func (c *HandleController) HandleNewWorkConn(content *plugin.NewWorkConnContent) plugin.Response { token := content.User.Metas["token"] user := content.User.User return c.JudgeToken(user, token) } func (c *HandleController) HandleNewUserConn(content *plugin.NewUserConnContent) plugin.Response { token := content.User.Metas["token"] user := content.User.User return c.JudgeToken(user, token) } func (c *HandleController) JudgeToken(user string, token string) plugin.Response { var res plugin.Response if len(c.Tokens) == 0 { res.Unchange = true } else if user == "" || token == "" { res.Reject = true res.RejectReason = "user or meta token can not be empty" } else if info, exist := c.Tokens[user]; exist { if !info.Status { res.Reject = true res.RejectReason = fmt.Sprintf("user [%s] is disabled", user) } else { if info.Token != token { res.Reject = true res.RejectReason = fmt.Sprintf("invalid meta token for user [%s]", user) } else { res.Unchange = true } } } else { res.Reject = true res.RejectReason = fmt.Sprintf("user [%s] not exist", user) } return res } func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Response { var res plugin.Response var portErr error var reject = false supportProxyTypes := []string{ "tcp", "tcpmux", "udp", "http", "https", } proxyType := content.ProxyType if StringIndexOf(proxyType, supportProxyTypes) == -1 { log.Printf("proxy type [%v] not support, plugin do nothing", proxyType) res.Unchange = true return res } user := content.User.User userPort := content.RemotePort userDomains := content.CustomDomains userSubdomain := content.SubDomain portAllowed := true if proxyType == "tcp" || proxyType == "udp" { portAllowed = false if _, exist := c.Ports[user]; exist { for _, port := range c.Ports[user] { if strings.Contains(port, "-") { allowedRanges := strings.Split(port, "-") if len(allowedRanges) != 2 { portErr = fmt.Errorf("user [%v] port range [%v] format error", user, port) break } start, err := strconv.Atoi(strings.TrimSpace(allowedRanges[0])) if err != nil { portErr = fmt.Errorf("user [%v] port rang [%v] start port [%v] is not a number", user, port, allowedRanges[0]) break } end, err := strconv.Atoi(strings.TrimSpace(allowedRanges[1])) if err != nil { portErr = fmt.Errorf("user [%v] port rang [%v] end port [%v] is not a number", user, port, allowedRanges[0]) break } if max(userPort, start) == userPort && min(userPort, end) == userPort { portAllowed = true break } } else { allowed, err := strconv.Atoi(port) if err != nil { portErr = fmt.Errorf("user [%v] allowed port [%v] is not a number", user, port) } if allowed == userPort { portAllowed = true break } } } } else { portAllowed = true } } if !portAllowed { if portErr == nil { portErr = fmt.Errorf("user [%v] port [%v] is not allowed", user, userPort) } reject = true } domainAllowed := true if proxyType == "http" || proxyType == "https" || proxyType == "tcpmux" { if portAllowed { if _, exist := c.Domains[user]; exist { for _, userDomain := range userDomains { if StringIndexOf(userDomain, c.Domains[user]) == -1 { domainAllowed = false break } } } if !domainAllowed { portErr = fmt.Errorf("user [%v] domain [%v] is not allowed", user, strings.Join(userDomains, ",")) reject = true } } } subdomainAllowed := true if proxyType == "http" || proxyType == "https" { subdomainAllowed = false if portAllowed && domainAllowed { if _, exist := c.Subdomains[user]; exist { for _, subdomain := range c.Subdomains[user] { if subdomain == userSubdomain { subdomainAllowed = true break } } } else { subdomainAllowed = true } if !subdomainAllowed { portErr = fmt.Errorf("user [%v] subdomain [%v] is not allowed", user, userSubdomain) reject = true } } } if reject { res.Reject = true res.RejectReason = portErr.Error() } else { res.Unchange = true } return res } func StringIndexOf(element string, data []string) int { for k, v := range data { if element == v { return k } } return -1 }