Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -220,11 +220,12 @@ OFHostAddressResolver.m \ OFKernelEventObserver.m \ ${OF_EPOLL_KERNEL_EVENT_OBSERVER_M} \ ${OF_KQUEUE_KERNEL_EVENT_OBSERVER_M} \ ${OF_POLL_KERNEL_EVENT_OBSERVER_M} \ - ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} + ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} \ + OFTCPSocketSOCKS5Connector.m OBJS_EXTRA = ${RUNTIME_RUNTIME_A} \ ${EXCEPTIONS_EXCEPTIONS_A} \ ${ENCODINGS_ENCODINGS_A} \ ${FORWARDING_FORWARDING_A} \ Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -17,11 +17,10 @@ #define __NO_EXT_QNX #include "config.h" -#include #include #include #include #include @@ -28,16 +27,17 @@ #ifdef HAVE_FCNTL_H # include #endif #import "OFTCPSocket.h" -#import "OFDate.h" #import "OFDNSResolver.h" #import "OFData.h" +#import "OFDate.h" #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFString.h" +#import "OFTCPSocketSOCKS5Connector.h" #import "OFThread.h" #import "OFTimer.h" #import "OFAlreadyConnectedException.h" #import "OFBindFailedException.h" @@ -73,46 +73,30 @@ OFRunLoopConnectDelegate, OFDNSResolverHostDelegate> { OFTCPSocket *_socket; OFString *_host; uint16_t _port; - OFString *_SOCKS5Host; - uint16_t _SOCKS5Port; id _delegate; #ifdef OF_HAVE_BLOCKS of_tcp_socket_async_connect_block_t _block; #endif id _exception; OFData *_socketAddresses; size_t _socketAddressesIndex; - enum { - SOCKS5_STATE_SEND_AUTHENTICATION = 1, - SOCKS5_STATE_READ_VERSION, - SOCKS5_STATE_SEND_REQUEST, - SOCKS5_STATE_READ_RESPONSE, - SOCKS5_STATE_READ_ADDRESS, - SOCKS5_STATE_READ_ADDRESS_LENGTH, - } _SOCKS5State; - /* Longest read is domain name (max 255 bytes) + port */ - unsigned char _buffer[257]; - OFMutableData *_request; } - (instancetype)initWithSocket: (OFTCPSocket *)sock host: (OFString *)host port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port delegate: (id )delegate #ifdef OF_HAVE_BLOCKS block: (of_tcp_socket_async_connect_block_t)block #endif ; - (void)didConnect; - (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; - (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)sendSOCKS5Request; @end @interface OFTCPSocketConnectDelegate: OFObject { @public @@ -123,12 +107,10 @@ @implementation OFTCPSocketAsyncConnectDelegate - (instancetype)initWithSocket: (OFTCPSocket *)sock host: (OFString *)host port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port delegate: (id )delegate #ifdef OF_HAVE_BLOCKS block: (of_tcp_socket_async_connect_block_t)block #endif { @@ -136,18 +118,14 @@ @try { _socket = [sock retain]; _host = [host copy]; _port = port; - _SOCKS5Host = [SOCKS5Host copy]; - _SOCKS5Port = SOCKS5Port; _delegate = [delegate retain]; #ifdef OF_HAVE_BLOCKS _block = [block copy]; #endif - - _socket.delegate = self; } @catch (id e) { [self release]; @throw e; } @@ -154,26 +132,18 @@ return self; } - (void)dealloc { -#ifdef OF_HAVE_BLOCKS - if (_block == NULL) -#endif - if (_socket.delegate == self) - _socket.delegate = _delegate; - [_socket release]; [_host release]; - [_SOCKS5Host release]; [_delegate release]; #ifdef OF_HAVE_BLOCKS [_block release]; #endif [_exception release]; [_socketAddresses release]; - [_request release]; [super dealloc]; } - (void)didConnect @@ -184,12 +154,10 @@ #ifdef OF_HAVE_BLOCKS if (_block != NULL) _block(_exception); else { #endif - _socket.delegate = _delegate; - if ([_delegate respondsToSelector: @selector(socket:didConnectToHost:port:exception:)]) [_delegate socket: _socket didConnectToHost: _host port: _port @@ -235,14 +203,11 @@ } return; } - if (_SOCKS5Host != nil) - [self sendSOCKS5Request]; - else - [self didConnect]; + [self didConnect]; } - (id)of_connectionFailedExceptionForErrNo: (int)errNo { return [OFConnectionFailedException exceptionWithHost: _host @@ -255,14 +220,11 @@ { of_socket_address_t address = *(const of_socket_address_t *) [_socketAddresses itemAtIndex: _socketAddressesIndex++]; int errNo; - if (_SOCKS5Host != nil) - of_socket_address_set_port(&address, _SOCKS5Port); - else - of_socket_address_set_port(&address, _port); + of_socket_address_set_port(&address, _port); if (![_socket of_createSocketForAddress: &address errNo: &errNo]) { if (_socketAddressesIndex >= _socketAddresses.count) { _exception = [[OFConnectionFailedException alloc] @@ -347,27 +309,13 @@ [OFRunLoop currentRunLoop].currentMode]; } - (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode { - OFString *host; - uint16_t port; - - if (_SOCKS5Host != nil) { - if (_host.UTF8StringLength > 255) - @throw [OFOutOfRangeException exception]; - - host = _SOCKS5Host; - port = _SOCKS5Port; - } else { - host = _host; - port = _port; - } - @try { of_socket_address_t address = - of_socket_address_parse_ip(host, port); + of_socket_address_parse_ip(_host, _port); _socketAddresses = [[OFData alloc] initWithItems: &address itemSize: sizeof(address) count: 1]; @@ -376,219 +324,15 @@ return; } @catch (OFInvalidFormatException *e) { } [[OFThread DNSResolver] - asyncResolveAddressesForHost: host + asyncResolveAddressesForHost: _host addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY runLoopMode: runLoopMode delegate: self]; } - -- (void)sendSOCKS5Request -{ - OFData *data = [OFData dataWithItems: "\x05\x01\x00" - count: 3]; - - _SOCKS5State = SOCKS5_STATE_SEND_AUTHENTICATION; - [_socket asyncWriteData: data - runLoopMode: [OFRunLoop currentRunLoop].currentMode]; -} - -- (bool)stream: (OFStream *)sock - didReadIntoBuffer: (void *)buffer - length: (size_t)length - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - unsigned char *SOCKSVersion; - uint8_t hostLength; - unsigned char port[2]; - unsigned char *response, *addressLength; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return false; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_READ_VERSION: - SOCKSVersion = buffer; - - if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - [_request release]; - _request = [[OFMutableData alloc] init]; - - [_request addItems: "\x05\x01\x00\x03" - count: 4]; - - hostLength = (uint8_t)_host.UTF8StringLength; - [_request addItem: &hostLength]; - [_request addItems: _host.UTF8String - count: hostLength]; - - port[0] = _port >> 8; - port[1] = _port & 0xFF; - [_request addItems: port - count: 2]; - - _SOCKS5State = SOCKS5_STATE_SEND_REQUEST; - [_socket asyncWriteData: _request - runLoopMode: runLoopMode]; - return false; - case SOCKS5_STATE_READ_RESPONSE: - response = buffer; - - if (response[0] != 5 || response[2] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - if (response[1] != 0) { - int errNo; - - switch (response[1]) { - case 0x02: - errNo = EPERM; - break; - case 0x03: - errNo = ENETUNREACH; - break; - case 0x04: - errNo = EHOSTUNREACH; - break; - case 0x05: - errNo = ECONNREFUSED; - break; - case 0x06: - errNo = ETIMEDOUT; - break; - case 0x07: - errNo = EOPNOTSUPP; - break; - case 0x08: - errNo = EAFNOSUPPORT; - break; - default: -#ifdef EPROTO - errNo = EPROTO; -#else - errNo = 0; -#endif - break; - } - - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return false; - } - - /* Skip the rest of the response */ - switch (response[3]) { - case 1: /* IPv4 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 + 2 - runLoopMode: runLoopMode]; - return false; - case 3: /* Domain name */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS_LENGTH; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 1 - runLoopMode: runLoopMode]; - return false; - case 4: /* IPv6 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 16 + 2 - runLoopMode: runLoopMode]; - return false; - default: - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - return false; - case SOCKS5_STATE_READ_ADDRESS: - [self didConnect]; - return false; - case SOCKS5_STATE_READ_ADDRESS_LENGTH: - addressLength = buffer; - - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: addressLength[0] + 2 - runLoopMode: runLoopMode]; - return false; - default: - assert(0); - return false; - } -} - -- (OFData *)stream: (OFStream *)sock - didWriteData: (OFData *)data - bytesWritten: (size_t)bytesWritten - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return nil; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_SEND_AUTHENTICATION: - _SOCKS5State = SOCKS5_STATE_READ_VERSION; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 2 - runLoopMode: runLoopMode]; - return nil; - case SOCKS5_STATE_SEND_REQUEST: - [_request release]; - _request = nil; - - _SOCKS5State = SOCKS5_STATE_READ_RESPONSE; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 - runLoopMode: runLoopMode]; - return nil; - default: - assert(0); - return nil; - } -} @end @implementation OFTCPSocketConnectDelegate - (void)dealloc { @@ -743,18 +487,32 @@ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port runLoopMode: (of_run_loop_mode_t)runLoopMode { void *pool = objc_autoreleasePoolPush(); + id delegate; + + if (_SOCKS5Host != nil) { + delegate = [[[OFTCPSocketSOCKS5Connector alloc] + initWithSocket: self + host: host + port: port + delegate: _delegate +#ifdef OF_HAVE_BLOCKS + block: NULL +#endif + ] autorelease]; + host = _SOCKS5Host; + port = _SOCKS5Port; + } else + delegate = _delegate; [[[[OFTCPSocketAsyncConnectDelegate alloc] initWithSocket: self host: host port: port - SOCKS5Host: _SOCKS5Host - SOCKS5Port: _SOCKS5Port - delegate: _delegate + delegate: delegate #ifdef OF_HAVE_BLOCKS block: NULL #endif ] autorelease] startWithRunLoopMode: runLoopMode]; @@ -776,19 +534,29 @@ port: (uint16_t)port runLoopMode: (of_run_loop_mode_t)runLoopMode block: (of_tcp_socket_async_connect_block_t)block { void *pool = objc_autoreleasePoolPush(); + id delegate = nil; + + if (_SOCKS5Host != nil) { + delegate = [[[OFTCPSocketSOCKS5Connector alloc] + initWithSocket: self + host: host + port: port + delegate: nil + block: block] autorelease]; + host = _SOCKS5Host; + port = _SOCKS5Port; + } [[[[OFTCPSocketAsyncConnectDelegate alloc] initWithSocket: self host: host port: port - SOCKS5Host: _SOCKS5Host - SOCKS5Port: _SOCKS5Port - delegate: nil - block: block] autorelease] + delegate: delegate + block: (delegate == nil ? block : NULL)] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } #endif ADDED src/OFTCPSocketSOCKS5Connector.h Index: src/OFTCPSocketSOCKS5Connector.h ================================================================== --- src/OFTCPSocketSOCKS5Connector.h +++ src/OFTCPSocketSOCKS5Connector.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFTCPSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +@interface OFTCPSocketSOCKS5Connector: OFObject +{ + OFTCPSocket *_socket; + OFString *_host; + uint16_t _port; + id _Nullable _delegate; +#ifdef OF_HAVE_BLOCKS + of_tcp_socket_async_connect_block_t _Nullable _block; +#endif + id _Nullable _exception; + enum { + OF_SOCKS5_STATE_SEND_AUTHENTICATION = 1, + OF_SOCKS5_STATE_READ_VERSION, + OF_SOCKS5_STATE_SEND_REQUEST, + OF_SOCKS5_STATE_READ_RESPONSE, + OF_SOCKS5_STATE_READ_ADDRESS, + OF_SOCKS5_STATE_READ_ADDRESS_LENGTH, + } _SOCKS5State; + /* Longest read is domain name (max 255 bytes) + port */ + unsigned char _buffer[257]; + OFMutableData *_Nullable _request; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (nullable id )delegate +#ifdef OF_HAVE_BLOCKS + block: (nullable of_tcp_socket_async_connect_block_t) + block +#endif +; +- (void)didConnect; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFTCPSocketSOCKS5Connector.m Index: src/OFTCPSocketSOCKS5Connector.m ================================================================== --- src/OFTCPSocketSOCKS5Connector.m +++ src/OFTCPSocketSOCKS5Connector.m @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "OFTCPSocketSOCKS5Connector.h" +#import "OFData.h" +#import "OFRunLoop.h" +#import "OFString.h" + +#import "OFConnectionFailedException.h" + +@implementation OFTCPSocketSOCKS5Connector +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (id )delegate +#ifdef OF_HAVE_BLOCKS + block: (of_tcp_socket_async_connect_block_t)block +#endif +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _delegate = [delegate retain]; +#ifdef OF_HAVE_BLOCKS + _block = [block copy]; +#endif + + _socket.delegate = self; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_socket.delegate == self) + _socket.delegate = _delegate; + + [_socket release]; + [_host release]; + [_delegate release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + [_exception release]; + [_request release]; + + [super dealloc]; +} + +- (void)didConnect +{ + if (_exception == nil) + _socket.blocking = true; + + _socket.delegate = _delegate; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(_exception); + else { +#endif + if ([_delegate respondsToSelector: + @selector(socket:didConnectToHost:port:exception:)]) + [_delegate socket: _socket + didConnectToHost: _host + port: _port + exception: _exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (void)socket: (OFTCPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + OFData *data; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return; + } + + data = [OFData dataWithItems: "\x05\x01\x00" + count: 3]; + + _SOCKS5State = OF_SOCKS5_STATE_SEND_AUTHENTICATION; + [_socket asyncWriteData: data + runLoopMode: [OFRunLoop currentRunLoop].currentMode]; +} + +- (bool)stream: (OFStream *)sock + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + unsigned char *SOCKSVersion; + uint8_t hostLength; + unsigned char port[2]; + unsigned char *response, *addressLength; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return false; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case OF_SOCKS5_STATE_READ_VERSION: + SOCKSVersion = buffer; + + if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + [_request release]; + _request = [[OFMutableData alloc] init]; + + [_request addItems: "\x05\x01\x00\x03" + count: 4]; + + hostLength = (uint8_t)_host.UTF8StringLength; + [_request addItem: &hostLength]; + [_request addItems: _host.UTF8String + count: hostLength]; + + port[0] = _port >> 8; + port[1] = _port & 0xFF; + [_request addItems: port + count: 2]; + + _SOCKS5State = OF_SOCKS5_STATE_SEND_REQUEST; + [_socket asyncWriteData: _request + runLoopMode: runLoopMode]; + return false; + case OF_SOCKS5_STATE_READ_RESPONSE: + response = buffer; + + if (response[0] != 5 || response[2] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + if (response[1] != 0) { + int errNo; + + switch (response[1]) { + case 0x02: + errNo = EPERM; + break; + case 0x03: + errNo = ENETUNREACH; + break; + case 0x04: + errNo = EHOSTUNREACH; + break; + case 0x05: + errNo = ECONNREFUSED; + break; + case 0x06: + errNo = ETIMEDOUT; + break; + case 0x07: + errNo = EOPNOTSUPP; + break; + case 0x08: + errNo = EAFNOSUPPORT; + break; + default: +#ifdef EPROTO + errNo = EPROTO; +#else + errNo = 0; +#endif + break; + } + + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return false; + } + + /* Skip the rest of the response */ + switch (response[3]) { + case 1: /* IPv4 */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + 2 + runLoopMode: runLoopMode]; + return false; + case 3: /* Domain name */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS_LENGTH; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 1 + runLoopMode: runLoopMode]; + return false; + case 4: /* IPv6 */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 16 + 2 + runLoopMode: runLoopMode]; + return false; + default: + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + return false; + case OF_SOCKS5_STATE_READ_ADDRESS: + [self didConnect]; + return false; + case OF_SOCKS5_STATE_READ_ADDRESS_LENGTH: + addressLength = buffer; + + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: addressLength[0] + 2 + runLoopMode: runLoopMode]; + return false; + default: + assert(0); + return false; + } +} + +- (OFData *)stream: (OFStream *)sock + didWriteData: (OFData *)data + bytesWritten: (size_t)bytesWritten + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return nil; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case OF_SOCKS5_STATE_SEND_AUTHENTICATION: + _SOCKS5State = OF_SOCKS5_STATE_READ_VERSION; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 2 + runLoopMode: runLoopMode]; + return nil; + case OF_SOCKS5_STATE_SEND_REQUEST: + [_request release]; + _request = nil; + + _SOCKS5State = OF_SOCKS5_STATE_READ_RESPONSE; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + runLoopMode: runLoopMode]; + return nil; + default: + assert(0); + return nil; + } +} +@end