diff --git a/ChangeLog b/ChangeLog index 26c52722d..9c846927e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2013-07-04 Ibadinov Marat + + * GSSocksParser/GSSocks5Parser.m: + * GSSocksParser/GSSocks5Parser.h: + * GSSocksParser/GSSocksParserPrivate.h: + * GSSocksParser/GSSocksParser.m: + * GSSocksParser/GSSocksParserPrivate.m: + * GSSocksParser/GSSocksParser.h: + * GSSocksParser/GSSocks4Parser.m: + * GSSocksParser/GSSocks4Parser.h: + Parsing code for SOCKS versions 4 and 5. + 2013-07-03 Sebastian Reitenbach * Source/Additions/GSMime.m diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 7034f5893..6463ef30c 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -174,6 +174,10 @@ GSString.m \ GSTimSort.m \ GSTLS.m \ GSValue.m \ +GSSocksParser/GSSocksParser.m \ +GSSocksParser/GSSocksParserPrivate.m \ +GSSocksParser/GSSocks4Parser.m \ +GSSocksParser/GSSocks5Parser.m \ NSAffineTransform.m \ NSArchiver.m \ NSArray.m \ diff --git a/Source/GSSocksParser/GSSocks4Parser.h b/Source/GSSocksParser/GSSocks4Parser.h new file mode 100644 index 000000000..03649a657 --- /dev/null +++ b/Source/GSSocksParser/GSSocks4Parser.h @@ -0,0 +1,34 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocksParser.h" + +@interface GSSocks4Parser : GSSocksParser { +} + +@end + diff --git a/Source/GSSocksParser/GSSocks4Parser.m b/Source/GSSocksParser/GSSocks4Parser.m new file mode 100644 index 000000000..c53b8b552 --- /dev/null +++ b/Source/GSSocksParser/GSSocks4Parser.m @@ -0,0 +1,170 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocks4Parser.h" +#import "GSSocksParserPrivate.h" +#import + +typedef enum GSSocks4InternalError { + GSSocks4InternalErrorIPv6 = 0x4a +} GSSocks4InternalError; + +typedef enum GSSocks4ResponseStatus { + GSSocks4ResponseStatusAccessGranted = 0x5a, + GSSocks4ResponseStatusRequestRejected = 0x5b, + GSSocks4ResponseStatusIdentdFailed = 0x5c, + GSSocks4ResponseStatusUserNotConfirmed = 0x5d, +} GSSocks4ResponseStatus; + +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" +#endif + +@implementation GSSocks4Parser + +- (id) initWithConfiguration: (NSDictionary *)aConfiguration + address: (NSString *)anAddress + port: (NSUInteger)aPort +{ + if (nil != (self = [super init])) + { + configuration = [aConfiguration retain]; + address = [anAddress retain]; + port = aPort; + } + return self; +} + +- (void) start +{ + NSMutableData *data; + uint8_t *bytes; + uint8_t zero; + NSString *user; + GSSocksAddressType addressType; + + addressType = [self addressType]; + if (addressType == GSSocksAddressTypeIPv6) + { + NSError *error; + + error = [self errorWithCode: GSSocks4InternalErrorIPv6 + description: @"IPv6 addresses are not supported by SOCKS4 proxies"]; + [delegate parser: self encounteredError: error]; + return; + } + + data = [NSMutableData dataWithLength: 8]; + bytes = [data mutableBytes]; + bytes[0] = 0x4; + bytes[1] = 0x1; + *(uint16_t *)(bytes + 2) = htons((uint16_t)port); + if (addressType == GSSocksAddressTypeDomain) + { + bytes[4] = bytes[5] = bytes[6] = 0; + bytes[7] = 1; + } + else + { + const uint32_t *addressBytes = [[self addressData] bytes]; + + *(uint32_t *)(bytes + 4) = htonl(*addressBytes); + } + zero = 0x0; + user = [configuration objectForKey: NSStreamSOCKSProxyUserKey]; + if (user) + { + [data appendData: [user dataUsingEncoding: NSUTF8StringEncoding]]; + [data appendBytes: &zero length: 1]; + } + if (addressType == GSSocksAddressTypeDomain) + { + [data appendData: [address dataUsingEncoding: NSUTF8StringEncoding]]; + [data appendBytes: &zero length: 1]; + } + + [delegate parser: self formedRequest: data]; + [delegate parser: self needsMoreBytes: 8]; +} + +- (NSError *) errorWithResponseStatus: (NSInteger)aStatus +{ + NSString *description; + + switch ((GSSocks4ResponseStatus)aStatus) + { + case GSSocks4ResponseStatusRequestRejected: + description = @"request was rejected or the server failed to fulfil it"; + break; + case GSSocks4ResponseStatusIdentdFailed: + description = @"identd is not running or not reachable from the server"; + break; + case GSSocks4ResponseStatusUserNotConfirmed: + description = @"identd could not confirm the user ID string in the request"; + break; + default: + description = @"unknown"; + break; + } + description = [NSString stringWithFormat: + @"SOCKS4 connnection failed, reason: %@", description]; + return [self errorWithCode: aStatus description: description]; +} + +- (void) parseNextChunk: (NSData *)aChunk +{ + NSUInteger bndPort; + uint32_t addressBytes; + NSData *addressData; + NSString *bndAddress; + const uint8_t *bytes; + + bytes = [aChunk bytes]; + if (bytes[1] != GSSocks4ResponseStatusAccessGranted) + { + NSError *error = [self errorWithResponseStatus:bytes[1]]; + [delegate parser:self encounteredError:error]; + return; + } + + bndPort = ntohs(*(uint16_t *)(bytes + 2)); + addressBytes = ntohl(*(uint32_t *)(bytes + 4)); + addressData = [NSData dataWithBytesNoCopy: &addressBytes + length: 4 + freeWhenDone: NO]; + bndAddress = [self addressFromData: addressData + withType :GSSocksAddressTypeIPv4]; + + [delegate parser: self finishedWithAddress: bndAddress port: bndPort]; +} + +@end + +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif diff --git a/Source/GSSocksParser/GSSocks5Parser.h b/Source/GSSocksParser/GSSocks5Parser.h new file mode 100644 index 000000000..633526da0 --- /dev/null +++ b/Source/GSSocksParser/GSSocks5Parser.h @@ -0,0 +1,38 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocksParser.h" + +@interface GSSocks5Parser : GSSocksParser { + NSUInteger state; + NSUInteger addressSize; + uint8_t addressType; + BOOL stopped; +} + +@end + diff --git a/Source/GSSocksParser/GSSocks5Parser.m b/Source/GSSocksParser/GSSocks5Parser.m new file mode 100644 index 000000000..bb0479e5a --- /dev/null +++ b/Source/GSSocksParser/GSSocks5Parser.m @@ -0,0 +1,283 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocks5Parser.h" +#import "GSSocksParserPrivate.h" +#import + +typedef enum GSSocks5ParserState { + GSSocks5ParserStateHandshake, + GSSocks5ParserStateAuthenticationRequest, + GSSocks5ParserStateAuthenticationResponse, + GSSocks5ParserStateRequest, + GSSocks5ParserStateResponse, + GSSocks5ParserStateResponseAddressLength, + GSSocks5ParserStateResponseAddressAndPort, +} GSSocks5ParserState; + +typedef enum GSSocks5AuthenticationMethod { + GSSocks5AuthenticationMethodNone = 0x00, + GSSocks5AuthenticationMethodGSSAPI = 0x01, + GSSocks5AuthenticationMethodPassword = 0x02, + GSSocks5AuthenticationMethodNoAcceptable = 0xFF, +} GSSocks5AuthenticationMethod; + +typedef enum GSSocks5ResponseStatus { + GSSocks5ResponseStatusSuccess = 0x0, + GSSocks5ResponseStatusGeneralFailure = 0x1, + GSSocks5ResponseStatusConnectionNotAllowed = 0x2, + GSSocks5ResponseStatusNetworkUnreachable = 0x3, + GSSocks5ResponseStatusHostUnreachable = 0x4, + GSSocks5ResponseStatusConnectionRefused = 0x5, + GSSocks5ResponseStatusTTLExpired = 0x6, + GSSocks5ResponseStatusCommandNotSupported = 0x7, + GSSocks5ResponseStatusAddressTypeNotSupported = 0x8, +} GSSocks5ResponseStatus; + +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" +#endif + +@implementation GSSocks5Parser + +- (id) initWithConfiguration: (NSDictionary *)aConfiguration + address: (NSString *)anAddress + port: (NSUInteger)aPort +{ + if (nil != (self = [super init])) + { + configuration = [aConfiguration retain]; + address = [anAddress retain]; + port = aPort; + stopped = YES; + } + return self; +} + +- (void) start +{ + uint8_t bytes[3] = {0x5, 0x1, GSSocks5AuthenticationMethodNone}; + + state = GSSocks5ParserStateHandshake; + stopped = NO; + + if ([configuration objectForKey: NSStreamSOCKSProxyUserKey]) + { + bytes[2] = GSSocks5AuthenticationMethodPassword; + } + + [delegate parser: self + formedRequest: [NSData dataWithBytes: bytes length: 3]]; + [delegate parser: self needsMoreBytes: 2]; +} + +- (NSError *) errorWithResponseStatus: (NSInteger)aStatus +{ + NSString *description; + + switch ((GSSocks5ResponseStatus)aStatus) + { + case GSSocks5ResponseStatusGeneralFailure: + description = @"general server failure"; + break; + case GSSocks5ResponseStatusConnectionNotAllowed: + description = @"connection is not allowed by a ruleset"; + break; + case GSSocks5ResponseStatusNetworkUnreachable: + description = @"destination network is unreachable"; + break; + case GSSocks5ResponseStatusHostUnreachable: + description = @"destination host is unreachable"; + break; + case GSSocks5ResponseStatusConnectionRefused: + description = @"connection has been refused"; + break; + case GSSocks5ResponseStatusTTLExpired: + description = @"connection has timed out"; + break; + case GSSocks5ResponseStatusCommandNotSupported: + description = @"command is not supported"; + break; + case GSSocks5ResponseStatusAddressTypeNotSupported: + description = @"address type is not supported"; + break; + default: + description = @"unkown"; + break; + } + description = [NSString stringWithFormat: + @"SOCKS5 server failed to fulfil request, reason: %@", description]; + return [self errorWithCode: aStatus description: description]; +} + +- (void) reportError: (NSError *)anError +{ + stopped = YES; + [delegate parser: self encounteredError: anError]; +} + +- (void) parseNextChunk: (NSData *)aChunk +{ + const uint8_t *bytes; + if (stopped) + { + return; + } + bytes = [aChunk bytes]; + switch ((GSSocks5ParserState)state) + { + case GSSocks5ParserStateHandshake: + { + if (bytes[1] == GSSocks5AuthenticationMethodNoAcceptable) + { + NSError *error; + + error = [self + errorWithCode: GSSocks5AuthenticationMethodNoAcceptable + description: @"SOCKS server does not support" + @" requested authentication method"]; + [self reportError:error]; + break; + } + if (![configuration objectForKey: NSStreamSOCKSProxyUserKey]) + { + state = GSSocks5ParserStateRequest; + goto GSSocks5ParserStateRequest; + } + state = GSSocks5ParserStateAuthenticationRequest; + } + case GSSocks5ParserStateAuthenticationRequest: + { + NSString *username + = [configuration objectForKey: NSStreamSOCKSProxyUserKey]; + NSString *password + = [configuration objectForKey: NSStreamSOCKSProxyPasswordKey]; + uint8_t bytes[3] = { + 0x5, (uint8_t)[username length], (uint8_t)[password length]}; + NSMutableData *request + = [NSMutableData dataWithCapacity:bytes[1] + bytes[2] + 3]; + + [request appendBytes: bytes length: 2]; + [request appendBytes: [username UTF8String] length: bytes[1]]; + [request appendBytes: &bytes[2] length: 1]; + [request appendBytes: [password UTF8String] length: bytes[2]]; + + state = GSSocks5ParserStateAuthenticationResponse; + [delegate parser: self formedRequest: request]; + [delegate parser: self needsMoreBytes: 2]; + break; + } + case GSSocks5ParserStateAuthenticationResponse: + { + if (bytes[1]) + { + NSError *error; + + error = [self errorWithCode: 0xFF + bytes[1] + description: @"SOCKS authentication failed"]; + [self reportError: error]; + break; + } + state = GSSocks5ParserStateRequest; + } + GSSocks5ParserStateRequest: + case GSSocks5ParserStateRequest: + { + GSSocksAddressType type = [self addressType]; + uint8_t request[4] = { + 0x5, 0x1, 0x0, type + }; + uint16_t portWithNetworkEndianness; + NSMutableData *data = [NSMutableData dataWithBytes:request length:4]; + NSData *addressData = [self addressData]; + + if (type == GSSocksAddressTypeDomain) + { + uint8_t length = (uint8_t)[addressData length]; + [data appendBytes: &length length: 1]; + } + [data appendData: addressData]; + portWithNetworkEndianness = htons((uint16_t)port); + [data appendBytes: &portWithNetworkEndianness length: 2]; + + state = GSSocks5ParserStateResponse; + [delegate parser: self formedRequest: data]; + [delegate parser: self needsMoreBytes: 4]; + break; + } + case GSSocks5ParserStateResponse: + { + if (bytes[1] != GSSocks5ResponseStatusSuccess) + { + NSError *error = [self errorWithResponseStatus: bytes[1]]; + [self reportError: error]; + break; + } + addressType = bytes[3]; /* addess type */ + if (addressType == GSSocksAddressTypeDomain) + { + state = GSSocks5ParserStateResponseAddressLength; + [delegate parser: self needsMoreBytes: 1]; + } + else + { + state = GSSocks5ParserStateResponseAddressAndPort; + addressSize = addressType == GSSocksAddressTypeIPv4 ? 4 : 16; + [delegate parser: self needsMoreBytes: addressSize + 2]; + } + break; + } + case GSSocks5ParserStateResponseAddressLength: + { + addressSize = bytes[0]; + state = GSSocks5ParserStateResponseAddressAndPort; + [delegate parser: self needsMoreBytes: addressSize + 2]; + break; + } + case GSSocks5ParserStateResponseAddressAndPort: + { + NSString *bndAddress; + NSUInteger bndPort; + NSData *data; + + data = [NSData dataWithBytes: [aChunk bytes] + length: addressSize]; + bndAddress = [self addressFromData: data + withType: addressType]; + bndPort = ntohs(*(uint16_t *)(bytes + addressSize)); + [delegate parser: self finishedWithAddress: bndAddress port: bndPort]; + break; + } + } +} + +@end + +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif diff --git a/Source/GSSocksParser/GSSocksParser.h b/Source/GSSocksParser/GSSocksParser.h new file mode 100644 index 000000000..0905e097f --- /dev/null +++ b/Source/GSSocksParser/GSSocksParser.h @@ -0,0 +1,73 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import +#import +#import +#import +#import + +@class GSSocksParser; + +@protocol GSSocksParserDelegate + +- (void) parser: (GSSocksParser *)aParser + needsMoreBytes: (NSUInteger)aLength; +- (void) parser: (GSSocksParser *)aParser + formedRequest: (NSData *)aRequest; +- (void) parser: (GSSocksParser *)aParser +finishedWithAddress: (NSString *)anAddress +port: (NSUInteger)aPort; + +- (void) parser: (GSSocksParser *)aParser +encounteredError: (NSError *)anError; + +@end + +@interface GSSocksParser : NSObject +{ + NSDictionary *configuration; + NSString *address; + id delegate; + NSUInteger port; +} + +- (id) initWithConfiguration: (NSDictionary *)aConfiguration + address: (NSString *)anAddress + port: (NSUInteger)aPort; + +- (id) delegate; +- (void) setDelegate: (id)aDelegate; + +- (NSString *) address; +- (NSUInteger) port; + +- (void) start; +- (void) parseNextChunk: (NSData *)aChunk; + +@end + diff --git a/Source/GSSocksParser/GSSocksParser.m b/Source/GSSocksParser/GSSocksParser.m new file mode 100644 index 000000000..945cb4f2c --- /dev/null +++ b/Source/GSSocksParser/GSSocksParser.m @@ -0,0 +1,121 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocksParser.h" +#import "GSSocks4Parser.h" +#import "GSSocks5Parser.h" +#import "Foundation/NSException.h" + +@interface NSObject (SubclassResponsibility) +- (id) subclassResponsibility: (SEL)aSelector; +@end + +@implementation GSSocksParser + +- (id) init +{ + if (nil != (self = [super init])) + { + configuration = nil; + address = nil; + delegate = nil; + port = 0; + } + return self; +} + +- (id) initWithConfiguration: (NSDictionary *)aConfiguration + address: (NSString *)anAddress + port: (NSUInteger)aPort +{ + NSString *version; + Class concreteClass; + + version = [aConfiguration objectForKey: NSStreamSOCKSProxyVersionKey]; + version = version ? version : NSStreamSOCKSProxyVersion5; + + [self release]; + + if ([version isEqualToString: NSStreamSOCKSProxyVersion5]) + { + concreteClass = [GSSocks5Parser class]; + } + else if ([version isEqualToString: NSStreamSOCKSProxyVersion4]) + { + concreteClass = [GSSocks4Parser class]; + } + else + { + [NSException raise: NSInternalInconsistencyException + format: @"Unsupported socks verion: %@", version]; + } + return [[concreteClass alloc] initWithConfiguration: aConfiguration + address: anAddress + port: aPort]; +} + +- (void) dealloc +{ + [delegate release]; + [address release]; + [configuration release]; + [super dealloc]; +} + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate: (id)aDelegate +{ + id previous = delegate; + delegate = [aDelegate retain]; + [previous release]; +} + +- (NSString *) address +{ + return address; +} + +- (NSUInteger) port +{ + return port; +} + +- (void) start +{ + [self subclassResponsibility:_cmd]; +} + +- (void) parseNextChunk: (NSData *)aChunk +{ + [self subclassResponsibility: _cmd]; +} + +@end diff --git a/Source/GSSocksParser/GSSocksParserPrivate.h b/Source/GSSocksParser/GSSocksParserPrivate.h new file mode 100644 index 000000000..122b1e785 --- /dev/null +++ b/Source/GSSocksParser/GSSocksParserPrivate.h @@ -0,0 +1,49 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocksParser.h" + +typedef enum GSSocksAddressType { + GSSocksAddressTypeIPv4 = 0x1, + GSSocksAddressTypeIPv6 = 0x4, + GSSocksAddressTypeDomain = 0x3, +} GSSocksAddressType; + +@interface GSSocksParser (Private) + +- (NSError *) errorWithCode: (NSInteger)aCode + description: (NSString *)aDescription; + + +- (GSSocksAddressType) addressType; + +- (NSData *) addressData; +- (NSString *) addressFromData: (NSData *)aData + withType: (GSSocksAddressType)anAddressType; + +@end + diff --git a/Source/GSSocksParser/GSSocksParserPrivate.m b/Source/GSSocksParser/GSSocksParserPrivate.m new file mode 100644 index 000000000..2c113690c --- /dev/null +++ b/Source/GSSocksParser/GSSocksParserPrivate.m @@ -0,0 +1,219 @@ +/* + * Parsers of SOCKS protocol messages + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * Written by Marat Ibadinov + * Date: 2013 + * + * This file is part of the GNUstep Base Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110 USA. + * + * $Date$ $Revision$ + */ + +#import "GSSocksParserPrivate.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSBundle.h" +#import "Foundation/NSCharacterSet.h" +#import "Foundation/NSException.h" +#import + +@interface NSString (GSSocksParser) +- (NSString *) stringByRepeatingCurrentString: (NSUInteger)times; +@end + +@implementation NSString (GSSocksParser) + +- (NSString *) stringByRepeatingCurrentString: (NSUInteger)times +{ + return [@"" stringByPaddingToLength: times * [self length] + withString: self + startingAtIndex: 0]; +} + +@end + +@implementation GSSocksParser (Private) + +- (NSError *) errorWithCode: (NSInteger)aCode + description: (NSString *)aDescription +{ + NSDictionary *userInfo; + + aDescription = NSLocalizedString(aDescription, @""); + userInfo = [NSDictionary dictionaryWithObject: aDescription + forKey: NSLocalizedDescriptionKey]; + return [NSError errorWithDomain: NSStreamSOCKSErrorDomain + code: aCode + userInfo: userInfo]; +} + +- (GSSocksAddressType) addressType +{ + const char *cAddress; + NSUInteger index; + BOOL hasAlpha; + BOOL hasDot; + char character; + + if ([address length] > 16) + { + return GSSocksAddressTypeDomain; + } + cAddress = [address UTF8String]; + index = 0; + hasAlpha = NO; + hasDot = NO; + + while (0 != (character = cAddress[index])) + { + BOOL isAlpha = character >= 'a' && character <= 'f'; + + if (!(character >= '0' && character <= '9') + && !isAlpha && character != '.' && character != ':') + { + return GSSocksAddressTypeDomain; + } + hasAlpha = hasAlpha || isAlpha; + hasDot = hasDot || character == '.'; + ++index; + } + return hasAlpha && hasDot ? GSSocksAddressTypeDomain + : (hasDot ? GSSocksAddressTypeIPv4 : GSSocksAddressTypeIPv6); +} + +- (NSData *) addressData +{ + switch ([self addressType]) + { + case GSSocksAddressTypeIPv4: + { + NSMutableData *result = [NSMutableData dataWithLength: 4]; + const char *cString = [address UTF8String]; + uint8_t *bytes = [result mutableBytes]; + + sscanf(cString, "%hhu.%hhu.%hhu.%hhu", + &bytes[0], &bytes[1], &bytes[2], &bytes[3]); + return result; + } + case GSSocksAddressTypeIPv6: + { + NSArray *components = [address componentsSeparatedByString: @"::"]; + NSMutableData *result; + uint16_t *bytes; + + if ([components count] == 2) + { + NSString *leading; + NSString *trailing; + NSCharacterSet *charset; + NSArray *separated; + NSUInteger leadingCount; + NSUInteger trailingCount; + + leading = [components objectAtIndex: 0]; + trailing = [components objectAtIndex: 1]; + charset + = [NSCharacterSet characterSetWithCharactersInString: @":"]; + separated + = [leading componentsSeparatedByCharactersInSet: charset]; + leadingCount = [leading length] ? [separated count] : 0; +/* FIXME ... do we need to add this following statement? + separated + = [trailing componentsSeparatedByCharactersInSet: charset]; +*/ + trailingCount = [trailing length] ? [separated count] : 0; + + if (leadingCount && trailingCount) + { + NSString *middle; + + middle = [@"0:" stringByRepeatingCurrentString: + 8 - leadingCount - trailingCount]; + address = [[[leading stringByAppendingString: @":"] + stringByAppendingString: middle] + stringByAppendingString: trailing]; + } + else if (!leadingCount) + { + NSString *start; + + start = [@"0:" stringByRepeatingCurrentString: + 8 - trailingCount]; + address = [start stringByAppendingString: trailing]; + } + else + { + NSString *end; + + end = [@":0" stringByRepeatingCurrentString: + 8 - leadingCount]; + address = [leading stringByAppendingString: end]; + } + } + + result = [NSMutableData dataWithLength:16]; + bytes = [result mutableBytes]; + sscanf([address UTF8String], "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + &bytes[0], &bytes[1], &bytes[2], &bytes[3], + &bytes[4], &bytes[5], &bytes[6], &bytes[7]); + return result; + } + case GSSocksAddressTypeDomain: + { + return [address dataUsingEncoding:NSUTF8StringEncoding]; + } + default: + [NSException raise: NSInternalInconsistencyException + format: @"Unknown address type"]; + return nil; + } +} + +- (NSString *) addressFromData: (NSData *)aData + withType: (GSSocksAddressType)anAddressType +{ + switch (anAddressType) + { + case GSSocksAddressTypeIPv4: + { + const uint8_t *bytes = [aData bytes]; + + return [NSString stringWithFormat: @"%hhu.%hhu.%hhu.%hhu", + bytes[0], bytes[1], bytes[2], bytes[3]]; + } + case GSSocksAddressTypeIPv6: + { + const uint16_t *bytes = [aData bytes]; + + return [NSString stringWithFormat: @"%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7]]; + } + case GSSocksAddressTypeDomain: + { + return [[[NSString alloc] initWithData: aData + encoding: NSUTF8StringEncoding] autorelease]; + } + default: + [NSException raise: NSInternalInconsistencyException + format: @"Unknown address type"]; + return nil; + } +} + +@end