github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/webServer.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 server
    21  
    22  import (
    23  	"crypto/tls"
    24  	"encoding/json"
    25  	"io/ioutil"
    26  	golanglog "log"
    27  	"net"
    28  	"net/http"
    29  	"strconv"
    30  	"sync"
    31  	"time"
    32  
    33  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    34  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
    36  	tris "github.com/Psiphon-Labs/tls-tris"
    37  )
    38  
    39  const WEB_SERVER_IO_TIMEOUT = 10 * time.Second
    40  
    41  type webServer struct {
    42  	support *SupportServices
    43  }
    44  
    45  // RunWebServer runs a web server which supports tunneled and untunneled
    46  // Psiphon API requests.
    47  //
    48  // The HTTP request handlers are light wrappers around the base Psiphon
    49  // API request handlers from the SSH API transport. The SSH API transport
    50  // is preferred by new clients. The web API transport provides support for
    51  // older clients.
    52  //
    53  // The API is compatible with all tunnel-core clients but not backwards
    54  // compatible with all legacy clients.
    55  //
    56  // Note: new features, including authorizations, are not supported in the
    57  // web API transport.
    58  //
    59  func RunWebServer(
    60  	support *SupportServices,
    61  	shutdownBroadcast <-chan struct{}) error {
    62  
    63  	webServer := &webServer{
    64  		support: support,
    65  	}
    66  
    67  	serveMux := http.NewServeMux()
    68  	serveMux.HandleFunc("/handshake", webServer.handshakeHandler)
    69  	serveMux.HandleFunc("/connected", webServer.connectedHandler)
    70  	serveMux.HandleFunc("/status", webServer.statusHandler)
    71  	serveMux.HandleFunc("/client_verification", webServer.clientVerificationHandler)
    72  
    73  	certificate, err := tris.X509KeyPair(
    74  		[]byte(support.Config.WebServerCertificate),
    75  		[]byte(support.Config.WebServerPrivateKey))
    76  	if err != nil {
    77  		return errors.Trace(err)
    78  	}
    79  
    80  	tlsConfig := &tris.Config{
    81  		Certificates: []tris.Certificate{certificate},
    82  	}
    83  
    84  	// TODO: inherits global log config?
    85  	logWriter := NewLogWriter()
    86  	defer logWriter.Close()
    87  
    88  	// Note: WriteTimeout includes time awaiting request, as per:
    89  	// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts
    90  
    91  	server := &HTTPSServer{
    92  		&http.Server{
    93  			MaxHeaderBytes: MAX_API_PARAMS_SIZE,
    94  			Handler:        serveMux,
    95  			ReadTimeout:    WEB_SERVER_IO_TIMEOUT,
    96  			WriteTimeout:   WEB_SERVER_IO_TIMEOUT,
    97  			ErrorLog:       golanglog.New(logWriter, "", 0),
    98  
    99  			// Disable auto HTTP/2 (https://golang.org/doc/go1.6)
   100  			TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
   101  		},
   102  	}
   103  
   104  	localAddress := net.JoinHostPort(
   105  		support.Config.ServerIPAddress,
   106  		strconv.Itoa(support.Config.WebServerPort))
   107  
   108  	listener, err := net.Listen("tcp", localAddress)
   109  	if err != nil {
   110  		return errors.Trace(err)
   111  	}
   112  
   113  	log.WithTraceFields(
   114  		LogFields{"localAddress": localAddress}).Info("starting")
   115  
   116  	err = nil
   117  	errorChannel := make(chan error)
   118  	waitGroup := new(sync.WaitGroup)
   119  
   120  	waitGroup.Add(1)
   121  	go func() {
   122  		defer waitGroup.Done()
   123  
   124  		// Note: will be interrupted by listener.Close()
   125  		err := server.ServeTLS(listener, tlsConfig)
   126  
   127  		// Can't check for the exact error that Close() will cause in Accept(),
   128  		// (see: https://code.google.com/p/go/issues/detail?id=4373). So using an
   129  		// explicit stop signal to stop gracefully.
   130  		select {
   131  		case <-shutdownBroadcast:
   132  		default:
   133  			if err != nil {
   134  				select {
   135  				case errorChannel <- errors.Trace(err):
   136  				default:
   137  				}
   138  			}
   139  		}
   140  
   141  		log.WithTraceFields(
   142  			LogFields{"localAddress": localAddress}).Info("stopped")
   143  	}()
   144  
   145  	select {
   146  	case <-shutdownBroadcast:
   147  	case err = <-errorChannel:
   148  	}
   149  
   150  	listener.Close()
   151  
   152  	waitGroup.Wait()
   153  
   154  	log.WithTraceFields(
   155  		LogFields{"localAddress": localAddress}).Info("exiting")
   156  
   157  	return err
   158  }
   159  
   160  // convertHTTPRequestToAPIRequest converts the HTTP request query
   161  // parameters and request body to the JSON object import format
   162  // expected by the API request handlers.
   163  func convertHTTPRequestToAPIRequest(
   164  	w http.ResponseWriter,
   165  	r *http.Request,
   166  	requestBodyName string) (common.APIParameters, error) {
   167  
   168  	params := make(common.APIParameters)
   169  
   170  	for name, values := range r.URL.Query() {
   171  
   172  		// Limitations:
   173  		// - This is intended only to support params sent by legacy
   174  		//   clients; non-base array-type params are not converted.
   175  		// - Only the first values per name is used.
   176  
   177  		if len(values) > 0 {
   178  			value := values[0]
   179  
   180  			// TODO: faster lookup?
   181  			isArray := false
   182  			for _, paramSpec := range baseSessionAndDialParams {
   183  				if paramSpec.name == name {
   184  					isArray = (paramSpec.flags&requestParamArray != 0)
   185  					break
   186  				}
   187  			}
   188  
   189  			if isArray {
   190  				// Special case: a JSON encoded array
   191  				var arrayValue []interface{}
   192  				err := json.Unmarshal([]byte(value), &arrayValue)
   193  				if err != nil {
   194  					return nil, errors.Trace(err)
   195  				}
   196  				params[name] = arrayValue
   197  			} else {
   198  				// All other query parameters are simple strings
   199  				params[name] = value
   200  			}
   201  		}
   202  	}
   203  
   204  	if requestBodyName != "" {
   205  		r.Body = http.MaxBytesReader(w, r.Body, MAX_API_PARAMS_SIZE)
   206  		body, err := ioutil.ReadAll(r.Body)
   207  		if err != nil {
   208  			return nil, errors.Trace(err)
   209  		}
   210  		var bodyParams map[string]interface{}
   211  
   212  		if len(body) != 0 {
   213  			err = json.Unmarshal(body, &bodyParams)
   214  			if err != nil {
   215  				return nil, errors.Trace(err)
   216  			}
   217  			params[requestBodyName] = bodyParams
   218  		}
   219  	}
   220  
   221  	return params, nil
   222  }
   223  
   224  func (webServer *webServer) lookupGeoIPData(params common.APIParameters) GeoIPData {
   225  
   226  	clientSessionID, err := getStringRequestParam(params, "client_session_id")
   227  	if err != nil {
   228  		// Not all clients send this parameter
   229  		return NewGeoIPData()
   230  	}
   231  
   232  	return webServer.support.GeoIPService.GetSessionCache(clientSessionID)
   233  }
   234  
   235  func (webServer *webServer) handshakeHandler(w http.ResponseWriter, r *http.Request) {
   236  
   237  	params, err := convertHTTPRequestToAPIRequest(w, r, "")
   238  
   239  	var responsePayload []byte
   240  	if err == nil {
   241  		responsePayload, err = dispatchAPIRequestHandler(
   242  			webServer.support,
   243  			protocol.PSIPHON_WEB_API_PROTOCOL,
   244  			r.RemoteAddr,
   245  			webServer.lookupGeoIPData(params),
   246  			nil,
   247  			protocol.PSIPHON_API_HANDSHAKE_REQUEST_NAME,
   248  			params)
   249  	}
   250  
   251  	if err != nil {
   252  		log.WithTraceFields(LogFields{"error": err}).Warning("failed")
   253  		w.WriteHeader(http.StatusNotFound)
   254  		return
   255  	}
   256  
   257  	// The legacy response format is newline separated, name prefixed values.
   258  	// Within that legacy format, the modern JSON response (containing all the
   259  	// legacy response values and more) is single value with a "Config:" prefix.
   260  	// This response uses the legacy format but omits all but the JSON value.
   261  	responseBody := append([]byte("Config: "), responsePayload...)
   262  
   263  	w.WriteHeader(http.StatusOK)
   264  	w.Write(responseBody)
   265  }
   266  
   267  func (webServer *webServer) connectedHandler(w http.ResponseWriter, r *http.Request) {
   268  
   269  	params, err := convertHTTPRequestToAPIRequest(w, r, "")
   270  
   271  	var responsePayload []byte
   272  	if err == nil {
   273  		responsePayload, err = dispatchAPIRequestHandler(
   274  			webServer.support,
   275  			protocol.PSIPHON_WEB_API_PROTOCOL,
   276  			r.RemoteAddr,
   277  			webServer.lookupGeoIPData(params),
   278  			nil, // authorizedAccessTypes not logged in web API transport
   279  			protocol.PSIPHON_API_CONNECTED_REQUEST_NAME,
   280  			params)
   281  	}
   282  
   283  	if err != nil {
   284  		log.WithTraceFields(LogFields{"error": err}).Warning("failed")
   285  		w.WriteHeader(http.StatusNotFound)
   286  		return
   287  	}
   288  
   289  	w.WriteHeader(http.StatusOK)
   290  	w.Write(responsePayload)
   291  }
   292  
   293  func (webServer *webServer) statusHandler(w http.ResponseWriter, r *http.Request) {
   294  
   295  	params, err := convertHTTPRequestToAPIRequest(w, r, "statusData")
   296  
   297  	var responsePayload []byte
   298  	if err == nil {
   299  		responsePayload, err = dispatchAPIRequestHandler(
   300  			webServer.support,
   301  			protocol.PSIPHON_WEB_API_PROTOCOL,
   302  			r.RemoteAddr,
   303  			webServer.lookupGeoIPData(params),
   304  			nil, // authorizedAccessTypes not logged in web API transport
   305  			protocol.PSIPHON_API_STATUS_REQUEST_NAME,
   306  			params)
   307  	}
   308  
   309  	if err != nil {
   310  		log.WithTraceFields(LogFields{"error": err}).Warning("failed")
   311  		w.WriteHeader(http.StatusNotFound)
   312  		return
   313  	}
   314  
   315  	w.WriteHeader(http.StatusOK)
   316  	w.Write(responsePayload)
   317  }
   318  
   319  // clientVerificationHandler is kept for compliance with older Android clients
   320  func (webServer *webServer) clientVerificationHandler(w http.ResponseWriter, r *http.Request) {
   321  
   322  	params, err := convertHTTPRequestToAPIRequest(w, r, "verificationData")
   323  
   324  	var responsePayload []byte
   325  	if err == nil {
   326  		responsePayload, err = dispatchAPIRequestHandler(
   327  			webServer.support,
   328  			protocol.PSIPHON_WEB_API_PROTOCOL,
   329  			r.RemoteAddr,
   330  			webServer.lookupGeoIPData(params),
   331  			nil, // authorizedAccessTypes not logged in web API transport
   332  			protocol.PSIPHON_API_CLIENT_VERIFICATION_REQUEST_NAME,
   333  			params)
   334  	}
   335  
   336  	if err != nil {
   337  		log.WithTraceFields(LogFields{"error": err}).Warning("failed")
   338  		w.WriteHeader(http.StatusNotFound)
   339  		return
   340  	}
   341  
   342  	w.WriteHeader(http.StatusOK)
   343  	w.Write(responsePayload)
   344  }