github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/Server/main.go (about)

     1  /*
     2   * Copyright (c) 2016, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"encoding/json"
    24  	"flag"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"strconv"
    30  	"strings"
    31  	"syscall"
    32  	"time"
    33  
    34  	"github.com/Psiphon-Inc/rotate-safe-writer"
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    36  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo"
    37  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server"
    38  	"github.com/mitchellh/panicwrap"
    39  )
    40  
    41  var loadedConfigJSON []byte
    42  
    43  func main() {
    44  
    45  	var configFilename string
    46  	var generateServerIPaddress string
    47  	var generateServerNetworkInterface string
    48  	var generateProtocolPorts stringListFlag
    49  	var generateWebServerPort int
    50  	var generateLogFilename string
    51  	var generateTrafficRulesConfigFilename string
    52  	var generateOSLConfigFilename string
    53  	var generateTacticsConfigFilename string
    54  	var generateServerEntryFilename string
    55  
    56  	flag.StringVar(
    57  		&configFilename,
    58  		"config",
    59  		server.SERVER_CONFIG_FILENAME,
    60  		"run or generate with this config `filename`")
    61  
    62  	flag.StringVar(
    63  		&generateServerIPaddress,
    64  		"ipaddress",
    65  		server.DEFAULT_SERVER_IP_ADDRESS,
    66  		"generate with this server `IP address`")
    67  
    68  	flag.StringVar(
    69  		&generateServerNetworkInterface,
    70  		"interface",
    71  		"",
    72  		"generate with server IP address from this `network-interface`")
    73  
    74  	flag.Var(
    75  		&generateProtocolPorts,
    76  		"protocol",
    77  		"generate with `protocol:port`; flag may be repeated to enable multiple protocols")
    78  
    79  	flag.IntVar(
    80  		&generateWebServerPort,
    81  		"web",
    82  		0,
    83  		"generate with web server `port`; 0 for no web server")
    84  
    85  	flag.StringVar(
    86  		&generateLogFilename,
    87  		"logFilename",
    88  		"",
    89  		"set application log file name and path; blank for stderr")
    90  
    91  	flag.StringVar(
    92  		&generateTrafficRulesConfigFilename,
    93  		"trafficRules",
    94  		server.SERVER_TRAFFIC_RULES_CONFIG_FILENAME,
    95  		"generate with this traffic rules config `filename`")
    96  
    97  	flag.StringVar(
    98  		&generateOSLConfigFilename,
    99  		"osl",
   100  		server.SERVER_OSL_CONFIG_FILENAME,
   101  		"generate with this OSL config `filename`")
   102  
   103  	flag.StringVar(
   104  		&generateTacticsConfigFilename,
   105  		"tactics",
   106  		server.SERVER_TACTICS_CONFIG_FILENAME,
   107  		"generate with this tactics config `filename`")
   108  
   109  	flag.StringVar(
   110  		&generateServerEntryFilename,
   111  		"serverEntry",
   112  		server.SERVER_ENTRY_FILENAME,
   113  		"generate with this server entry `filename`")
   114  
   115  	flag.Usage = func() {
   116  		fmt.Fprintf(os.Stderr,
   117  			"Usage:\n\n"+
   118  				"%s <flags> generate    generates configuration files\n"+
   119  				"%s <flags> run         runs configured services\n\n",
   120  			os.Args[0], os.Args[0])
   121  		flag.PrintDefaults()
   122  	}
   123  
   124  	flag.Parse()
   125  
   126  	args := flag.Args()
   127  
   128  	if len(args) < 1 {
   129  		flag.Usage()
   130  		os.Exit(1)
   131  	} else if args[0] == "generate" {
   132  
   133  		serverIPaddress := generateServerIPaddress
   134  
   135  		if generateServerNetworkInterface != "" {
   136  			// TODO: IPv6 support
   137  			serverIPv4Address, _, err := common.GetInterfaceIPAddresses(generateServerNetworkInterface)
   138  			if err == nil && serverIPv4Address == nil {
   139  				err = fmt.Errorf("no IPv4 address for interface %s", generateServerNetworkInterface)
   140  			}
   141  			if err != nil {
   142  				fmt.Printf("generate failed: %s\n", err)
   143  				os.Exit(1)
   144  			}
   145  			serverIPaddress = serverIPv4Address.String()
   146  		}
   147  
   148  		tunnelProtocolPorts := make(map[string]int)
   149  
   150  		for _, protocolPort := range generateProtocolPorts {
   151  			parts := strings.Split(protocolPort, ":")
   152  			if len(parts) == 2 {
   153  				port, err := strconv.Atoi(parts[1])
   154  				if err != nil {
   155  					fmt.Printf("generate failed: %s\n", err)
   156  					os.Exit(1)
   157  				}
   158  				tunnelProtocolPorts[parts[0]] = port
   159  			}
   160  		}
   161  
   162  		configJSON, trafficRulesConfigJSON, OSLConfigJSON,
   163  			tacticsConfigJSON, encodedServerEntry, err :=
   164  			server.GenerateConfig(
   165  				&server.GenerateConfigParams{
   166  					LogFilename:                generateLogFilename,
   167  					ServerIPAddress:            serverIPaddress,
   168  					EnableSSHAPIRequests:       true,
   169  					WebServerPort:              generateWebServerPort,
   170  					TunnelProtocolPorts:        tunnelProtocolPorts,
   171  					TrafficRulesConfigFilename: generateTrafficRulesConfigFilename,
   172  					OSLConfigFilename:          generateOSLConfigFilename,
   173  					TacticsConfigFilename:      generateTacticsConfigFilename,
   174  				})
   175  		if err != nil {
   176  			fmt.Printf("generate failed: %s\n", err)
   177  			os.Exit(1)
   178  		}
   179  
   180  		err = ioutil.WriteFile(configFilename, configJSON, 0600)
   181  		if err != nil {
   182  			fmt.Printf("error writing configuration file: %s\n", err)
   183  			os.Exit(1)
   184  		}
   185  
   186  		err = ioutil.WriteFile(generateTrafficRulesConfigFilename, trafficRulesConfigJSON, 0600)
   187  		if err != nil {
   188  			fmt.Printf("error writing traffic rule config file: %s\n", err)
   189  			os.Exit(1)
   190  		}
   191  
   192  		err = ioutil.WriteFile(generateOSLConfigFilename, OSLConfigJSON, 0600)
   193  		if err != nil {
   194  			fmt.Printf("error writing OSL config file: %s\n", err)
   195  			os.Exit(1)
   196  		}
   197  
   198  		err = ioutil.WriteFile(generateTacticsConfigFilename, tacticsConfigJSON, 0600)
   199  		if err != nil {
   200  			fmt.Printf("error writing tactics config file: %s\n", err)
   201  			os.Exit(1)
   202  		}
   203  
   204  		err = ioutil.WriteFile(generateServerEntryFilename, encodedServerEntry, 0600)
   205  		if err != nil {
   206  			fmt.Printf("error writing server entry file: %s\n", err)
   207  			os.Exit(1)
   208  		}
   209  
   210  	} else if args[0] == "run" {
   211  
   212  		configJSON, err := ioutil.ReadFile(configFilename)
   213  		if err != nil {
   214  			fmt.Printf("error loading configuration file: %s\n", err)
   215  			os.Exit(1)
   216  		}
   217  
   218  		loadedConfigJSON = configJSON
   219  
   220  		// The initial call to panicwrap.Wrap will spawn a child process
   221  		// running the same program.
   222  		//
   223  		// The parent process waits for the child to terminate and
   224  		// panicHandler logs any panics from the child.
   225  		//
   226  		// The child will return immediately from Wrap without spawning
   227  		// and fall through to server.RunServices.
   228  
   229  		// Unhandled panic wrapper. Logs it, then re-executes the current executable
   230  		exitStatus, err := panicwrap.Wrap(&panicwrap.WrapConfig{
   231  			Handler:        panicHandler,
   232  			ForwardSignals: []os.Signal{os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTSTP, syscall.SIGCONT},
   233  		})
   234  		if err != nil {
   235  			fmt.Printf("failed to set up the panic wrapper: %s\n", err)
   236  			os.Exit(1)
   237  		}
   238  
   239  		// Note: panicwrap.Wrap documentation states that exitStatus == -1
   240  		// should be used to determine whether the process is the child.
   241  		// However, we have found that this exitStatus is returned even when
   242  		// the process is the parent. Likely due to panicwrap returning
   243  		// syscall.WaitStatus.ExitStatus() as the exitStatus, which _can_ be
   244  		// -1. Checking panicwrap.Wrapped(nil) is more reliable.
   245  
   246  		if !panicwrap.Wrapped(nil) {
   247  			os.Exit(exitStatus)
   248  		}
   249  		// Else, this is the child process.
   250  
   251  		err = server.RunServices(configJSON)
   252  		if err != nil {
   253  			fmt.Printf("run failed: %s\n", err)
   254  			os.Exit(1)
   255  		}
   256  	}
   257  }
   258  
   259  type stringListFlag []string
   260  
   261  func (list *stringListFlag) String() string {
   262  	return strings.Join(*list, ", ")
   263  }
   264  
   265  func (list *stringListFlag) Set(flagValue string) error {
   266  	*list = append(*list, flagValue)
   267  	return nil
   268  }
   269  
   270  func panicHandler(output string) {
   271  	if len(loadedConfigJSON) > 0 {
   272  		config, err := server.LoadConfig([]byte(loadedConfigJSON))
   273  		if err != nil {
   274  			fmt.Printf("error parsing configuration file: %s\n%s\n", err, output)
   275  			os.Exit(1)
   276  		}
   277  
   278  		logEvent := make(map[string]string)
   279  		logEvent["host_id"] = config.HostID
   280  		logEvent["build_rev"] = buildinfo.GetBuildInfo().BuildRev
   281  		logEvent["timestamp"] = time.Now().Format(time.RFC3339)
   282  		logEvent["event_name"] = "server_panic"
   283  
   284  		// ELK has a maximum field length of 32766 UTF8 bytes. Over that length, the
   285  		// log won't be delivered. Truncate the panic field, as it may be much longer.
   286  		maxLen := 32766
   287  		if len(output) > maxLen {
   288  			output = output[:maxLen]
   289  		}
   290  
   291  		logEvent["panic"] = output
   292  
   293  		// Logs are written to the configured file name. If no name is specified, logs are written to stderr
   294  		var jsonWriter io.Writer
   295  		if config.LogFilename != "" {
   296  
   297  			retries, create, mode := config.GetLogFileReopenConfig()
   298  			panicLog, err := rotate.NewRotatableFileWriter(
   299  				config.LogFilename, retries, create, mode)
   300  			if err != nil {
   301  				fmt.Printf("unable to set panic log output: %s\n%s\n", err, output)
   302  				os.Exit(1)
   303  			}
   304  			defer panicLog.Close()
   305  
   306  			jsonWriter = panicLog
   307  		} else {
   308  			jsonWriter = os.Stderr
   309  		}
   310  
   311  		enc := json.NewEncoder(jsonWriter)
   312  		err = enc.Encode(logEvent)
   313  		if err != nil {
   314  			fmt.Printf("unable to serialize panic message to JSON: %s\n%s\n", err, output)
   315  			os.Exit(1)
   316  		}
   317  	} else {
   318  		fmt.Printf("no configuration JSON was loaded, cannot continue\n%s\n", output)
   319  		os.Exit(1)
   320  	}
   321  
   322  	os.Exit(1)
   323  }