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 }