Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/frpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ import (

func main() {
system.EnableCompatibilityMode()
sub.Execute()
system.Run("FrpClient", sub.Execute)
}
263 changes: 263 additions & 0 deletions cmd/frpc/sub/install_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Copyright 2021 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sub

import (
"errors"
"fmt"
"github.com/fatedier/frp/pkg/policy/security"
"github.com/fatedier/frp/pkg/util/log/events"
"github.com/fatedier/frp/pkg/util/system"
"github.com/fatedier/frp/pkg/util/version"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
"io/fs"
"os"
"path/filepath"
"strings"
"syscall"

"github.com/spf13/cobra"

"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
)

var (
verifyInstallation = false
restricted = false
)

const fmtServiceDesc = "Frp is a fast reverse proxy that allows you to expose a local server located behind a NAT or firewall to the Internet. This service is %s."

func init() {
installCmd.PersistentFlags().BoolVarP(&verifyInstallation, "verify", "", false, "verify config(s) before installation")
installCmd.PersistentFlags().BoolVarP(&restricted, "restricted", "", false, "run service in restricted context")

installCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ClientUnsafeFeatures, ", ")))

rootCmd.AddCommand(installCmd)
rootCmd.AddCommand(uninstallCmd)
}

var installCmd = &cobra.Command{
Use: "install",
Short: "Install this executable as a Windows service or update the existing service (run as privileged user)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}

execPath, err := os.Executable()
if err != nil {
fmt.Println("frpc: the executable no longer exists")
os.Exit(1)
}
stat, err := os.Stat(execPath)
if err != nil || stat.IsDir() {
fmt.Println("frpc: the executable is no longer valid")
os.Exit(1)
}

// Ignore other params if "--config-dir" specified
if cfgDir != "" {
if verifyInstallation {
var hasValidCfg = false
err := filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return os.ErrNotExist
}
if d.IsDir() {
return nil
}
cfgFile1 := cfgDir + "\\" + d.Name()
if verifyCfg(cfgFile1) == nil {
fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile1)
hasValidCfg = true
}
return nil
})
if !hasValidCfg {
err = errors.New("no valid configuration file found")
}
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
err := installService(execPath, "--config-dir", cfgDir)
if err != nil {
os.Exit(1)
}
return nil
}

// Ignore other params if "-c" / "--config" specified
if cfgFile != "" {
if verifyInstallation {
err := verifyCfg(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile)
}
err := installService(execPath, "--config", cfgFile)
if err != nil {
os.Exit(1)
}
return nil
}

err = installService(execPath, args...)
if err != nil {
os.Exit(1)
}

return nil
},
}

var uninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Remove the Windows service installed for this executable (run as privileged user)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}

err := uninstallService()
if err != nil {
os.Exit(1)
}

return nil
},
}

func verifyCfg(f string) error {
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(f, strictConfigMode)
if err != nil {
return err
}
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
warning, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs, unsafeFeatures)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
return err
}
return nil
}

func installService(exec string, args ...string) error {
scm, err := mgr.Connect()
if err != nil {
fmt.Println("frpc: Failed connect to SCM (Permission Denied)")
return err
}
defer func() {
_ = scm.Disconnect()
}()

// Check and modify existing service
if modifyService(scm, exec, args...) == nil {
return nil
}
// Create new service
var objName string
var sidType uint32 = windows.SERVICE_SID_TYPE_UNRESTRICTED
if restricted {
objName = "NT AUTHORITY\\LocalService"
sidType = windows.SERVICE_SID_TYPE_RESTRICTED
}
_, err = scm.CreateService("frpc", exec, mgr.Config{
ErrorControl: mgr.ErrorNormal,
TagId: 0,
Dependencies: []string{"Tcpip"},
ServiceStartName: objName,
DisplayName: system.ServiceName,
Description: fmt.Sprintf(fmtServiceDesc, system.ServiceName),
SidType: sidType,
DelayedAutoStart: false,
}, args...)
if err != nil {
return err
}
_ = events.CreateEventSource(system.ServiceName)
fmt.Println("Service successfully installed.")
return nil
}

func modifyService(scm *mgr.Mgr, exec string, args ...string) error {
service, err := scm.OpenService("frpc")
if err != nil {
return err
}
defer func(service *mgr.Service) {
_ = service.Close()
}(service)
serviceConfig, err := service.Config()
if err != nil {
return err
}
s := syscall.EscapeArg(exec)
for _, v := range args {
s += " " + syscall.EscapeArg(v)
}
serviceConfig.BinaryPathName = s
var objName string
var sidType uint32 = windows.SERVICE_SID_TYPE_UNRESTRICTED
if restricted {
objName = "NT AUTHORITY\\LocalService"
sidType = windows.SERVICE_SID_TYPE_RESTRICTED
}
serviceConfig.ServiceStartName = objName
serviceConfig.SidType = sidType
err = service.UpdateConfig(serviceConfig)
if err != nil {
return err
}
return nil
}

func uninstallService() error {
scm, err := mgr.Connect()
if err != nil {
return err
}
defer func() {
_ = scm.Disconnect()
}()

service, err := scm.OpenService("frpc")
if err != nil {
return err
}
defer func(service *mgr.Service) {
_ = service.Close()
}(service)
err = service.Delete()
if err != nil {
return err
}
_ = events.DeleteEventSource(system.ServiceName)
fmt.Println("Service successfully removed.")
return nil
}
22 changes: 21 additions & 1 deletion cmd/frpc/sub/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package sub
import (
"context"
"fmt"
"github.com/fatedier/frp/pkg/util/system"
"io/fs"
"os"
"os/signal"
Expand Down Expand Up @@ -47,7 +48,7 @@ var (
)

func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frpc")
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
Expand Down Expand Up @@ -154,6 +155,9 @@ func startService(
cfgFile string,
) error {
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
defer func() {
_ = log.DestroyEventWriter()
}()

if cfgFile != "" {
log.Infof("start frpc service for config file [%s]", cfgFile)
Expand All @@ -170,6 +174,22 @@ func startService(
return err
}

// Setup system.PauseF and system.ContinueF
system.PauseF = func() error {
return svr.UpdateAllConfigurer([]v1.ProxyConfigurer{}, []v1.VisitorConfigurer{})
}
system.ContinueF = func() error {
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
if err != nil {
return err
}
_, err = validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs, unsafeFeatures)
if err != nil {
return err
}
return svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs)
}

shouldGracefulClose := cfg.Transport.Protocol == "kcp" || cfg.Transport.Protocol == "quic"
// Capture the exit signal if we use kcp or quic.
if shouldGracefulClose {
Expand Down
Loading