diff --git a/errors.go b/errors.go index cf5a7f41..f8f126da 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,6 @@ // Copyright (c) 2022 RoseLoverX -package mtproto +package gogram import ( "fmt" diff --git a/handshake.go b/handshake.go index d4cbbf92..e72b8eb2 100644 --- a/handshake.go +++ b/handshake.go @@ -1,6 +1,6 @@ // Copyright (c) 2022 RoseLoverX -package mtproto +package gogram import ( "bytes" diff --git a/internal/aes_ige/aes.go b/internal/aes_ige/aes.go index 500abf0d..0b3831d3 100644 --- a/internal/aes_ige/aes.go +++ b/internal/aes_ige/aes.go @@ -1,49 +1,88 @@ -// Copyright (c) 2022 RoseLoverX +// Copyright (c) 2022,RoseLoverX package ige import ( "bytes" "crypto/aes" + "crypto/rand" + "crypto/sha256" "math/big" - utils "github.com/amarnathcjd/gogram/internal/encoding/tl" + "github.com/amarnathcjd/gogram/internal/utils" ) type AesBlock [aes.BlockSize]byte type AesKV [32]byte type AesIgeBlock [48]byte -func MessageKey(msg []byte) []byte { - return utils.Sha1(string(msg))[4:20] +func MessageKey(authKey, msgPadded []byte, decode bool) []byte { + var x int + if decode { + x = 8 + } else { + x = 0 + } + + // `msg_key_large = SHA256 (substr (auth_key, 88+x, 32) + plaintext + random_padding);` + var msgKeyLarge [sha256.Size]byte + { + h := sha256.New() + + substr := authKey[88+x:] + _, _ = h.Write(substr[:32]) + _, _ = h.Write(msgPadded) + + h.Sum(msgKeyLarge[:0]) + } + r := make([]byte, 16) + // `msg_key = substr (msg_key_large, 8, 16);` + copy(r, msgKeyLarge[8:8+16]) + return r } -func Encrypt(msg, key []byte) ([]byte, error) { - msgKey := MessageKey(msg) - aesKey, aesIV := generateAESIGE(msgKey, key, false) +func Encrypt(msg, authKey []byte) (out, msgKey []byte, _ error) { + return encrypt(msg, authKey, false) +} +func encrypt(msg, authKey []byte, decode bool) (out, msgKey []byte, _ error) { // СУДЯ ПО ВСЕМУ вообще не уверен, но это видимо паддинг для добива блока, чтоб он делился на 256 бит - data := make([]byte, len(msg)+((16-(len(msg)%16))&15)) - copy(data, msg) + padding := 16 + (16-(len(msg)%16))&15 + data := make([]byte, len(msg)+padding) + n := copy(data, msg) + + // Fill padding using secure PRNG. + // + // See https://core.telegram.org/mtproto/description#encrypted-message-encrypted-data. + if _, err := rand.Read(data[n:]); err != nil { + return nil, nil, err + } - c, err := NewCipher(aesKey, aesIV) + msgKey = MessageKey(authKey, data, decode) + aesKey, aesIV := aesKeys(msgKey[:], authKey, decode) + + c, err := NewCipher(aesKey[:], aesIV[:]) if err != nil { - return nil, err + return nil, nil, err } - out := make([]byte, len(data)) + out = make([]byte, len(data)) if err := c.doAES256IGEencrypt(data, out); err != nil { - return nil, err + return nil, nil, err } - return out, nil + return out, msgKey, nil } // checkData это msgkey в понятиях мтпрото, нужно что бы проверить, успешно ли прошла расшифровка -func Decrypt(msg, key, checkData []byte) ([]byte, error) { - aesKey, aesIV := generateAESIGE(checkData, key, true) +func Decrypt(msg, authKey, checkData []byte) ([]byte, error) { + return decrypt(msg, authKey, checkData, true) +} + +func decrypt(msg, authKey, msgKey []byte, decode bool) ([]byte, error) { + aesKey, aesIV := aesKeys(msgKey, authKey, decode) - c, err := NewCipher(aesKey, aesIV) + c, err := NewCipher(aesKey[:], aesIV[:]) if err != nil { return nil, err } @@ -77,7 +116,9 @@ func DecryptMessageWithTempKeys(msg []byte, nonceSecond, nonceServer *big.Int) [ key, iv := generateTempKeys(nonceSecond, nonceServer) decodedWithHash := make([]byte, len(msg)) err := doAES256IGEdecrypt(msg, decodedWithHash, key, iv) - check(err) + if err != nil { + panic(err) + } // decodedWithHash := SHA1(answer) + answer + (0-15 рандомных байт); длина должна делиться на 16; decodedHash := decodedWithHash[:20] @@ -111,7 +152,9 @@ func encryptMessageWithTempKeys(msg []byte, nonceSecond, nonceServer *big.Int) [ encodedWithHash := make([]byte, len(msg)) err := doAES256IGEencrypt(msg, encodedWithHash, key, iv) - check(err) + if err != nil { + panic(err) + } return encodedWithHash } diff --git a/internal/aes_ige/extra.go b/internal/aes_ige/extra.go deleted file mode 100644 index b8773a6f..00000000 --- a/internal/aes_ige/extra.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2022 RoseLoverX - -package ige - -type any = interface{} -type null = struct{} - -func check(err error) { - if err != nil { - panic(err) - } -} diff --git a/internal/aes_ige/ige_cipher.go b/internal/aes_ige/ige_cipher.go index 4ac9bc3b..452dbcd4 100644 --- a/internal/aes_ige/ige_cipher.go +++ b/internal/aes_ige/ige_cipher.go @@ -1,13 +1,13 @@ -// Copyright (c) 2022 RoseLoverX +// Copyright (c) 2022,RoseLoverX package ige import ( "crypto/aes" "crypto/cipher" - "fmt" + "crypto/sha256" - utils "github.com/amarnathcjd/gogram/internal/encoding/tl" + "github.com/amarnathcjd/gogram/internal/utils" "github.com/pkg/errors" ) @@ -48,9 +48,9 @@ func (c *Cipher) doAES256IGEencrypt(in, out []byte) error { //nolint:dupl пот } for i := 0; i < len(in); i += aes.BlockSize { - xor(c.x, in[i:i+aes.BlockSize]) + utils.Xor(c.x, in[i:i+aes.BlockSize]) c.block.Encrypt(c.t, c.x) - xor(c.t, c.y) + utils.Xor(c.t, c.y) c.x, c.y = c.t, in[i:i+aes.BlockSize] copy(out[i:], c.t) } @@ -63,9 +63,9 @@ func (c *Cipher) doAES256IGEdecrypt(in, out []byte) error { //nolint:dupl пот } for i := 0; i < len(in); i += aes.BlockSize { - xor(c.y, in[i:i+aes.BlockSize]) + utils.Xor(c.y, in[i:i+aes.BlockSize]) c.block.Decrypt(c.t, c.y) - xor(c.t, c.x) + utils.Xor(c.t, c.x) c.y, c.x = c.t, in[i:i+aes.BlockSize] copy(out[i:], c.t) } @@ -84,21 +84,7 @@ func isCorrectData(data []byte) error { // -------------------------------------------------------------------------------------------------- -// generateAESIGEv2 это переписанная функция generateAESIGE, которая выглядить чуточку более понятно. -func generateAESIGEv2(msgKey, authKey []byte, decode bool) (aesKey, aesIv []byte) { //nolint:deadcode wait for it - var ( - kvBlock [2]AesKV - igeBlock [4]AesIgeBlock - ) - - aesKey = kvBlock[0][:] - aesIv = kvBlock[1][:] - - tA := igeBlock[0][:] - tB := igeBlock[1][:] - tC := igeBlock[2][:] - tD := igeBlock[3][:] - +func aesKeys(msgKey, authKey []byte, decode bool) (aesKey, aesIv [32]byte) { var x int if decode { x = 8 @@ -106,101 +92,41 @@ func generateAESIGEv2(msgKey, authKey []byte, decode bool) (aesKey, aesIv []byte x = 0 } - var ( - step = 32 - tAOffStart = x - tAOffEnd = tAOffStart + step - - tBOffP0Start = tAOffEnd - tBOffP0End = tBOffP0Start + aes.BlockSize - - tBOffP1Start = tAOffEnd + aes.BlockSize - tBOffP1End = tBOffP1Start + aes.BlockSize - - tCOffStart = x + 64 - tCOffEnd = tCOffStart + step - - tDOffStart = x + 96 - tDOffEnd = tDOffStart + step - ) - - tA = append(tA, msgKey...) - tA = append(tA, authKey[tAOffStart:tAOffEnd]...) - - tB = append(tB, authKey[tBOffP0Start:tBOffP0End]...) - tB = append(tB, msgKey...) - tB = append(tB, authKey[tBOffP1Start:tBOffP1End]...) - - tC = append(tC, authKey[tCOffStart:tCOffEnd]...) - tC = append(tC, msgKey...) - - tD = append(tD, msgKey...) - tD = append(tD, authKey[tDOffStart:tDOffEnd]...) - - sha1PartA := utils.Sha1Byte(tA) - sha1PartB := utils.Sha1Byte(tB) - sha1PartC := utils.Sha1Byte(tC) - sha1PartD := utils.Sha1Byte(tD) - - aesKey = append(aesKey, sha1PartA[0:8]...) - aesKey = append(aesKey, sha1PartB[8:8+12]...) - aesKey = append(aesKey, sha1PartC[4:4+12]...) - - aesIv = append(aesIv, sha1PartA[8:8+12]...) - aesIv = append(aesIv, sha1PartB[0:8]...) - aesIv = append(aesIv, sha1PartC[16:16+4]...) - aesIv = append(aesIv, sha1PartD[0:8]...) - - return aesKey, aesIv -} - -// generateAESIGE ЭТО ЕБАНАЯ МАГИЧЕСКАЯ ФУНКЦИЯ ОНА НАХУЙ РАБОТАЕТ ПРОСТО БЛЯТЬ НЕ ТРОГАЙ ШАКАЛ ЕБАНЫЙ -//nolint:godox ты че ебанулся // TODO: порезать себе вены -func generateAESIGE(msg_key, auth_key []byte, decode bool) ([]byte, []byte) { - var x int - if decode { - x = 8 - } else { - x = 0 + // aes_key = substr (sha256_a, 0, 8) + substr (sha256_b, 8, 16) + substr (sha256_a, 24, 8); + computeAesKey := func(sha256a, sha256b []byte) (v [32]byte) { + n := copy(v[:], sha256a[:8]) + n += copy(v[n:], sha256b[8:16+8]) + copy(v[n:], sha256a[24:24+8]) + return v } - - if len(auth_key) < 96+x+32 { - panic(fmt.Sprintf("wrong len of auth key, got %v want at least %v", len(auth_key), 96+x+32)) + // aes_iv = substr (sha256_b, 0, 8) + substr (sha256_a, 8, 16) + substr (sha256_b, 24, 8); + computeAesIV := func(sha256b, sha256a []byte) (v [32]byte) { + n := copy(v[:], sha256a[:8]) + n += copy(v[n:], sha256b[8:16+8]) + copy(v[n:], sha256a[24:24+8]) + return v } - aes_key := make([]byte, 0, 32) - aes_iv := make([]byte, 0, 32) - t_a := make([]byte, 0, 48) - t_b := make([]byte, 0, 48) - t_c := make([]byte, 0, 48) - t_d := make([]byte, 0, 48) - - t_a = append(t_a, msg_key...) - t_a = append(t_a, auth_key[x:x+32]...) - - t_b = append(t_b, auth_key[32+x:32+x+16]...) - t_b = append(t_b, msg_key...) - t_b = append(t_b, auth_key[48+x:48+x+16]...) + var sha256a, sha256b [256]byte + // sha256_a = SHA256 (msg_key + substr (auth_key, x, 36)); + { + h := sha256.New() - t_c = append(t_c, auth_key[64+x:64+x+32]...) - t_c = append(t_c, msg_key...) + _, _ = h.Write(msgKey) + _, _ = h.Write(authKey[x : x+36]) - t_d = append(t_d, msg_key...) - t_d = append(t_d, auth_key[96+x:96+x+32]...) - - sha1_a := utils.Sha1Byte(t_a) - sha1_b := utils.Sha1Byte(t_b) - sha1_c := utils.Sha1Byte(t_c) - sha1_d := utils.Sha1Byte(t_d) + h.Sum(sha256a[:0]) + } + // sha256_b = SHA256 (substr (auth_key, 40+x, 36) + msg_key); + { + h := sha256.New() - aes_key = append(aes_key, sha1_a[0:8]...) - aes_key = append(aes_key, sha1_b[8:8+12]...) - aes_key = append(aes_key, sha1_c[4:4+12]...) + substr := authKey[40+x:] + _, _ = h.Write(substr[:36]) + _, _ = h.Write(msgKey) - aes_iv = append(aes_iv, sha1_a[8:8+12]...) - aes_iv = append(aes_iv, sha1_b[0:8]...) - aes_iv = append(aes_iv, sha1_c[16:16+4]...) - aes_iv = append(aes_iv, sha1_d[0:8]...) + h.Sum(sha256b[:0]) + } - return aes_key, aes_iv + return computeAesKey(sha256a[:], sha256b[:]), computeAesIV(sha256a[:], sha256b[:]) } diff --git a/internal/aes_ige/utils.go b/internal/aes_ige/utils.go deleted file mode 100644 index ee90fe11..00000000 --- a/internal/aes_ige/utils.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2022 RoseLoverX - -package ige - -// побайтовый xor -func xor(dst, src []byte) { - for i := range dst { - dst[i] ^= src[i] - } -} diff --git a/internal/encoding/tl/errors.go b/internal/encoding/tl/errors.go index fa9ab761..1146ad94 100644 --- a/internal/encoding/tl/errors.go +++ b/internal/encoding/tl/errors.go @@ -1,8 +1,4 @@ -// Copyright£ (c) 2020-2021 KHS Films -// -// This file is a part of mtproto package. -// See https://github.com/amarnathcjd/gogram/blob/master/LICENSE for details - +// Copyright (c) 2022,RoseLoverX package tl import "fmt" @@ -16,7 +12,7 @@ func (e *ErrRegisteredObjectNotFound) Error() string { return fmt.Sprintf("object with provided crc not registered: 0x%08x", e.Crc) } -type ErrMustParseSlicesExplicitly null +type ErrMustParseSlicesExplicitly struct{} func (e *ErrMustParseSlicesExplicitly) Error() string { return "got vector CRC code when parsing unknown object: vectors can't be parsed as predicted objects" diff --git a/internal/encoding/tl/extra.go b/internal/encoding/tl/extra.go deleted file mode 100644 index 7cea563f..00000000 --- a/internal/encoding/tl/extra.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2022 RoseLoverX - -package tl - -type any = interface{} -type null = struct{} diff --git a/internal/encoding/tl/object.go b/internal/encoding/tl/object.go index ede09d35..48a90566 100644 --- a/internal/encoding/tl/object.go +++ b/internal/encoding/tl/object.go @@ -24,20 +24,20 @@ type Unmarshaler interface { //==========================================================================================================// // PseudoTrue is a support struct which is required to get native -type PseudoTrue null +type PseudoTrue struct{} func (*PseudoTrue) CRC() uint32 { return CrcTrue } // PseudoFalse is a support struct which is required to get native -type PseudoFalse null +type PseudoFalse struct{} func (*PseudoFalse) CRC() uint32 { return CrcFalse } -type PseudoNil null +type PseudoNil struct{} func (*PseudoNil) CRC() uint32 { return CrcNull diff --git a/internal/encoding/tl/register.go b/internal/encoding/tl/register.go index 2d8bfc0b..0cdad91a 100644 --- a/internal/encoding/tl/register.go +++ b/internal/encoding/tl/register.go @@ -11,7 +11,7 @@ import ( var ( // used by decoder, guaranteed that types are convertible to tl.Object objectByCrc = make(map[uint32]reflect.Type) // this value setting by registerObject(), DO NOT CALL IT BY HANDS - enumCrcs = make(map[uint32]null) + enumCrcs = make(map[uint32]struct{}) ) func registerObject(o Object) { @@ -23,7 +23,7 @@ func registerObject(o Object) { func registerEnum(o Object) { registerObject(o) - enumCrcs[o.CRC()] = null{} + enumCrcs[o.CRC()] = struct{}{} } func RegisterObjects(obs ...Object) { diff --git a/internal/mtproto/messages/messages.go b/internal/mtproto/messages/messages.go index ea88c16e..bfe5600b 100644 --- a/internal/mtproto/messages/messages.go +++ b/internal/mtproto/messages/messages.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 RoseLoverX +// Copyright (c) 2022,RoseLoverX package messages @@ -10,11 +10,10 @@ import ( "encoding/binary" "fmt" - "github.com/pkg/errors" - ige "github.com/amarnathcjd/gogram/internal/aes_ige" "github.com/amarnathcjd/gogram/internal/encoding/tl" "github.com/amarnathcjd/gogram/internal/utils" + "github.com/pkg/errors" ) // Common это сообщение (зашифрованое либо открытое) которыми общаются между собой клиент и сервер @@ -37,7 +36,7 @@ type Encrypted struct { func (msg *Encrypted) Serialize(client MessageInformator, requireToAck bool, seqNo int32) ([]byte, error) { obj := serializePacket(client, msg.Msg, msg.MsgID, requireToAck, seqNo) - encryptedData, err := ige.Encrypt(obj, client.GetAuthKey()) + encryptedData, msgKey, err := ige.Encrypt(obj, client.GetAuthKey()) if err != nil { return nil, errors.Wrap(err, "encrypting") } @@ -46,7 +45,7 @@ func (msg *Encrypted) Serialize(client MessageInformator, requireToAck bool, seq e := tl.NewEncoder(buf) e.PutRawBytes(utils.AuthKeyHash(client.GetAuthKey())) - e.PutRawBytes(ige.MessageKey(obj)) + e.PutRawBytes(msgKey) e.PutRawBytes(encryptedData) return buf.Bytes(), nil @@ -92,8 +91,8 @@ func DeserializeEncrypted(data, authKey []byte) (*Encrypted, error) { } // этот кусок проверяет валидность данных по ключу - trimed := decrypted[0 : 32+messageLen] // суммарное сообщение, после расшифровки - if !bytes.Equal(utils.Sha1Byte(trimed)[4:20], msg.MsgKey) { + msgKey := ige.MessageKey(authKey, decrypted, true) + if !bytes.Equal(msgKey, msg.MsgKey) { return nil, errors.New("wrong message key, can't trust to sender") } msg.Msg = d.PopRawBytes(int(messageLen)) diff --git a/internal/mtproto/objects/extra.go b/internal/mtproto/objects/extra.go deleted file mode 100644 index 618d5850..00000000 --- a/internal/mtproto/objects/extra.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2022 RoseLoverX - -package objects - -type any = interface{} -type null = struct{} diff --git a/internal/mtproto/objects/types.go b/internal/mtproto/objects/types.go index 8dd56957..51f1f5bf 100644 --- a/internal/mtproto/objects/types.go +++ b/internal/mtproto/objects/types.go @@ -189,7 +189,7 @@ type RpcDropAnswer interface { ImplementsRpcDropAnswer() } -type RpcAnswerUnknown null +type RpcAnswerUnknown struct{} func (*RpcAnswerUnknown) ImplementsRpcDropAnswer() {} @@ -197,7 +197,7 @@ func (*RpcAnswerUnknown) CRC() uint32 { return 0x5e2ad36e //nolint:gomnd not magic } -type RpcAnswerDroppedRunning null +type RpcAnswerDroppedRunning struct{} func (*RpcAnswerDroppedRunning) ImplementsRpcDropAnswer() {} diff --git a/internal/transport/conn_tcp.go b/internal/transport/connection.go similarity index 79% rename from internal/transport/conn_tcp.go rename to internal/transport/connection.go index 0622dbf2..c98f9b6a 100644 --- a/internal/transport/conn_tcp.go +++ b/internal/transport/connection.go @@ -19,9 +19,13 @@ type TCPConnConfig struct { Ctx context.Context Host string Timeout time.Duration + Socks *Socks } func NewTCP(cfg TCPConnConfig) (Conn, error) { + if cfg.Socks.Host != "" { + return newSocksTCP(cfg) + } tcpAddr, err := net.ResolveTCPAddr("tcp", cfg.Host) if err != nil { return nil, errors.Wrap(err, "resolving tcp") @@ -38,6 +42,18 @@ func NewTCP(cfg TCPConnConfig) (Conn, error) { }, nil } +func newSocksTCP(cfg TCPConnConfig) (Conn, error) { + conn, err := DialProxy(cfg.Socks, "tcp", cfg.Host) + if err != nil { + return nil, err + } + return &tcpConn{ + cancelReader: NewCancelableReader(cfg.Ctx, conn), + conn: conn.(*net.TCPConn), + timeout: cfg.Timeout, + }, nil +} + func (t *tcpConn) Close() error { return t.conn.Close() } diff --git a/internal/transport/socks.go b/internal/transport/socks.go new file mode 100644 index 00000000..e3ac47b3 --- /dev/null +++ b/internal/transport/socks.go @@ -0,0 +1,211 @@ +package transport + +import ( + "errors" + "io" + "net" + "strconv" +) + +type Socks struct { + Host string + Port int + Username string + Password string + Version int +} + +func DialProxy(s *Socks, network, addr string) (net.Conn, error) { + var ( + conn net.Conn + err error + ) + switch s.Version { + case 5: + conn, err = DialSocks5(s, network, addr) + case 4: + conn, err = DialSocks4(s, network, addr) + default: + return nil, errors.New("unsupported socks version") + } + if err != nil { + return nil, err + } + return conn, nil +} + +func DialSocks5(s *Socks, network, addr string) (net.Conn, error) { + conn, err := net.Dial("tcp", s.Host+":"+strconv.Itoa(s.Port)) + if err != nil { + return nil, err + } + // auth + if s.Username != "" { + _, err = conn.Write([]byte{5, 2, 0, 2}) + if err != nil { + return nil, err + } + buf := make([]byte, 2) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + if buf[0] != 5 { + return nil, errors.New("socks version not supported") + } + if buf[1] == 0 { + // no auth + } else if buf[1] == 2 { + // username/password + _, err = conn.Write(append([]byte{1, byte(len(s.Username))}, []byte(s.Username)...)) + if err != nil { + return nil, err + } + _, err = conn.Write(append([]byte{byte(len(s.Password))}, []byte(s.Password)...)) + if err != nil { + return nil, err + } + buf = make([]byte, 2) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + if buf[0] != 1 { + return nil, errors.New("socks version not supported") + } + if buf[1] != 0 { + return nil, errors.New("socks auth failed") + } + } else { + return nil, errors.New("socks auth method not supported") + } + } else { + _, err = conn.Write([]byte{5, 1, 0}) + if err != nil { + return nil, err + } + buf := make([]byte, 2) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + if buf[0] != 5 { + return nil, errors.New("socks version not supported") + } + if buf[1] != 0 { + return nil, errors.New("socks auth failed") + } + } + // connect + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + ip := net.ParseIP(host) + var atyp byte + var dst []byte + if ip == nil { + atyp = 3 + dst = append([]byte{byte(len(host))}, []byte(host)...) + } + if ip4 := ip.To4(); ip4 != nil { + atyp = 1 + dst = ip4 + } + if ip6 := ip.To16(); ip6 != nil { + atyp = 4 + dst = ip6 + } + p, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + _, err = conn.Write([]byte{5, 1, 0, atyp}) + if err != nil { + return nil, err + } + _, err = conn.Write(append(dst, byte(p>>8), byte(p))) + if err != nil { + return nil, err + } + buf := make([]byte, 4) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + if buf[0] != 5 { + return nil, errors.New("socks version not supported") + } + if buf[1] != 0 { + return nil, errors.New("socks connect failed") + } + switch buf[3] { + case 1: + _, err = io.ReadFull(conn, buf[:4]) + if err != nil { + return nil, err + } + case 3: + _, err = io.ReadFull(conn, buf[:1]) + if err != nil { + return nil, err + } + n := int(buf[0]) + _, err = io.ReadFull(conn, buf[:n]) + if err != nil { + return nil, err + } + case 4: + _, err = io.ReadFull(conn, buf[:16]) + if err != nil { + return nil, err + } + default: + return nil, errors.New("socks address type not supported") + } + _, err = io.ReadFull(conn, buf[:2]) + if err != nil { + return nil, err + } + return conn, nil +} + +func DialSocks4(s *Socks, network, addr string) (net.Conn, error) { + conn, err := net.Dial("tcp", s.Host+":"+strconv.Itoa(s.Port)) + if err != nil { + return nil, err + } + // connect + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + ip := net.ParseIP(host) + if ip == nil { + return nil, errors.New("socks4 only support ip address") + } + ip4 := ip.To4() + if ip4 == nil { + return nil, errors.New("socks4 only support ipv4 address") + } + p, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + _, err = conn.Write([]byte{4, 1, byte(p >> 8), byte(p), ip4[0], ip4[1], ip4[2], ip4[3], 0}) + if err != nil { + return nil, err + } + buf := make([]byte, 8) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + if buf[0] != 0 { + return nil, errors.New("socks version not supported") + } + if buf[1] != 90 { + return nil, errors.New("socks connect failed") + } + return conn, nil +} diff --git a/internal/utils/extra.go b/internal/utils/extra.go deleted file mode 100644 index c6d0e515..00000000 --- a/internal/utils/extra.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2022 RoseLoverX - -package utils - -type null = struct{} - -var ( - DcList = map[int]string{ - 1: "149.154.175.58:443", - 2: "149.154.167.50:443", - 3: "149.154.175.100:443", - 4: "149.154.167.91:443", - 5: "91.108.56.151:443", - } -) diff --git a/internal/utils/helpers.go b/internal/utils/helpers.go deleted file mode 100644 index 30418994..00000000 --- a/internal/utils/helpers.go +++ /dev/null @@ -1,63 +0,0 @@ -package utils - -import ( - "github.com/amarnathcjd/gogram/internal/encoding/tl" -) - -var ( - ApiVersion = 145 -) - -type PingParams struct { - PingID int64 -} - -func (*PingParams) CRC() uint32 { - return 0x7abe77ec -} - -type InvokeWithLayerParams struct { - Layer int32 - Query tl.Object -} - -func (*InvokeWithLayerParams) CRC() uint32 { - return 0xda9b0d0d -} - -type InitConnectionParams struct { - ApiID int32 // Application identifier (see. App configuration) - DeviceModel string // Device model - SystemVersion string // Operation system version - AppVersion string // Application version - SystemLangCode string // Code for the language used on the device's OS, ISO 639-1 standard - LangPack string // Language pack to use - LangCode string // Code for the language used on the client, ISO 639-1 standard - Query tl.Object // The query itself -} - -func (*InitConnectionParams) CRC() uint32 { - return 0xc1cd5ea9 //nolint:gomnd not magic -} - -type HelpGetConfigParams struct{} - -func (*HelpGetConfigParams) CRC() uint32 { - return 0xc4f9186b -} - -func (*InitConnectionParams) FlagIndex() int { - return 0 -} - -type AuthExportAuthorizationParams struct { - DcID int32 -} - -func (*AuthExportAuthorizationParams) CRC() uint32 { - return 0xe5bfffcd -} - -func GetDCID(ip, port string) int { - return 0 -} diff --git a/internal/utils/sync.go b/internal/utils/sync.go index fcf338fd..c047ac1c 100644 --- a/internal/utils/sync.go +++ b/internal/utils/sync.go @@ -11,11 +11,11 @@ import ( type SyncSetInt struct { mutex sync.RWMutex - m map[int]null + m map[int]struct{} } func NewSyncSetInt() *SyncSetInt { - return &SyncSetInt{m: make(map[int]null)} + return &SyncSetInt{m: make(map[int]struct{})} } func (s *SyncSetInt) Has(key int) bool { @@ -29,7 +29,7 @@ func (s *SyncSetInt) Add(key int) bool { s.mutex.Lock() defer s.mutex.Unlock() _, ok := s.m[key] - s.m[key] = null{} + s.m[key] = struct{}{} return !ok } @@ -44,7 +44,7 @@ func (s *SyncSetInt) Delete(key int) bool { func (s *SyncSetInt) Reset() { s.mutex.Lock() defer s.mutex.Unlock() - s.m = make(map[int]null) + s.m = make(map[int]struct{}) } type SyncIntObjectChan struct { diff --git a/internal/utils/utils.go b/internal/utils/utils.go index fae34b6a..536c831c 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -13,6 +13,24 @@ import ( "time" ) +var ( + DcList = map[int]string{ + 1: "149.154.175.58:443", + 2: "149.154.167.50:443", + 3: "149.154.175.100:443", + 4: "149.154.167.91:443", + 5: "91.108.56.151:443", + } +) + +type PingParams struct { + PingID int64 +} + +func (*PingParams) CRC() uint32 { + return 0x7abe77ec +} + func GenerateMessageId(prevID int64) int64 { const billion = 1000 * 1000 * 1000 unixnano := time.Now().UnixNano() @@ -60,3 +78,9 @@ func RandomBytes(size int) []byte { _, _ = rand.Read(b) return b } + +func Xor(dst, src []byte) { + for i := range dst { + dst[i] ^= src[i] + } +} diff --git a/mtproto.go b/mtproto.go index b5e3a663..84460fcb 100644 --- a/mtproto.go +++ b/mtproto.go @@ -1,6 +1,6 @@ // Copyright (c) 2022 RoseLoverX -package mtproto +package gogram import ( "context" @@ -30,6 +30,7 @@ var wd, _ = os.Getwd() type MTProto struct { Addr string + socksProxy *transport.Socks transport transport.Transport stopRoutines context.CancelFunc routineswg sync.WaitGroup @@ -77,6 +78,7 @@ type Config struct { PublicKey *rsa.PublicKey DataCenter int LogLevel string + SocksProxy *transport.Socks } func NewMTProto(c Config) (*MTProto, error) { @@ -115,6 +117,7 @@ func NewMTProto(c Config) (*MTProto, error) { Logger: utils.NewLogger("MTProto").SetLevel(c.LogLevel), memorySession: c.MemorySession, } + m.socksProxy = c.SocksProxy if c.StringSession != "" { m.ImportAuth(c.StringSession) } else { @@ -239,6 +242,7 @@ func (m *MTProto) connect(ctx context.Context) error { Ctx: ctx, Host: m.Addr, Timeout: defaultTimeout, + Socks: m.socksProxy, }, mode.Intermediate, ) diff --git a/network.go b/network.go index 96dd5774..4c0c92e8 100644 --- a/network.go +++ b/network.go @@ -1,6 +1,6 @@ // Copyright (c) 2022 RoseLoverX -package mtproto +package gogram import ( "fmt" diff --git a/telegram/client.go b/telegram/client.go index 0cc389ee..d3950b73 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -13,6 +13,7 @@ import ( "github.com/amarnathcjd/gogram/internal/keys" "github.com/amarnathcjd/gogram/internal/session" + "github.com/amarnathcjd/gogram/internal/transport" "github.com/amarnathcjd/gogram/internal/utils" ) @@ -53,10 +54,26 @@ type ClientConfig struct { ParseMode string // Data center id, default: 4 (Not recommended to change) DataCenter int + // Socket proxy (supported: socks5, socks4) + // + SocksProxy *SocksProxy // Set log level (debug, info, warn, error, disable), default: info LogLevel string } +type SocksProxy struct { + // Socks proxy address + Host string + // Socks proxy port + Port int + // Socks5 proxy username + Username string + // Socks5 proxy password + Password string + // v5 or v4 + Version int +} + // New instance of telegram client, // If session file is not provided, it will create a new session file // in the current working directory @@ -82,6 +99,9 @@ func TelegramClient(c ClientConfig) (*Client, error) { return nil, errors.New("Your API ID or Hash cannot be empty or None. Please get your own API ID and Hash from https://my.telegram.org/apps") // TODO: no need APPID when using string session or session file } + if c.SocksProxy == nil { + c.SocksProxy = &SocksProxy{} + } mtproto, err := mtproto.NewMTProto(mtproto.Config{ AuthKeyFile: c.SessionFile, ServerHost: GetHostIp(dcID), @@ -89,9 +109,10 @@ func TelegramClient(c ClientConfig) (*Client, error) { DataCenter: dcID, StringSession: c.StringSession, LogLevel: getStr(c.LogLevel, LogInfo), + SocksProxy: &transport.Socks{Host: c.SocksProxy.Host, Port: c.SocksProxy.Port, Username: c.SocksProxy.Username, Password: c.SocksProxy.Password, Version: c.SocksProxy.Version}, }) if err != nil { - return nil, errors.Wrap(err, "MTProto client") + return nil, errors.Wrap(err, "creating mtproto client") } err = mtproto.CreateConnection(true) diff --git a/telegram/const.go b/telegram/const.go index 187fec04..b61e37b0 100644 --- a/telegram/const.go +++ b/telegram/const.go @@ -4,7 +4,7 @@ import "regexp" const ( ApiVersion = 147 - Version = "v1.0.2e" + Version = "v1.1.0" DefaultDC = 4