250 lines
8.3 KiB
TypeScript
250 lines
8.3 KiB
TypeScript
// /* eslint-disable no-restricted-globals */
|
|
// todo fixme
|
|
|
|
// import { connect } from 'node:net'
|
|
|
|
// import type {
|
|
// IPacketCodec,
|
|
// tl,
|
|
// } from '@mtcute/node'
|
|
// import {
|
|
// BaseTcpTransport,
|
|
// IntermediatePacketCodec,
|
|
// MtSecurityError,
|
|
// MtUnsupportedError,
|
|
// MtcuteError,
|
|
// ObfuscatedPacketCodec,
|
|
// PaddedIntermediatePacketCodec,
|
|
// TransportState,
|
|
// } from '@mtcute/node'
|
|
// import { buffersEqual } from '@mtcute/node/utils.js'
|
|
|
|
// import { FakeTlsPacketCodec, generateFakeTlsHeader } from './fake-tls.js'
|
|
|
|
// /**
|
|
// * MTProto proxy settings
|
|
// */
|
|
// export interface MtProxySettings {
|
|
// /**
|
|
// * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)
|
|
// */
|
|
// host: string
|
|
|
|
// /**
|
|
// * Port of the proxy (e.g. `8888`)
|
|
// */
|
|
// port: number
|
|
|
|
// /**
|
|
// * Secret of the proxy, optionally encoded either as hex or base64
|
|
// */
|
|
// secret: string | Buffer
|
|
// }
|
|
|
|
// const MAX_DOMAIN_LENGTH = 182 // must be small enough not to overflow TLS-hello length
|
|
// const TLS_START = [Buffer.from('160303', 'hex'), Buffer.from('140303000101170303', 'hex')]
|
|
|
|
// /**
|
|
// * TCP transport that connects via an MTProxy
|
|
// */
|
|
// export class MtProxyTcpTransport extends BaseTcpTransport {
|
|
// readonly _proxy: MtProxySettings
|
|
|
|
// private _rawSecret: Buffer
|
|
// private _randomPadding = false
|
|
// private _fakeTlsDomain: string | null = null
|
|
|
|
// /**
|
|
// * @param proxy Information about the proxy
|
|
// */
|
|
// constructor(proxy: MtProxySettings) {
|
|
// super()
|
|
|
|
// this._proxy = proxy
|
|
|
|
// // validate and parse secret
|
|
// let secret: Buffer
|
|
|
|
// if (Buffer.isBuffer(proxy.secret)) {
|
|
// secret = proxy.secret
|
|
// } else if (proxy.secret.match(/^[0-9a-f]+$/i)) {
|
|
// secret = Buffer.from(proxy.secret, 'hex')
|
|
// } else {
|
|
// secret = Buffer.from(proxy.secret, 'base64url')
|
|
// }
|
|
|
|
// if (secret.length > 17 + MAX_DOMAIN_LENGTH) {
|
|
// throw new MtSecurityError('Invalid secret: too long')
|
|
// }
|
|
|
|
// if (secret.length < 16) {
|
|
// throw new MtSecurityError('Invalid secret: too short')
|
|
// }
|
|
|
|
// if (secret.length === 16) {
|
|
// this._rawSecret = secret
|
|
// } else if (secret.length === 17 && secret[0] === 0xDD) {
|
|
// this._rawSecret = secret.slice(1)
|
|
// this._randomPadding = true
|
|
// } else if (secret.length >= 18 && secret[0] === 0xEE) {
|
|
// this._rawSecret = secret.slice(1, 17)
|
|
// this._fakeTlsDomain = secret.slice(17).toString()
|
|
// } else {
|
|
// throw new MtUnsupportedError('Unsupported secret')
|
|
// }
|
|
// }
|
|
|
|
// getMtproxyInfo(): tl.RawInputClientProxy {
|
|
// return {
|
|
// _: 'inputClientProxy',
|
|
// address: this._proxy.host,
|
|
// port: this._proxy.port,
|
|
// }
|
|
// }
|
|
|
|
// _packetCodec!: IPacketCodec
|
|
|
|
// connect(dc: tl.RawDcOption, testMode: boolean): void {
|
|
// if (this._state !== TransportState.Idle) {
|
|
// throw new MtcuteError('Transport is not IDLE')
|
|
// }
|
|
|
|
// if (this._packetCodec && this._currentDc?.id !== dc.id) {
|
|
// // dc changed, thus the codec's init will change too
|
|
// // clean up to avoid memory leaks
|
|
// this.packetCodecInitialized = false
|
|
// this._packetCodec.reset()
|
|
// this._packetCodec.removeAllListeners()
|
|
// delete (this as Partial<MtProxyTcpTransport>)._packetCodec
|
|
// }
|
|
|
|
// if (!this._packetCodec) {
|
|
// const proxy = {
|
|
// dcId: dc.id,
|
|
// media: dc.mediaOnly!,
|
|
// test: testMode,
|
|
// secret: this._rawSecret,
|
|
// }
|
|
|
|
// if (!this._fakeTlsDomain) {
|
|
// let inner: IPacketCodec
|
|
|
|
// if (this._randomPadding) {
|
|
// inner = new PaddedIntermediatePacketCodec()
|
|
// } else {
|
|
// inner = new IntermediatePacketCodec()
|
|
// }
|
|
|
|
// this._packetCodec = new ObfuscatedPacketCodec(inner, proxy)
|
|
// } else {
|
|
// this._packetCodec = new FakeTlsPacketCodec(
|
|
// new ObfuscatedPacketCodec(new PaddedIntermediatePacketCodec(), proxy),
|
|
// )
|
|
// }
|
|
|
|
// this._packetCodec.setup?.(this._crypto, this.log)
|
|
// this._packetCodec.on('error', err => this.emit('error', err))
|
|
// this._packetCodec.on('packet', buf => this.emit('message', buf))
|
|
// }
|
|
|
|
// this._state = TransportState.Connecting
|
|
// this._currentDc = dc
|
|
|
|
// if (this._fakeTlsDomain) {
|
|
// this._socket = connect(
|
|
// this._proxy.port,
|
|
// this._proxy.host,
|
|
// // MTQ-55
|
|
// // eslint-disable-next-line ts/no-misused-promises
|
|
// this._handleConnectFakeTls.bind(this),
|
|
// )
|
|
// } else {
|
|
// this._socket = connect(
|
|
// this._proxy.port,
|
|
// this._proxy.host,
|
|
// // MTQ-55
|
|
|
|
// this.handleConnect.bind(this),
|
|
// )
|
|
// this._socket.on('data', data => this._packetCodec.feed(data))
|
|
// }
|
|
// this._socket.on('error', this.handleError.bind(this))
|
|
// this._socket.on('close', this.close.bind(this))
|
|
// }
|
|
|
|
// private async _handleConnectFakeTls(): Promise<void> {
|
|
// try {
|
|
// const hello = await generateFakeTlsHeader(this._fakeTlsDomain!, this._rawSecret, this._crypto)
|
|
// const helloRand = hello.slice(11, 11 + 32)
|
|
|
|
// let serverHelloBuffer: Buffer | null = null
|
|
|
|
// const checkHelloResponse = async (buf: Buffer): Promise<boolean> => {
|
|
// if (serverHelloBuffer) {
|
|
// buf = Buffer.concat([serverHelloBuffer, buf])
|
|
// }
|
|
|
|
// const resp = buf
|
|
|
|
// for (const first of TLS_START) {
|
|
// if (buf.length < first.length + 2) {
|
|
// throw new MtSecurityError('Server hello is too short')
|
|
// }
|
|
|
|
// if (!buffersEqual(buf.slice(0, first.length), first)) {
|
|
// throw new MtSecurityError('Server hello is invalid')
|
|
// }
|
|
// buf = buf.slice(first.length)
|
|
|
|
// const skipSize = buf.readUInt16BE()
|
|
// buf = buf.slice(2)
|
|
|
|
// if (buf.length < skipSize) {
|
|
// // likely got split into multiple packets
|
|
// if (serverHelloBuffer) {
|
|
// throw new MtSecurityError('Server hello is too short')
|
|
// }
|
|
|
|
// serverHelloBuffer = resp
|
|
|
|
// return false
|
|
// }
|
|
|
|
// buf = buf.slice(skipSize)
|
|
// }
|
|
|
|
// const respRand = resp.slice(11, 11 + 32)
|
|
// const hash = await this._crypto.hmacSha256(
|
|
// Buffer.concat([helloRand, resp.slice(0, 11), Buffer.alloc(32, 0), resp.slice(11 + 32)]),
|
|
// this._rawSecret,
|
|
// )
|
|
|
|
// if (!buffersEqual(hash, respRand)) {
|
|
// throw new MtSecurityError('Response hash is invalid')
|
|
// }
|
|
|
|
// return true
|
|
// }
|
|
|
|
// const packetHandler = (buf: Buffer): void => {
|
|
// checkHelloResponse(buf)
|
|
// .then((done) => {
|
|
// if (!done) return
|
|
|
|
// this._socket!.off('data', packetHandler)
|
|
// this._socket!.on('data', (data) => {
|
|
// this._packetCodec.feed(data)
|
|
// })
|
|
|
|
// return this.handleConnect()
|
|
// })
|
|
// .catch(err => this._socket!.emit('error', err))
|
|
// }
|
|
|
|
// this._socket!.write(hello)
|
|
// this._socket!.on('data', packetHandler)
|
|
// } catch (e) {
|
|
// this._socket!.emit('error', e)
|
|
// }
|
|
// }
|
|
// }
|