Files
opera-proxy/seclient/seclient.go
T

336 lines
7.8 KiB
Go
Raw Normal View History

2021-03-25 16:25:36 +02:00
package seclient
import (
2021-03-25 19:58:30 +02:00
"context"
2021-03-25 20:29:37 +02:00
"encoding/json"
2021-03-25 19:58:30 +02:00
"fmt"
2021-03-25 20:29:37 +02:00
"io"
"io/ioutil"
2021-03-25 18:50:53 +02:00
"math/rand"
2021-03-25 16:25:36 +02:00
"net/http"
2021-03-25 19:58:30 +02:00
"net/url"
"strings"
2021-04-02 00:44:48 +03:00
"sync"
2021-03-25 16:25:36 +02:00
2026-03-25 15:26:39 +03:00
dac "github.com/Alexey71/go-http-digest-auth-client"
2021-03-25 16:25:36 +02:00
)
2021-03-25 18:50:53 +02:00
const (
2021-03-25 20:29:37 +02:00
ANON_EMAIL_LOCALPART_BYTES = 32
ANON_PASSWORD_BYTES = 20
DEVICE_ID_BYTES = 20
READ_LIMIT int64 = 128 * 1024
2021-03-25 18:50:53 +02:00
)
2021-03-25 16:25:36 +02:00
type SEEndpoints struct {
RegisterSubscriber string
SubscriberLogin string
RegisterDevice string
DeviceGeneratePassword string
GeoList string
Discover string
2021-03-25 16:25:36 +02:00
}
var DefaultSEEndpoints = SEEndpoints{
2024-10-05 19:01:39 +03:00
RegisterSubscriber: "https://api2.sec-tunnel.com/v4/register_subscriber",
SubscriberLogin: "https://api2.sec-tunnel.com/v4/subscriber_login",
RegisterDevice: "https://api2.sec-tunnel.com/v4/register_device",
DeviceGeneratePassword: "https://api2.sec-tunnel.com/v4/device_generate_password",
GeoList: "https://api2.sec-tunnel.com/v4/geo_list",
Discover: "https://api2.sec-tunnel.com/v4/discover",
2021-03-25 16:25:36 +02:00
}
type SESettings struct {
ClientVersion string
2021-03-25 18:50:53 +02:00
ClientType string
DeviceName string
2021-03-25 16:25:36 +02:00
OperatingSystem string
2021-03-25 19:58:30 +02:00
UserAgent string
2021-03-25 16:25:36 +02:00
Endpoints SEEndpoints
}
var DefaultSESettings = SESettings{
2024-10-05 19:01:39 +03:00
ClientVersion: "Stable 114.0.5282.21",
2021-03-25 18:50:53 +02:00
ClientType: "se0316",
2024-10-05 19:01:39 +03:00
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0",
2021-03-25 18:50:53 +02:00
DeviceName: "Opera-Browser-Client",
2021-03-25 16:25:36 +02:00
OperatingSystem: "Windows",
Endpoints: DefaultSEEndpoints,
}
type SEClient struct {
2021-04-02 13:23:36 +03:00
httpClient *http.Client
2021-03-25 18:50:53 +02:00
Settings SESettings
SubscriberEmail string
SubscriberPassword string
DeviceID string
AssignedDeviceID string
2021-03-25 22:09:36 +02:00
AssignedDeviceIDHash string
2021-03-25 18:50:53 +02:00
DevicePassword string
2021-04-02 13:23:36 +03:00
Mux sync.Mutex
2021-03-25 18:50:53 +02:00
rng *rand.Rand
2021-03-25 16:25:36 +02:00
}
2021-04-02 01:56:17 +03:00
type StrKV map[string]string
2021-03-25 16:25:36 +02:00
// Instantiates SurfEasy client with default settings and given API keys.
// Optional `transport` parameter allows to override HTTP transport used
// for HTTP calls
func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) {
if transport == nil {
transport = http.DefaultTransport
}
2021-03-25 19:58:30 +02:00
rng := rand.New(RandomSource)
2021-03-25 18:50:53 +02:00
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
if err != nil {
return nil, err
}
2021-04-02 13:23:36 +03:00
jar, err := NewStdJar()
if err != nil {
return nil, err
}
2021-04-02 01:56:17 +03:00
res := &SEClient{
2021-04-02 13:23:36 +03:00
httpClient: &http.Client{
2021-04-02 13:24:52 +03:00
Jar: jar,
2021-03-25 16:25:36 +02:00
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
},
Settings: DefaultSESettings,
2021-03-25 18:50:53 +02:00
rng: rng,
DeviceID: device_id,
2021-04-02 01:56:17 +03:00
}
return res, nil
2021-03-25 16:25:36 +02:00
}
2021-03-25 18:50:53 +02:00
2021-04-02 01:56:17 +03:00
func (c *SEClient) ResetCookies() error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
return c.resetCookies()
}
2021-04-02 01:56:17 +03:00
2021-04-02 13:23:36 +03:00
func (c *SEClient) resetCookies() error {
return (c.httpClient.Jar.(*StdJar)).Reset()
2021-04-02 01:56:17 +03:00
}
2021-04-01 23:40:09 +03:00
2021-03-25 19:58:30 +02:00
func (c *SEClient) AnonRegister(ctx context.Context) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
2021-03-25 19:58:30 +02:00
localPart, err := randomEmailLocalPart(c.rng)
2021-03-25 18:50:53 +02:00
if err != nil {
2021-03-25 19:58:30 +02:00
return err
2021-03-25 18:50:53 +02:00
}
2021-03-25 19:58:30 +02:00
2021-04-02 13:23:36 +03:00
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail)
2021-03-25 19:58:30 +02:00
2021-04-02 13:23:36 +03:00
return c.register(ctx)
2021-03-25 19:58:30 +02:00
}
func (c *SEClient) Register(ctx context.Context) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
return c.register(ctx)
}
func (c *SEClient) register(ctx context.Context) error {
err := c.resetCookies()
if err != nil {
return err
}
2021-03-25 20:29:37 +02:00
var regRes SERegisterSubscriberResponse
2021-04-02 13:23:36 +03:00
err = c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
2021-04-02 00:46:17 +03:00
"email": c.SubscriberEmail,
2021-04-01 23:40:09 +03:00
"password": c.SubscriberPassword,
}, &regRes)
2021-03-25 20:29:37 +02:00
if err != nil {
return err
}
if regRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return newAPIError(regRes.Status)
2021-03-25 20:29:37 +02:00
}
2021-03-25 19:58:30 +02:00
return nil
}
2021-03-25 22:09:36 +02:00
func (c *SEClient) RegisterDevice(ctx context.Context) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
2021-03-25 22:09:36 +02:00
var regRes SERegisterDeviceResponse
2021-04-02 13:23:36 +03:00
err := c.rpcCall(ctx, c.Settings.Endpoints.RegisterDevice, StrKV{
2021-04-01 23:40:09 +03:00
"client_type": c.Settings.ClientType,
"device_hash": c.DeviceID,
"device_name": c.Settings.DeviceName,
}, &regRes)
2021-03-25 22:09:36 +02:00
if err != nil {
return err
}
if regRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return newAPIError(regRes.Status)
2021-03-25 22:09:36 +02:00
}
c.AssignedDeviceID = regRes.Data.DeviceID
c.DevicePassword = regRes.Data.DevicePassword
c.AssignedDeviceIDHash = capitalHexSHA1(regRes.Data.DeviceID)
return nil
}
2021-03-26 21:52:09 +02:00
func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
2021-03-26 21:52:09 +02:00
var geoListRes SEGeoListResponse
2021-04-02 13:23:36 +03:00
err := c.rpcCall(ctx, c.Settings.Endpoints.GeoList, StrKV{
2021-04-01 23:40:09 +03:00
"device_id": c.AssignedDeviceIDHash,
}, &geoListRes)
2021-03-26 21:52:09 +02:00
if err != nil {
return nil, err
}
if geoListRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return nil, newAPIError(geoListRes.Status)
2021-03-26 21:52:09 +02:00
}
return geoListRes.Data.Geos, nil
}
2021-03-26 22:09:37 +02:00
func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEntry, error) {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
2021-03-26 22:09:37 +02:00
var discoverRes SEDiscoverResponse
2021-04-02 13:23:36 +03:00
err := c.rpcCall(ctx, c.Settings.Endpoints.Discover, StrKV{
2021-04-01 23:40:09 +03:00
"serial_no": c.AssignedDeviceIDHash,
"requested_geo": requestedGeo,
}, &discoverRes)
2021-03-26 22:09:37 +02:00
if err != nil {
return nil, err
}
if discoverRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return nil, newAPIError(discoverRes.Status)
2021-03-26 22:09:37 +02:00
}
return discoverRes.Data.IPs, nil
}
2021-03-30 15:51:20 +03:00
func (c *SEClient) Login(ctx context.Context) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
err := c.resetCookies()
2021-04-02 01:56:17 +03:00
if err != nil {
return err
}
2021-04-01 23:40:09 +03:00
var loginRes SESubscriberLoginResponse
2021-04-02 13:23:36 +03:00
err = c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
2021-04-01 23:40:09 +03:00
"login": c.SubscriberEmail,
"password": c.SubscriberPassword,
"client_type": c.Settings.ClientType,
}, &loginRes)
if err != nil {
return err
}
if loginRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return newAPIError(loginRes.Status)
2021-04-01 23:40:09 +03:00
}
return nil
}
func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
var genRes SEDeviceGeneratePasswordResponse
2021-04-02 13:23:36 +03:00
err := c.rpcCall(ctx, c.Settings.Endpoints.DeviceGeneratePassword, StrKV{
"device_id": c.AssignedDeviceID,
}, &genRes)
if err != nil {
return err
}
if genRes.Status.Code != SE_STATUS_OK {
2026-04-26 15:35:01 +03:00
return newAPIError(genRes.Status)
}
c.DevicePassword = genRes.Data.DevicePassword
return nil
}
2021-04-01 23:40:09 +03:00
func (c *SEClient) GetProxyCredentials() (string, string) {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
return c.AssignedDeviceIDHash, c.DevicePassword
2021-04-01 23:40:09 +03:00
}
func (c *SEClient) populateRequest(req *http.Request) {
req.Header["SE-Client-Version"] = []string{c.Settings.ClientVersion}
req.Header["SE-Operating-System"] = []string{c.Settings.OperatingSystem}
req.Header["User-Agent"] = []string{c.Settings.UserAgent}
}
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
2021-04-02 13:23:36 +03:00
c.Mux.Lock()
defer c.Mux.Unlock()
return c.rpcCall(ctx, endpoint, params, res)
}
func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
2021-04-01 23:40:09 +03:00
input := make(url.Values)
for k, v := range params {
input[k] = []string{v}
2021-03-30 15:51:20 +03:00
}
req, err := http.NewRequestWithContext(
ctx,
"POST",
2021-04-01 23:40:09 +03:00
endpoint,
strings.NewReader(input.Encode()),
2021-03-30 15:51:20 +03:00
)
if err != nil {
return err
}
c.populateRequest(req)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
2021-04-02 13:23:36 +03:00
resp, err := c.httpClient.Do(req)
2021-03-30 15:51:20 +03:00
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
2021-04-02 01:56:17 +03:00
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
2021-03-30 15:51:20 +03:00
}
decoder := json.NewDecoder(resp.Body)
2021-04-01 23:40:09 +03:00
err = decoder.Decode(res)
2021-03-30 15:51:20 +03:00
cleanupBody(resp.Body)
2021-04-01 23:40:09 +03:00
2021-03-30 15:51:20 +03:00
if err != nil {
return err
}
return nil
}
2021-03-25 20:29:37 +02:00
// Does cleanup of HTTP response in order to make it reusable by keep-alive
// logic of HTTP client
func cleanupBody(body io.ReadCloser) {
2021-03-25 20:45:48 +02:00
io.Copy(ioutil.Discard, &io.LimitedReader{
R: body,
2021-03-25 20:29:37 +02:00
N: READ_LIMIT,
})
body.Close()
}