Example #1
0
// connectedAPIRequestHandler implements the "connected" API request.
// Clients make the connected request once a tunnel connection has been
// established and at least once per day. The last_connected input value,
// which should be a connected_timestamp output from a previous connected
// response, is used to calculate unique user stats.
func connectedAPIRequestHandler(
	support *SupportServices,
	geoIPData GeoIPData,
	params requestJSONObject) ([]byte, error) {

	err := validateRequestParams(support, params, connectedRequestParams)
	if err != nil {
		return nil, common.ContextError(err)
	}

	log.LogRawFieldsWithTimestamp(
		getRequestLogFields(
			support,
			"connected",
			geoIPData,
			params,
			connectedRequestParams))

	connectedResponse := common.ConnectedResponse{
		ConnectedTimestamp: common.TruncateTimestampToHour(common.GetCurrentTimestamp()),
	}

	responsePayload, err := json.Marshal(connectedResponse)
	if err != nil {
		return nil, common.ContextError(err)
	}

	return responsePayload, nil
}
// tunneledLookupIP resolves a split tunnel candidate hostname with a tunneled
// DNS request.
func tunneledLookupIP(
	dnsServerAddress string, dnsTunneler Tunneler, host string) (addr net.IP, ttl time.Duration, err error) {

	ipAddr := net.ParseIP(host)
	if ipAddr != nil {
		// maxDuration from golang.org/src/time/time.go
		return ipAddr, time.Duration(1<<63 - 1), nil
	}

	// dnsServerAddress must be an IP address
	ipAddr = net.ParseIP(dnsServerAddress)
	if ipAddr == nil {
		return nil, 0, common.ContextError(errors.New("invalid IP address"))
	}

	// Dial's alwaysTunnel is set to true to ensure this connection
	// is tunneled (also ensures this code path isn't circular).
	// Assumes tunnel dialer conn configures timeouts and interruptibility.

	conn, err := dnsTunneler.Dial(fmt.Sprintf(
		"%s:%d", dnsServerAddress, DNS_PORT), true, nil)
	if err != nil {
		return nil, 0, common.ContextError(err)
	}

	ipAddrs, ttls, err := ResolveIP(host, conn)
	if err != nil {
		return nil, 0, common.ContextError(err)
	}
	if len(ipAddrs) < 1 {
		return nil, 0, common.ContextError(errors.New("no IP address"))
	}

	return ipAddrs[0], ttls[0], nil
}
// Encrypt plaintext with AES in CBC mode.
func encryptAESCBC(plaintext []byte) ([]byte, []byte, []byte, error) {
	// CBC mode works on blocks so plaintexts need to be padded to the
	// next whole block (https://tools.ietf.org/html/rfc5246#section-6.2.3.2).
	plaintext = AddPKCS7Padding(plaintext, aes.BlockSize)

	ciphertext := make([]byte, len(plaintext))
	iv, err := common.MakeSecureRandomBytes(aes.BlockSize)
	if err != nil {
		return nil, nil, nil, err
	}

	key, err := common.MakeSecureRandomBytes(aes.BlockSize)
	if err != nil {
		return nil, nil, nil, common.ContextError(err)
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, nil, common.ContextError(err)
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext, plaintext)

	return iv, key, ciphertext, nil
}
// NewServerContext makes the tunnelled handshake request to the Psiphon server
// and returns a ServerContext struct for use with subsequent Psiphon server API
// requests (e.g., periodic connected and status requests).
func NewServerContext(tunnel *Tunnel, sessionId string) (*ServerContext, error) {

	// For legacy servers, set up psiphonHttpsClient for
	// accessing the Psiphon API via the web service.
	var psiphonHttpsClient *http.Client
	if !tunnel.serverEntry.SupportsSSHAPIRequests() ||
		tunnel.config.TargetApiProtocol == common.PSIPHON_WEB_API_PROTOCOL {

		var err error
		psiphonHttpsClient, err = makePsiphonHttpsClient(tunnel)
		if err != nil {
			return nil, common.ContextError(err)
		}
	}

	serverContext := &ServerContext{
		sessionId:          sessionId,
		tunnelNumber:       atomic.AddInt64(&nextTunnelNumber, 1),
		tunnel:             tunnel,
		psiphonHttpsClient: psiphonHttpsClient,
	}

	err := serverContext.doHandshakeRequest()
	if err != nil {
		return nil, common.ContextError(err)
	}

	return serverContext, nil
}
func HandleOSLRequest(
	tunnelOwner TunnelOwner, tunnel *Tunnel, payload []byte) error {

	var oslRequest protocol.OSLRequest
	err := json.Unmarshal(payload, &oslRequest)
	if err != nil {
		return common.ContextError(err)
	}

	if oslRequest.ClearLocalSLOKs {
		DeleteSLOKs()
	}

	seededNewSLOK := false

	for _, slok := range oslRequest.SeedPayload.SLOKs {
		duplicate, err := SetSLOK(slok.ID, slok.Key)
		if err != nil {
			// TODO: return error to trigger retry?
			NoticeAlert("SetSLOK failed: %s", common.ContextError(err))
		} else if !duplicate {
			seededNewSLOK = true
		}

		if tunnel.config.EmitSLOKs {
			NoticeSLOKSeeded(base64.StdEncoding.EncodeToString(slok.ID), duplicate)
		}
	}

	if seededNewSLOK {
		tunnelOwner.SignalSeededNewSLOK()
	}

	return nil
}
Example #6
0
func parseResolveConf(filename string) (net.IP, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, common.ContextError(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) == 2 && fields[0] == "nameserver" {
			// TODO: parseResolverAddress will fail when the nameserver
			// is not an IP address. It may be a domain name. To support
			// this case, should proceed to the next "nameserver" line.
			return parseResolver(fields[1])
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, common.ContextError(err)
	}
	return nil, common.ContextError(errors.New("nameserver not found"))
}
Example #7
0
func validateRequestParams(
	support *SupportServices,
	params requestJSONObject,
	expectedParams []requestParamSpec) error {

	for _, expectedParam := range expectedParams {
		value := params[expectedParam.name]
		if value == nil {
			if expectedParam.flags&requestParamOptional != 0 {
				continue
			}
			return common.ContextError(
				fmt.Errorf("missing param: %s", expectedParam.name))
		}
		var err error
		if expectedParam.flags&requestParamArray != 0 {
			err = validateStringArrayRequestParam(support, expectedParam, value)
		} else {
			err = validateStringRequestParam(support, expectedParam, value)
		}
		if err != nil {
			return common.ContextError(err)
		}
	}

	return nil
}
// NewSupportServices initializes a new SupportServices.
func NewSupportServices(config *Config) (*SupportServices, error) {
	trafficRulesSet, err := NewTrafficRulesSet(config.TrafficRulesFilename)
	if err != nil {
		return nil, common.ContextError(err)
	}

	psinetDatabase, err := psinet.NewDatabase(config.PsinetDatabaseFilename)
	if err != nil {
		return nil, common.ContextError(err)
	}

	geoIPService, err := NewGeoIPService(
		config.GeoIPDatabaseFilenames, config.DiscoveryValueHMACKey)
	if err != nil {
		return nil, common.ContextError(err)
	}

	dnsResolver, err := NewDNSResolver(config.DNSResolverIPAddress)
	if err != nil {
		return nil, common.ContextError(err)
	}

	return &SupportServices{
		Config:          config,
		TrafficRulesSet: trafficRulesSet,
		PsinetDatabase:  psinetDatabase,
		GeoIPService:    geoIPService,
		DNSResolver:     dnsResolver,
	}, nil
}
// unpackRemoteServerListFile reads a file that contains a
// zlib compressed authenticated data package, validates
// the package, and returns the payload.
func unpackRemoteServerListFile(
	config *Config, filename string) (string, error) {

	fileReader, err := os.Open(filename)
	if err != nil {
		return "", common.ContextError(err)
	}
	defer fileReader.Close()

	zlibReader, err := zlib.NewReader(fileReader)
	if err != nil {
		return "", common.ContextError(err)
	}

	dataPackage, err := ioutil.ReadAll(zlibReader)
	zlibReader.Close()
	if err != nil {
		return "", common.ContextError(err)
	}

	payload, err := common.ReadAuthenticatedDataPackage(
		dataPackage, config.RemoteServerListSignaturePublicKey)
	if err != nil {
		return "", common.ContextError(err)
	}

	return payload, nil
}
func scanServerEntries(scanner func(*protocol.ServerEntry)) error {
	err := singleton.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(serverEntriesBucket))
		cursor := bucket.Cursor()

		for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
			serverEntry := new(protocol.ServerEntry)
			err := json.Unmarshal(value, serverEntry)
			if err != nil {
				// In case of data corruption or a bug causing this condition,
				// do not stop iterating.
				NoticeAlert("scanServerEntries: %s", common.ContextError(err))
				continue
			}
			scanner(serverEntry)
		}

		return nil
	})

	if err != nil {
		return common.ContextError(err)
	}

	return nil
}
Example #11
0
// NewDatabase initializes a Database, calling Reload on the specified
// filename.
func NewDatabase(filename string) (*Database, error) {

	database := &Database{}

	database.ReloadableFile = common.NewReloadableFile(
		filename,
		func(filename string) error {
			psinetJSON, err := ioutil.ReadFile(filename)
			if err != nil {
				// On error, state remains the same
				return common.ContextError(err)
			}
			err = json.Unmarshal(psinetJSON, &database)
			if err != nil {
				// On error, state remains the same
				// (Unmarshal first validates the provided
				//  JOSN and then populates the interface)
				return common.ContextError(err)
			}
			return nil
		})

	_, err := database.Reload()
	if err != nil {
		return nil, common.ContextError(err)
	}

	return database, nil
}
Example #12
0
// InitLogging configures a logger according to the specified
// config params. If not called, the default logger set by the
// package init() is used.
// Concurrenty note: should only be called from the main
// goroutine.
func InitLogging(config *Config) error {

	level, err := logrus.ParseLevel(config.LogLevel)
	if err != nil {
		return common.ContextError(err)
	}

	logWriter := os.Stderr

	if config.LogFilename != "" {
		logWriter, err = os.OpenFile(
			config.LogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
		if err != nil {
			return common.ContextError(err)
		}
	}

	log = &ContextLogger{
		&logrus.Logger{
			Out:       logWriter,
			Formatter: &CustomJSONFormatter{},
			Level:     level,
		},
	}

	return nil
}
func readSshPacket(
	conn net.Conn, deobfuscate func([]byte)) ([]byte, bool, error) {

	prefix := make([]byte, SSH_PACKET_PREFIX_LENGTH)
	_, err := io.ReadFull(conn, prefix)
	if err != nil {
		return nil, false, common.ContextError(err)
	}
	deobfuscate(prefix)
	packetLength, _, payloadLength, messageLength := getSshPacketPrefix(prefix)
	if packetLength > SSH_MAX_PACKET_LENGTH {
		return nil, false, common.ContextError(errors.New("ssh packet length too large"))
	}
	readBuffer := make([]byte, messageLength)
	copy(readBuffer, prefix)
	_, err = io.ReadFull(conn, readBuffer[len(prefix):])
	if err != nil {
		return nil, false, common.ContextError(err)
	}
	deobfuscate(readBuffer[len(prefix):])
	isMsgNewKeys := false
	if payloadLength > 0 {
		packetType := int(readBuffer[SSH_PACKET_PREFIX_LENGTH])
		if packetType == SSH_MSG_NEWKEYS {
			isMsgNewKeys = true
		}
	}
	return readBuffer, isMsgNewKeys, nil
}
// NewClientObfuscator creates a new Obfuscator, staging a seed message to be
// sent to the server (by the caller) and initializing stream ciphers to
// obfuscate data.
func NewClientObfuscator(
	config *ObfuscatorConfig) (obfuscator *Obfuscator, err error) {

	seed, err := common.MakeSecureRandomBytes(OBFUSCATE_SEED_LENGTH)
	if err != nil {
		return nil, common.ContextError(err)
	}

	clientToServerCipher, serverToClientCipher, err := initObfuscatorCiphers(seed, config)
	if err != nil {
		return nil, common.ContextError(err)
	}

	maxPadding := OBFUSCATE_MAX_PADDING
	if config.MaxPadding > 0 {
		maxPadding = config.MaxPadding
	}

	seedMessage, err := makeSeedMessage(maxPadding, seed, clientToServerCipher)
	if err != nil {
		return nil, common.ContextError(err)
	}

	return &Obfuscator{
		seedMessage:          seedMessage,
		clientToServerCipher: clientToServerCipher,
		serverToClientCipher: serverToClientCipher}, nil
}
// NewTrafficRulesSet initializes a TrafficRulesSet with
// the rules data in the specified config file.
func NewTrafficRulesSet(filename string) (*TrafficRulesSet, error) {

	set := &TrafficRulesSet{}

	set.ReloadableFile = common.NewReloadableFile(
		filename,
		func(filename string) error {
			configJSON, err := ioutil.ReadFile(filename)
			if err != nil {
				// On error, state remains the same
				return common.ContextError(err)
			}
			var newSet TrafficRulesSet
			err = json.Unmarshal(configJSON, &newSet)
			if err != nil {
				return common.ContextError(err)
			}
			err = newSet.Validate()
			if err != nil {
				return common.ContextError(err)
			}
			// Modify actual traffic rules only after validation
			set.DefaultRules = newSet.DefaultRules
			set.FilteredRules = newSet.FilteredRules
			return nil
		})

	_, err := set.Reload()
	if err != nil {
		return nil, common.ContextError(err)
	}

	return set, nil
}
func initObfuscatorCiphers(
	seed []byte, config *ObfuscatorConfig) (*rc4.Cipher, *rc4.Cipher, error) {

	clientToServerKey, err := deriveKey(seed, []byte(config.Keyword), []byte(OBFUSCATE_CLIENT_TO_SERVER_IV))
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	serverToClientKey, err := deriveKey(seed, []byte(config.Keyword), []byte(OBFUSCATE_SERVER_TO_CLIENT_IV))
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	clientToServerCipher, err := rc4.NewCipher(clientToServerKey)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	serverToClientCipher, err := rc4.NewCipher(serverToClientKey)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	return clientToServerCipher, serverToClientCipher, nil
}
// Write writes data to the connection.
// net.Conn Deadlines are ignored. net.Conn concurrency semantics are supported.
func (meek *MeekConn) Write(buffer []byte) (n int, err error) {
	if meek.closed() {
		return 0, common.ContextError(errors.New("meek connection is closed"))
	}
	// Repeats until all n bytes are written
	n = len(buffer)
	for len(buffer) > 0 {
		// Block until there is capacity in the send buffer
		var sendBuffer *bytes.Buffer
		select {
		case sendBuffer = <-meek.emptySendBuffer:
		case sendBuffer = <-meek.partialSendBuffer:
		case <-meek.broadcastClosed:
			return 0, common.ContextError(errors.New("meek connection has closed"))
		}
		writeLen := MAX_SEND_PAYLOAD_LENGTH - sendBuffer.Len()
		if writeLen > 0 {
			if writeLen > len(buffer) {
				writeLen = len(buffer)
			}
			_, err = sendBuffer.Write(buffer[:writeLen])
			buffer = buffer[writeLen:]
		}
		meek.replaceSendBuffer(sendBuffer)
	}
	return n, err
}
// DecodeServerEntry extracts server entries from the encoding
// used by remote server lists and Psiphon server handshake requests.
//
// The resulting ServerEntry.LocalSource is populated with serverEntrySource,
// which should be one of SERVER_ENTRY_SOURCE_EMBEDDED, SERVER_ENTRY_SOURCE_REMOTE,
// SERVER_ENTRY_SOURCE_DISCOVERY, SERVER_ENTRY_SOURCE_TARGET.
// ServerEntry.LocalTimestamp is populated with the provided timestamp, which
// should be a RFC 3339 formatted string. These local fields are stored with the
// server entry and reported to the server as stats (a coarse granularity timestamp
// is reported).
func DecodeServerEntry(
	encodedServerEntry, timestamp,
	serverEntrySource string) (serverEntry *ServerEntry, err error) {

	hexDecodedServerEntry, err := hex.DecodeString(encodedServerEntry)
	if err != nil {
		return nil, common.ContextError(err)
	}

	// Skip past legacy format (4 space delimited fields) and just parse the JSON config
	fields := bytes.SplitN(hexDecodedServerEntry, []byte(" "), 5)
	if len(fields) != 5 {
		return nil, common.ContextError(errors.New("invalid encoded server entry"))
	}

	serverEntry = new(ServerEntry)
	err = json.Unmarshal(fields[4], &serverEntry)
	if err != nil {
		return nil, common.ContextError(err)
	}

	// NOTE: if the source JSON happens to have values in these fields, they get clobbered.
	serverEntry.LocalSource = serverEntrySource
	serverEntry.LocalTimestamp = timestamp

	return serverEntry, nil
}
// Take in an interface name ("lo", "eth0", "any") passed from either
// a config setting, by using the -listenInterface flag on client or
// -interface flag on server from the command line and return the IP
// address associated with it.
// If no interface is provided use the default loopback interface (127.0.0.1).
// If "any" is passed then listen on 0.0.0.0 for client (invalid with server)
func GetInterfaceIPAddress(listenInterface string) (string, error) {
	var ip net.IP
	if listenInterface == "" {
		ip = net.ParseIP("127.0.0.1")
		return ip.String(), nil
	} else if listenInterface == "any" {
		ip = net.ParseIP("0.0.0.0")
		return ip.String(), nil
	} else {
		availableInterfaces, err := net.InterfaceByName(listenInterface)
		if err != nil {
			return "", common.ContextError(err)
		}

		addrs, err := availableInterfaces.Addrs()
		if err != nil {
			return "", common.ContextError(err)
		}
		for _, addr := range addrs {
			iptype := addr.(*net.IPNet)
			if iptype == nil {
				continue
			}
			// TODO: IPv6 support
			ip = iptype.IP.To4()
			if ip == nil {
				continue
			}
			return ip.String(), nil
		}
	}

	return "", common.ContextError(errors.New("Could not find IP address of specified interface"))

}
Example #20
0
// MakeTunneledHttpClient returns a net/http.Client which is
// configured to use custom dialing features including tunneled
// dialing and, optionally, UseTrustedCACertificatesForStockTLS.
// Unlike MakeUntunneledHttpsClient and makePsiphonHttpsClient,
// This http.Client uses stock TLS and no scheme transformation
// hack is required.
func MakeTunneledHttpClient(
	config *Config,
	tunnel *Tunnel,
	requestTimeout time.Duration) (*http.Client, error) {

	tunneledDialer := func(_, addr string) (conn net.Conn, err error) {
		return tunnel.sshClient.Dial("tcp", addr)
	}

	transport := &http.Transport{
		Dial: tunneledDialer,
	}

	if config.UseTrustedCACertificatesForStockTLS {
		if config.TrustedCACertificatesFilename == "" {
			return nil, common.ContextError(errors.New(
				"UseTrustedCACertificatesForStockTLS requires TrustedCACertificatesFilename"))
		}
		rootCAs := x509.NewCertPool()
		certData, err := ioutil.ReadFile(config.TrustedCACertificatesFilename)
		if err != nil {
			return nil, common.ContextError(err)
		}
		rootCAs.AppendCertsFromPEM(certData)
		transport.TLSClientConfig = &tls.Config{RootCAs: rootCAs}
	}

	return &http.Client{
		Transport: transport,
		Timeout:   requestTimeout,
	}, nil
}
Example #21
0
// MakeDownloadHttpClient is a resusable helper that sets up a
// http.Client for use either untunneled or through a tunnel.
// See MakeUntunneledHttpsClient for a note about request URL
// rewritting.
func MakeDownloadHttpClient(
	config *Config,
	tunnel *Tunnel,
	untunneledDialConfig *DialConfig,
	requestUrl string,
	requestTimeout time.Duration) (*http.Client, string, error) {

	var httpClient *http.Client
	var err error

	if tunnel != nil {
		httpClient, err = MakeTunneledHttpClient(config, tunnel, requestTimeout)
		if err != nil {
			return nil, "", common.ContextError(err)
		}
	} else {
		httpClient, requestUrl, err = MakeUntunneledHttpsClient(
			untunneledDialConfig, nil, requestUrl, requestTimeout)
		if err != nil {
			return nil, "", common.ContextError(err)
		}
	}

	return httpClient, requestUrl, nil
}
Example #22
0
// shamirSplit is a helper wrapper for sss.Split
func shamirSplit(secret []byte, total, threshold int) ([][]byte, error) {
	if !isValidShamirSplit(total, threshold) {
		return nil, common.ContextError(errors.New("invalid parameters"))
	}

	if threshold == 1 {
		// Special case: each share is simply the secret
		shares := make([][]byte, total)
		for i := 0; i < total; i++ {
			shares[i] = secret
		}
		return shares, nil
	}

	shareMap, err := sss.Split(byte(total), byte(threshold), secret)
	if err != nil {
		return nil, common.ContextError(err)
	}

	shares := make([][]byte, total)
	for i := 0; i < total; i++ {
		// Note: sss.Combine index starts at 1
		shares[i] = shareMap[byte(i)+1]
	}

	return shares, nil
}
Example #23
0
// makeOSLFileSpec creates a random OSL file key, splits it according
// the the scheme's key splits, and sets the OSL ID as its first SLOK
// ID. The returned key is used to encrypt the OSL payload and then
// discarded; the key may be reassembled using the data in the KeyShares
// tree, given sufficient SLOKs.
func makeOSLFileSpec(
	scheme *Scheme,
	propagationChannelID string,
	firstSLOKTime time.Time) ([]byte, *OSLFileSpec, error) {

	ref := &slokReference{
		PropagationChannelID: propagationChannelID,
		SeedSpecID:           string(scheme.SeedSpecs[0].ID),
		Time:                 firstSLOKTime,
	}
	firstSLOK := deriveSLOK(scheme, ref)
	oslID := firstSLOK.ID

	fileKey, err := common.MakeSecureRandomBytes(KEY_LENGTH_BYTES)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	keyShares, err := divideKey(
		scheme,
		fileKey,
		scheme.SeedPeriodKeySplits,
		propagationChannelID,
		&firstSLOKTime)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	fileSpec := &OSLFileSpec{
		ID:        oslID,
		KeyShares: keyShares,
	}

	return fileKey, fileSpec, nil
}
Example #24
0
// selectProtocol is a helper that picks the tunnel protocol
func selectProtocol(
	config *Config, serverEntry *protocol.ServerEntry) (selectedProtocol string, err error) {

	// TODO: properly handle protocols (e.g. FRONTED-MEEK-OSSH) vs. capabilities (e.g., {FRONTED-MEEK, OSSH})
	// for now, the code is simply assuming that MEEK capabilities imply OSSH capability.
	if config.TunnelProtocol != "" {
		if !serverEntry.SupportsProtocol(config.TunnelProtocol) {
			return "", common.ContextError(fmt.Errorf("server does not have required capability"))
		}
		selectedProtocol = config.TunnelProtocol
	} else {
		// Pick at random from the supported protocols. This ensures that we'll eventually
		// try all possible protocols. Depending on network configuration, it may be the
		// case that some protocol is only available through multi-capability servers,
		// and a simpler ranked preference of protocols could lead to that protocol never
		// being selected.

		candidateProtocols := serverEntry.GetSupportedProtocols()
		if len(candidateProtocols) == 0 {
			return "", common.ContextError(fmt.Errorf("server does not have any supported capabilities"))
		}

		index, err := common.MakeSecureRandomInt(len(candidateProtocols))
		if err != nil {
			return "", common.ContextError(err)
		}
		selectedProtocol = candidateProtocols[index]
	}
	return selectedProtocol, nil
}
Example #25
0
// dispatchAPIRequestHandler is the common dispatch point for both
// web and SSH API requests.
func dispatchAPIRequestHandler(
	support *SupportServices,
	apiProtocol string,
	geoIPData GeoIPData,
	name string,
	params requestJSONObject) (response []byte, reterr error) {

	// Recover from and log any unexpected panics caused by user input
	// handling bugs. User inputs should be properly validated; this
	// mechanism is only a last resort to prevent the process from
	// terminating in the case of a bug.
	defer func() {
		if e := recover(); e != nil {
			reterr = common.ContextError(
				fmt.Errorf(
					"request handler panic: %s: %s", e, debug.Stack()))
		}
	}()

	switch name {
	case common.PSIPHON_API_HANDSHAKE_REQUEST_NAME:
		return handshakeAPIRequestHandler(support, apiProtocol, geoIPData, params)
	case common.PSIPHON_API_CONNECTED_REQUEST_NAME:
		return connectedAPIRequestHandler(support, geoIPData, params)
	case common.PSIPHON_API_STATUS_REQUEST_NAME:
		return statusAPIRequestHandler(support, geoIPData, params)
	case common.PSIPHON_API_CLIENT_VERIFICATION_REQUEST_NAME:
		return clientVerificationAPIRequestHandler(support, geoIPData, params)
	}

	return nil, common.ContextError(fmt.Errorf("invalid request name: %s", name))
}
// Next returns the next server entry, by rank, for a legacyServerEntryIterator.
// Returns nil with no error when there is no next item.
func (iterator *legacyServerEntryIterator) Next() (serverEntry *ServerEntry, err error) {
	defer func() {
		if err != nil {
			iterator.Close()
		}
	}()

	if !iterator.cursor.Next() {
		err = iterator.cursor.Err()
		if err != nil {
			return nil, common.ContextError(err)
		}
		// There is no next item
		return nil, nil
	}

	var data []byte
	err = iterator.cursor.Scan(&data)
	if err != nil {
		return nil, common.ContextError(err)
	}
	serverEntry = new(ServerEntry)
	err = json.Unmarshal(data, serverEntry)
	if err != nil {
		return nil, common.ContextError(err)
	}

	return MakeCompatibleServerEntry(serverEntry), nil
}
// interruptibleTCPDial establishes a TCP network connection. A conn is added
// to config.PendingConns before blocking on network I/O, which enables interruption.
// The caller is responsible for removing an established conn from PendingConns.
// An upstream proxy is used when specified.
//
// Note: do not to set a UpstreamProxyUrl in the config when using
// NewTCPDialer as a custom dialer for NewProxyAuthTransport (or http.Transport
// with a ProxyUrl), as that would result in double proxy chaining.
//
// Note: interruption does not actually cancel a connection in progress; it
// stops waiting for the goroutine blocking on connect()/Dial.
func interruptibleTCPDial(addr string, config *DialConfig) (*TCPConn, error) {

	// Buffers the first result; senders should discard results when
	// sending would block, as that means the first result is already set.
	conn := &TCPConn{dialResult: make(chan error, 1)}

	// Enable interruption
	if config.PendingConns != nil && !config.PendingConns.Add(conn) {
		return nil, common.ContextError(errors.New("pending connections already closed"))
	}

	// Call the blocking Connect() in a goroutine. ConnectTimeout is handled
	// in the platform-specific tcpDial helper function.
	// Note: since this goroutine may be left running after an interrupt, don't
	// call Notice() or perform other actions unexpected after a Controller stops.
	// The lifetime of the goroutine may depend on the host OS TCP connect timeout
	// when tcpDial, amoung other things, when makes a blocking syscall.Connect()
	// call.
	go func() {
		var netConn net.Conn
		var err error
		if config.UpstreamProxyUrl != "" {
			netConn, err = proxiedTcpDial(addr, config, conn.dialResult)
		} else {
			netConn, err = tcpDial(addr, config, conn.dialResult)
		}

		// Mutex is necessary for referencing conn.isClosed and conn.Conn as
		// TCPConn.Close may be called while this goroutine is running.
		conn.mutex.Lock()

		// If already interrupted, cleanup the net.Conn resource and discard.
		if conn.isClosed && netConn != nil {
			netConn.Close()
			conn.mutex.Unlock()
			return
		}

		conn.Conn = netConn
		conn.mutex.Unlock()

		select {
		case conn.dialResult <- err:
		default:
		}
	}()

	// Wait until Dial completes (or times out) or until interrupt
	err := <-conn.dialResult
	if err != nil {
		if config.PendingConns != nil {
			config.PendingConns.Remove(conn)
		}
		return nil, common.ContextError(err)
	}

	// TODO: now allow conn.dialResult to be garbage collected?

	return conn, nil
}
// relay sends and receives tunneled traffic (payload). An HTTP request is
// triggered when data is in the write queue or at a polling interval.
// There's a geometric increase, up to a maximum, in the polling interval when
// no data is exchanged. Only one HTTP request is in flight at a time.
func (meek *MeekConn) relay() {
	// Note: meek.Close() calls here in relay() are made asynchronously
	// (using goroutines) since Close() will wait on this WaitGroup.
	defer meek.relayWaitGroup.Done()
	interval := MIN_POLL_INTERVAL
	timeout := time.NewTimer(interval)
	sendPayload := make([]byte, MAX_SEND_PAYLOAD_LENGTH)
	for {
		timeout.Reset(interval)
		// Block until there is payload to send or it is time to poll
		var sendBuffer *bytes.Buffer
		select {
		case sendBuffer = <-meek.partialSendBuffer:
		case sendBuffer = <-meek.fullSendBuffer:
		case <-timeout.C:
			// In the polling case, send an empty payload
		case <-meek.broadcastClosed:
			// TODO: timeout case may be selected when broadcastClosed is set?
			return
		}
		sendPayloadSize := 0
		if sendBuffer != nil {
			var err error
			sendPayloadSize, err = sendBuffer.Read(sendPayload)
			meek.replaceSendBuffer(sendBuffer)
			if err != nil {
				NoticeAlert("%s", common.ContextError(err))
				go meek.Close()
				return
			}
		}
		receivedPayload, err := meek.roundTrip(sendPayload[:sendPayloadSize])
		if err != nil {
			NoticeAlert("%s", common.ContextError(err))
			go meek.Close()
			return
		}
		if receivedPayload == nil {
			// In this case, meek.roundTrip encountered broadcastClosed. Exit without error.
			return
		}
		receivedPayloadSize, err := meek.readPayload(receivedPayload)
		if err != nil {
			NoticeAlert("%s", common.ContextError(err))
			go meek.Close()
			return
		}
		if receivedPayloadSize > 0 || sendPayloadSize > 0 {
			interval = 0
		} else if interval == 0 {
			interval = MIN_POLL_INTERVAL
		} else {
			interval = time.Duration(float64(interval) * POLL_INTERNAL_MULTIPLIER)
			if interval >= MAX_POLL_INTERVAL {
				interval = MAX_POLL_INTERVAL
			}
		}
	}
}
Example #29
0
func getInt64RequestParam(params requestJSONObject, name string) (int64, error) {
	if params[name] == nil {
		return 0, common.ContextError(fmt.Errorf("missing param: %s", name))
	}
	value, ok := params[name].(float64)
	if !ok {
		return 0, common.ContextError(fmt.Errorf("invalid param: %s", name))
	}
	return int64(value), nil
}
func verifyLegacyCertificate(conn *tls.Conn, expectedCertificate *x509.Certificate) error {
	certs := conn.ConnectionState().PeerCertificates
	if len(certs) < 1 {
		return common.ContextError(errors.New("no certificate to verify"))
	}
	if !bytes.Equal(certs[0].Raw, expectedCertificate.Raw) {
		return common.ContextError(errors.New("unexpected certificate"))
	}
	return nil
}