Просмотр исходного кода

Merge pull request #238 from rod-hynes/master

Add TCPPortForwardRedirects + minor changes
Rod Hynes 9 лет назад
Родитель
Сommit
e73ad9f63d
4 измененных файлов с 82 добавлено и 12 удалено
  1. 18 1
      psiphon/notice.go
  2. 46 8
      psiphon/server/config.go
  3. 15 3
      psiphon/server/tunnelServer.go
  4. 3 0
      psiphon/server/webServer.go

+ 18 - 1
psiphon/notice.go

@@ -22,6 +22,7 @@ package psiphon
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"log"
@@ -112,7 +113,23 @@ func outputNotice(noticeType string, noticeFlags uint32, args ...interface{}) {
 	if err == nil {
 		output = string(encodedJson)
 	} else {
-		output = fmt.Sprintf("{\"Alert\":{\"message\":\"%s\"}}", common.ContextError(err))
+		// Try to emit a properly formatted Alert notice that the outer client can
+		// report. One scenario where this is useful is if the preceeding Marshal
+		// fails due to bad data in the args. This has happened for a json.RawMessage
+		// field.
+		obj := make(map[string]interface{})
+		obj["noticeType"] = "Alert"
+		obj["showUser"] = false
+		obj["data"] = map[string]interface{}{
+			"message": fmt.Sprintf("Marshal notice failed: %s", common.ContextError(err)),
+		}
+		obj["timestamp"] = time.Now().UTC().Format(time.RFC3339)
+		encodedJson, err := json.Marshal(obj)
+		if err == nil {
+			output = string(encodedJson)
+		} else {
+			output = common.ContextError(errors.New("failed to marshal notice")).Error()
+		}
 	}
 	noticeLoggerMutex.Lock()
 	defer noticeLoggerMutex.Unlock()

+ 46 - 8
psiphon/server/config.go

@@ -173,6 +173,11 @@ type Config struct {
 	// directly by this server, which parses the SSH channel using the
 	// udpgw protocol. Handling includes udpgw transparent DNS: tunneled
 	// UDP DNS packets are rerouted to the host's DNS server.
+	//
+	// The intercept is applied before the port forward destination is
+	// validated against SSH_DISALLOWED_PORT_FORWARD_HOSTS and
+	// AllowTCPPorts/DenyTCPPorts. So the intercept address may be any
+	// otherwise prohibited destination.
 	UDPInterceptUdpgwServerAddress string
 
 	// DNSResolverIPAddress specifies the IP address of a DNS server
@@ -181,6 +186,21 @@ type Config struct {
 	// "nameserver" entry.
 	DNSResolverIPAddress string
 
+	// TCPPortForwardRedirects is a mapping from client port forward
+	// destination to an alternate destination address. When the client's
+	// port forward HostToConnect and PortToConnect matches a redirect,
+	// the redirect is substituted and dialed instead of the original
+	// destination.
+	//
+	// The redirect is applied after the original destination is
+	// validated against SSH_DISALLOWED_PORT_FORWARD_HOSTS and
+	// AllowTCPPorts/DenyTCPPorts. So the redirect may map to any
+	// otherwise prohibited destination.
+	//
+	// The redirect is applied after UDPInterceptUdpgwServerAddress is
+	// checked. So the redirect address will not be intercepted.
+	TCPPortForwardRedirects map[string]string
+
 	// LoadMonitorPeriodSeconds indicates how frequently to log server
 	// load information (number of connected clients per tunnel protocol,
 	// number of running goroutines, amount of memory allocated, etc.)
@@ -257,19 +277,26 @@ func LoadConfig(configJSON []byte) (*Config, error) {
 		}
 	}
 
-	validateNetworkAddress := func(address string) error {
-		host, port, err := net.SplitHostPort(address)
-		if err == nil && net.ParseIP(host) == nil {
-			err = errors.New("Host must be an IP address")
+	validateNetworkAddress := func(address string, requireIPaddress bool) error {
+		host, portStr, err := net.SplitHostPort(address)
+		if err != nil {
+			return err
+		}
+		if requireIPaddress && net.ParseIP(host) == nil {
+			return errors.New("host must be an IP address")
+		}
+		port, err := strconv.Atoi(portStr)
+		if err != nil {
+			return err
 		}
-		if err == nil {
-			_, err = strconv.Atoi(port)
+		if port < 0 || port > 65535 {
+			return errors.New("invalid port")
 		}
-		return err
+		return nil
 	}
 
 	if config.UDPInterceptUdpgwServerAddress != "" {
-		if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress); err != nil {
+		if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress, true); err != nil {
 			return nil, fmt.Errorf("UDPInterceptUdpgwServerAddress is invalid: %s", err)
 		}
 	}
@@ -280,6 +307,17 @@ func LoadConfig(configJSON []byte) (*Config, error) {
 		}
 	}
 
+	if config.TCPPortForwardRedirects != nil {
+		for destination, redirect := range config.TCPPortForwardRedirects {
+			if err := validateNetworkAddress(destination, false); err != nil {
+				return nil, fmt.Errorf("TCPPortForwardRedirects destination %s is invalid: %s", destination, err)
+			}
+			if err := validateNetworkAddress(redirect, false); err != nil {
+				return nil, fmt.Errorf("TCPPortForwardRedirects redirect %s is invalid: %s", redirect, err)
+			}
+		}
+	}
+
 	return &config, nil
 }
 

+ 15 - 3
psiphon/server/tunnelServer.go

@@ -26,6 +26,7 @@ import (
 	"fmt"
 	"io"
 	"net"
+	"strconv"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -801,9 +802,7 @@ func (sshClient *sshClient) handleNewPortForwardChannel(newChannel ssh.NewChanne
 	// TODO: also support UDP explicitly, e.g. with a custom "direct-udp" channel type?
 	isUDPChannel := sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress != "" &&
 		sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress ==
-			fmt.Sprintf("%s:%d",
-				directTcpipExtraData.HostToConnect,
-				directTcpipExtraData.PortToConnect)
+			net.JoinHostPort(directTcpipExtraData.HostToConnect, strconv.Itoa(int(directTcpipExtraData.PortToConnect)))
 
 	if isUDPChannel {
 		sshClient.handleUDPChannel(newChannel)
@@ -891,6 +890,19 @@ func (sshClient *sshClient) handleTCPChannel(
 		return
 	}
 
+	// Note: redirects are applied *after* isPortForwardPermitted allows the original destination
+	if sshClient.sshServer.support.Config.TCPPortForwardRedirects != nil {
+		destination := net.JoinHostPort(hostToConnect, strconv.Itoa(portToConnect))
+		if redirect, ok := sshClient.sshServer.support.Config.TCPPortForwardRedirects[destination]; ok {
+			// Note: redirect format is validated when config is loaded
+			host, portStr, _ := net.SplitHostPort(redirect)
+			port, _ := strconv.Atoi(portStr)
+			hostToConnect = host
+			portToConnect = port
+			log.WithContextFields(LogFields{"destination": destination, "redirect": redirect}).Debug("port forward redirect")
+		}
+	}
+
 	var bytesUp, bytesDown int64
 	sshClient.openedPortForward(sshClient.tcpTrafficState)
 	defer func() {

+ 3 - 0
psiphon/server/webServer.go

@@ -94,6 +94,9 @@ func RunWebServer(
 			ReadTimeout:    WEB_SERVER_IO_TIMEOUT,
 			WriteTimeout:   WEB_SERVER_IO_TIMEOUT,
 			ErrorLog:       golanglog.New(logWriter, "", 0),
+
+			// Disable auto HTTP/2 (https://golang.org/doc/go1.6)
+			TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
 		},
 	}