diff --git a/client/middle.go b/client/middle.go index 53bb64c..4e50895 100644 --- a/client/middle.go +++ b/client/middle.go @@ -17,9 +17,16 @@ func MiddleInit(socket net.Conn, connID string, conf *config.Config) (wrappers.W } connStream := conn.(wrappers.StreamReadWriteCloser) - newConn := wrappers.NewMTProtoAbridged(connStream, opts) - if opts.ConnectionType != mtproto.ConnectionTypeAbridged { + var newConn wrappers.PacketReadWriteCloser + switch opts.ConnectionType { + case mtproto.ConnectionTypeAbridged: + newConn = wrappers.NewMTProtoAbridged(connStream, opts) + case mtproto.ConnectionTypeIntermediate: newConn = wrappers.NewMTProtoIntermediate(connStream, opts) + case mtproto.ConnectionTypeSecure: + newConn = wrappers.NewMTProtoIntermediateSecure(connStream, opts) + default: + panic("Unknown connection type") } opts.ConnectionProto = mtproto.ConnectionProtocolIPv4 diff --git a/config/config.go b/config/config.go index 396d1fa..aaab626 100644 --- a/config/config.go +++ b/config/config.go @@ -18,8 +18,9 @@ const ( // Config represents common configuration of mtg. type Config struct { - Debug bool - Verbose bool + Debug bool + Verbose bool + SecureMode bool BindPort uint16 PublicIPv4Port uint16 @@ -45,8 +46,9 @@ type URLs struct { // IPURLs contains links to both ipv4 and ipv6 of the proxy. type IPURLs struct { - IPv4 URLs `json:"ipv4"` - IPv6 URLs `json:"ipv6"` + IPv4 URLs `json:"ipv4"` + IPv6 URLs `json:"ipv6"` + BotSecret string `json:"secret_for_mtproxybot"` } // BindAddr returns connection for this server to bind to. @@ -65,15 +67,32 @@ func (c *Config) UseMiddleProxy() bool { return len(c.AdTag) > 0 } +// BotSecretString returns secret string which should work with MTProxybot. +func (c *Config) BotSecretString() string { + return hex.EncodeToString(c.Secret) +} + +// SecretString returns a secret in a form entered on the start of the +// application. +func (c *Config) SecretString() string { + secret := c.BotSecretString() + if c.SecureMode { + return "dd" + secret + } + return secret +} + // GetURLs returns configured IPURLs instance with links to this server. func (c *Config) GetURLs() IPURLs { urls := IPURLs{} + secret := c.SecretString() if c.PublicIPv4 != nil { - urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, c.Secret) + urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret) } if c.PublicIPv6 != nil { - urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, c.Secret) + urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret) } + urls.BotSecret = c.BotSecretString() return urls } @@ -91,8 +110,11 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo publicIPv6 net.IP, publicIPv6Port uint16, statsIP net.IP, statsPort uint16, secret, adtag string) (*Config, error) { - secret = strings.TrimPrefix(secret, "dd") - if len(secret) != 32 { + secureMode := false + if strings.HasPrefix(secret, "dd") && len(secret) == 34 { + secureMode = true + secret = strings.TrimPrefix(secret, "dd") + } else if len(secret) != 32 { return nil, errors.New("Telegram demands secret of length 32") } secretBytes, err := hex.DecodeString(secret) @@ -149,6 +171,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo StatsPort: statsPort, Secret: secretBytes, AdTag: adTagBytes, + SecureMode: secureMode, } return conf, nil diff --git a/config/urls.go b/config/urls.go index 7aada07..bfa1def 100644 --- a/config/urls.go +++ b/config/urls.go @@ -1,17 +1,16 @@ package config import ( - "encoding/hex" "net" "net/url" "strconv" ) -func getURLs(addr net.IP, port uint16, secret []byte) (urls URLs) { +func getURLs(addr net.IP, port uint16, secret string) (urls URLs) { values := url.Values{} values.Set("server", addr.String()) values.Set("port", strconv.Itoa(int(port))) - values.Set("secret", hex.EncodeToString(secret)) + values.Set("secret", secret) urls.TG = makeTGURL(values) urls.TMe = makeTMeURL(values) diff --git a/mtproto/rpc/proxy_flags.go b/mtproto/rpc/proxy_flags.go index 5599537..0167162 100644 --- a/mtproto/rpc/proxy_flags.go +++ b/mtproto/rpc/proxy_flags.go @@ -15,6 +15,7 @@ const ( proxyRequestFlagsIntermediate = 0x20000000 proxyRequestFlagsAbdridged = 0x40000000 proxyRequestFlagsQuickAck = 0x80000000 + proxyRequestFlagsPad = 0x8000000 ) var proxyRequestFlagsEncryptedPrefix [8]byte @@ -50,6 +51,9 @@ func (r proxyRequestFlags) String() string { if r&proxyRequestFlagsQuickAck != 0 { flags = append(flags, "QUICK_ACK") } + if r&proxyRequestFlagsPad != 0 { + flags = append(flags, "PAD") + } return strings.Join(flags, " | ") } diff --git a/mtproto/rpc/proxy_request.go b/mtproto/rpc/proxy_request.go index 2fda394..b377fbe 100644 --- a/mtproto/rpc/proxy_request.go +++ b/mtproto/rpc/proxy_request.go @@ -67,10 +67,15 @@ func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer) func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr, opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) { flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2 - if opts.ConnectionType == mtproto.ConnectionTypeAbridged { + switch opts.ConnectionType { + case mtproto.ConnectionTypeAbridged: flags |= proxyRequestFlagsAbdridged - } else { + case mtproto.ConnectionTypeIntermediate: flags |= proxyRequestFlagsIntermediate + case mtproto.ConnectionTypeSecure: + flags |= proxyRequestFlagsIntermediate | proxyRequestFlagsPad + default: + panic("Unknown connection type") } request := &ProxyRequest{ diff --git a/wrappers/mtproto_intermediate.go b/wrappers/mtproto_intermediate.go index ab0761c..5b88d74 100644 --- a/wrappers/mtproto_intermediate.go +++ b/wrappers/mtproto_intermediate.go @@ -62,10 +62,6 @@ func (m *MTProtoIntermediate) Read() ([]byte, error) { return nil, errors.Annotate(err, "Cannot read the message") } - if length%4 != 0 { - length -= length % 4 - } - return buf.Bytes()[:length], nil } @@ -80,7 +76,7 @@ func (m *MTProtoIntermediate) Write(p []byte) (int, error) { "counter", m.writeCounter, ) - if m.opts.ReadHacks.SimpleAck { + if m.opts.WriteHacks.SimpleAck { return m.conn.Write(p) } diff --git a/wrappers/mtproto_intermediate_secure.go b/wrappers/mtproto_intermediate_secure.go new file mode 100644 index 0000000..b5aa362 --- /dev/null +++ b/wrappers/mtproto_intermediate_secure.go @@ -0,0 +1,74 @@ +package wrappers + +import ( + "bytes" + "encoding/binary" + "math/rand" + + "github.com/9seconds/mtg/mtproto" +) + +// MTProtoIntermediateSecure is an extension of MTProtoIntermediate +// mode which supports random paddings (socalled 'secure mode' or +// 'dd-secrets'). +type MTProtoIntermediateSecure struct { + MTProtoIntermediate +} + +func (m *MTProtoIntermediateSecure) Read() ([]byte, error) { + data, err := m.MTProtoIntermediate.Read() + if err != nil { + return nil, err + } + length := len(data) - (len(data) % 4) + + return data[:length], nil +} + +func (m *MTProtoIntermediateSecure) Write(p []byte) (int, error) { + defer func() { + m.writeCounter++ + }() + + m.logger.Debugw("Write packet", + "simple_ack", m.opts.WriteHacks.SimpleAck, + "quick_ack", m.opts.WriteHacks.QuickAck, + "counter", m.writeCounter, + ) + + if m.opts.WriteHacks.SimpleAck { + return m.conn.Write(p) + } + + buf := &bytes.Buffer{} + paddingLength := rand.Intn(4) + buf.Grow(4 + len(p) + paddingLength) + + binary.Write(buf, binary.LittleEndian, uint32(len(p)+paddingLength)) // nolint: errcheck + buf.Write(p) + buf.Write(make([]byte, paddingLength)) + + m.logger.Debugw("Write packet with padding", + "simple_ack", m.opts.WriteHacks.SimpleAck, + "quick_ack", m.opts.WriteHacks.QuickAck, + "counter", m.writeCounter, + "padding_length", paddingLength, + "length", len(p), + ) + + _, err := m.conn.Write(buf.Bytes()) + + return len(p), err +} + +// NewMTProtoIntermediateSecure create new instance of +// MTProtoIntermediateSecure instance. +func NewMTProtoIntermediateSecure(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser { + return &MTProtoIntermediateSecure{ + MTProtoIntermediate: MTProtoIntermediate{ + conn: conn, + logger: conn.Logger().Named("mtproto-intermediate-secure"), + opts: opts, + }, + } +}