mtcute/packages/socks-proxy/index.ts

420 lines
15 KiB
TypeScript

// todo: move to fuman
// import { connect } from 'node:net'
// // @ts-expect-error no typings
// import { normalize } from 'ip6'
// import type { tl } from '@mtcute/node'
// import { BaseTcpTransport, IntermediatePacketCodec, MtArgumentError, NodePlatform, TransportState, assertNever } from '@mtcute/node'
// import { dataViewFromBuffer } from '@mtcute/node/utils.js'
// const p = new NodePlatform()
// /**
// * An error has occurred while connecting to an SOCKS proxy
// */
// export class SocksProxyConnectionError extends Error {
// readonly proxy: SocksProxySettings
// constructor(proxy: SocksProxySettings, message: string) {
// super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`)
// this.proxy = proxy
// }
// }
// /**
// * Settings for a SOCKS4/5 proxy
// */
// export interface SocksProxySettings {
// /**
// * 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
// /**
// * Proxy authorization username, if needed
// */
// user?: string
// /**
// * Proxy authorization password, if needed
// */
// password?: string
// /**
// * Version of the SOCKS proxy (4 or 5)
// *
// * @default `5`
// */
// version?: 4 | 5
// }
// function writeIpv4(ip: string, buf: Uint8Array, offset: number): void {
// const parts = ip.split('.')
// if (parts.length !== 4) {
// throw new MtArgumentError('Invalid IPv4 address')
// }
// for (let i = 0; i < 4; i++) {
// const n = Number.parseInt(parts[i])
// if (Number.isNaN(n) || n < 0 || n > 255) {
// throw new MtArgumentError('Invalid IPv4 address')
// }
// buf[offset + i] = n
// }
// }
// function buildSocks4ConnectRequest(ip: string, port: number, username = ''): Uint8Array {
// const userId = p.utf8Encode(username)
// const buf = new Uint8Array(9 + userId.length)
// buf[0] = 0x04 // VER
// buf[1] = 0x01 // CMD = establish a TCP/IP stream connection
// dataViewFromBuffer(buf).setUint16(2, port, false)
// writeIpv4(ip, buf, 4) // DSTIP
// buf.set(userId, 8)
// buf[8 + userId.length] = 0x00 // ID (null-termination)
// return buf
// }
// function buildSocks5Greeting(authAvailable: boolean): Uint8Array {
// const buf = new Uint8Array(authAvailable ? 4 : 3)
// buf[0] = 0x05 // VER
// if (authAvailable) {
// buf[1] = 0x02 // NAUTH
// buf[2] = 0x00 // AUTH[0] = No authentication
// buf[3] = 0x02 // AUTH[1] = Username/password
// } else {
// buf[1] = 0x01 // NAUTH
// buf[2] = 0x00 // AUTH[0] = No authentication
// }
// return buf
// }
// function buildSocks5Auth(username: string, password: string) {
// const usernameBuf = p.utf8Encode(username)
// const passwordBuf = p.utf8Encode(password)
// if (usernameBuf.length > 255) {
// throw new MtArgumentError(`Too long username (${usernameBuf.length} > 255)`)
// }
// if (passwordBuf.length > 255) {
// throw new MtArgumentError(`Too long password (${passwordBuf.length} > 255)`)
// }
// const buf = new Uint8Array(3 + usernameBuf.length + passwordBuf.length)
// buf[0] = 0x01 // VER of auth
// buf[1] = usernameBuf.length
// buf.set(usernameBuf, 2)
// buf[2 + usernameBuf.length] = passwordBuf.length
// buf.set(passwordBuf, 3 + usernameBuf.length)
// return buf
// }
// function writeIpv6(ip: string, buf: Uint8Array, offset: number): void {
// // eslint-disable-next-line ts/no-unsafe-call
// ip = normalize(ip) as string
// const parts = ip.split(':')
// if (parts.length !== 8) {
// throw new MtArgumentError('Invalid IPv6 address')
// }
// const dv = dataViewFromBuffer(buf)
// for (let i = 0, j = offset; i < 8; i++, j += 2) {
// const n = Number.parseInt(parts[i])
// if (Number.isNaN(n) || n < 0 || n > 0xFFFF) {
// throw new MtArgumentError('Invalid IPv6 address')
// }
// dv.setUint16(j, n, false)
// }
// }
// function buildSocks5Connect(ip: string, port: number, ipv6 = false): Uint8Array {
// const buf = new Uint8Array(ipv6 ? 22 : 10)
// const dv = dataViewFromBuffer(buf)
// buf[0] = 0x05 // VER
// buf[1] = 0x01 // CMD = establish a TCP/IP stream connection
// buf[2] = 0x00 // RSV
// if (ipv6) {
// buf[3] = 0x04 // TYPE = IPv6
// writeIpv6(ip, buf, 4) // ADDR
// dv.setUint16(20, port, false)
// } else {
// buf[3] = 0x01 // TYPE = IPv4
// writeIpv4(ip, buf, 4) // ADDR
// dv.setUint16(8, port, false)
// }
// return buf
// }
// const SOCKS4_ERRORS: Record<number, string> = {
// 91: 'Request rejected or failed',
// 92: 'Request failed because client is not running identd',
// 93: "Request failed because client's identd could not confirm the user ID in the request",
// }
// const SOCKS5_ERRORS: Record<number, string> = {
// 1: 'General failure',
// 2: 'Connection not allowed by ruleset',
// 3: 'Network unreachable',
// 4: 'Host unreachable',
// 5: 'Connection refused by destination host',
// 6: 'TTL expired',
// 7: 'Command not supported / protocol error',
// 8: 'Address type not supported',
// }
// /**
// * TCP transport that connects via a SOCKS4/5 proxy.
// */
// export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
// readonly _proxy: SocksProxySettings
// constructor(proxy: SocksProxySettings) {
// super()
// if (proxy.version != null && proxy.version !== 4 && proxy.version !== 5) {
// throw new SocksProxyConnectionError(
// proxy,
// `Invalid SOCKS version: ${proxy.version}`,
// )
// }
// this._proxy = proxy
// }
// connect(dc: tl.RawDcOption): void {
// if (this._state !== TransportState.Idle) {
// throw new MtArgumentError('Transport is not IDLE')
// }
// if (!this.packetCodecInitialized) {
// this._packetCodec.on('error', err => this.emit('error', err))
// this._packetCodec.on('packet', buf => this.emit('message', buf))
// this.packetCodecInitialized = true
// }
// this._state = TransportState.Connecting
// this._currentDc = dc
// this._socket = connect(this._proxy.port, this._proxy.host, this._onProxyConnected.bind(this))
// this._socket.on('error', this.handleError.bind(this))
// this._socket.on('close', this.close.bind(this))
// }
// private _onProxyConnected() {
// let packetHandler: (msg: Uint8Array) => void
// if (this._proxy.version === 4) {
// packetHandler = (msg) => {
// if (msg[0] !== 0x04) {
// // VER, must be 4
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`),
// )
// return
// }
// const code = msg[1]
// this.log.debug('[%s:%d] CONNECT returned code %d', this._proxy.host, this._proxy.port, code)
// if (code === 0x5A) {
// this._socket!.off('data', packetHandler)
// this._socket!.on('data', data => this._packetCodec.feed(data))
// this.handleConnect()
// } else {
// const msg
// = code in SOCKS4_ERRORS ? SOCKS4_ERRORS[code] : `Unknown error code: 0x${code.toString(16)}`
// this._socket!.emit('error', new SocksProxyConnectionError(this._proxy, msg))
// }
// }
// this.log.debug('[%s:%d] connected to proxy, sending CONNECT', this._proxy.host, this._proxy.port)
// try {
// this._socket!.write(
// buildSocks4ConnectRequest(this._currentDc!.ipAddress, this._currentDc!.port, this._proxy.user),
// )
// } catch (e) {
// this._socket!.emit('error', e)
// }
// } else {
// let state: 'greeting' | 'auth' | 'connect' = 'greeting'
// const sendConnect = () => {
// this.log.debug('[%s:%d] sending CONNECT', this._proxy.host, this._proxy.port)
// try {
// this._socket!.write(
// buildSocks5Connect(this._currentDc!.ipAddress, this._currentDc!.port, this._currentDc!.ipv6),
// )
// state = 'connect'
// } catch (e) {
// this._socket!.emit('error', e)
// }
// }
// packetHandler = (msg) => {
// switch (state) {
// case 'greeting': {
// if (msg[0] !== 0x05) {
// // VER, must be 5
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`),
// )
// return
// }
// const chosen = msg[1]
// this.log.debug(
// '[%s:%d] GREETING returned auth method %d',
// this._proxy.host,
// this._proxy.port,
// chosen,
// )
// switch (chosen) {
// case 0x00:
// // "No authentication"
// sendConnect()
// break
// case 0x02:
// // Username/password
// if (!this._proxy.user || !this._proxy.password) {
// // should not happen
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(
// this._proxy,
// 'Authentication is required, but not provided',
// ),
// )
// break
// }
// try {
// this._socket!.write(buildSocks5Auth(this._proxy.user, this._proxy.password))
// state = 'auth'
// } catch (e) {
// this._socket!.emit('error', e)
// }
// break
// case 0xFF:
// default:
// // "no acceptable methods were offered"
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(
// this._proxy,
// 'Authentication is required, but not provided/supported',
// ),
// )
// break
// }
// break
// }
// case 'auth':
// if (msg[0] !== 0x01) {
// // VER of auth, must be 1
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`),
// )
// return
// }
// this.log.debug('[%s:%d] AUTH returned code %d', this._proxy.host, this._proxy.port, msg[1])
// if (msg[1] === 0x00) {
// // success
// sendConnect()
// } else {
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(this._proxy, 'Authentication failure'),
// )
// }
// break
// case 'connect': {
// if (msg[0] !== 0x05) {
// // VER, must be 5
// this._socket!.emit(
// 'error',
// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`),
// )
// return
// }
// const code = msg[1]
// this.log.debug('[%s:%d] CONNECT returned code %d', this._proxy.host, this._proxy.port, code)
// if (code === 0x00) {
// // Request granted
// this._socket!.off('data', packetHandler)
// this._socket!.on('data', data => this._packetCodec.feed(data))
// this.handleConnect()
// } else {
// const msg
// = code in SOCKS5_ERRORS
// ? SOCKS5_ERRORS[code]
// : `Unknown error code: 0x${code.toString(16)}`
// this._socket!.emit('error', new SocksProxyConnectionError(this._proxy, msg))
// }
// break
// }
// default:
// assertNever(state)
// }
// }
// this.log.debug('[%s:%d] connected to proxy, sending GREETING', this._proxy.host, this._proxy.port)
// try {
// this._socket!.write(buildSocks5Greeting(Boolean(this._proxy.user && this._proxy.password)))
// } catch (e) {
// this._socket!.emit('error', e)
// }
// }
// this._socket!.on('data', packetHandler)
// }
// }
// /**
// * Socks TCP transport using an intermediate packet codec.
// *
// * Should be the one passed as `transport` to `TelegramClient` constructor
// * (unless you want to use a custom codec).
// */
// export class SocksTcpTransport extends BaseSocksTcpTransport {
// _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec()
// }