/** Implementation for GSSocketStream for GNUStep Copyright (C) 2006-2008 Free Software Foundation, Inc. Written by: Derek Zhou Written by: Richard Frith-Macdonald Date: 2006 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 Lesser 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., 31 Milk Street #960789 Boston, MA 02196 USA. */ #import "common.h" #import "Foundation/NSArray.h" #import "Foundation/NSByteOrder.h" #import "Foundation/NSData.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSEnumerator.h" #import "Foundation/NSException.h" #import "Foundation/NSHost.h" #import "Foundation/NSLock.h" #import "Foundation/NSNotification.h" #import "Foundation/NSRunLoop.h" #import "Foundation/NSUserDefaults.h" #import "Foundation/NSValue.h" #import "GSPrivate.h" #import "GSStream.h" #import "GSSocketStream.h" #import "GNUstepBase/GSTLS.h" #ifndef SHUT_RD # ifdef SD_RECEIVE # define SHUT_RD SD_RECEIVE # define SHUT_WR SD_SEND # define SHUT_RDWR SD_BOTH # else # define SHUT_RD 0 # define SHUT_WR 1 # define SHUT_RDWR 2 # endif #endif #ifdef _WIN32 #ifdef HAVE_WS2TCPIP_H #include #endif // HAVE_WS2TCPIP_H #if !defined(HAVE_INET_NTOP) extern const char* WSAAPI inet_ntop(int, const void *, char *, size_t); #endif #if !defined(HAVE_INET_NTOP) extern int WSAAPI inet_pton(int , const char *, void *); #endif #define OPTLEN int #else // _WIN32 #define OPTLEN socklen_t #endif // _WIN32 // Maximum data in single I/O operation #define NETBUF_SIZE (1024 * 16) #define READ_SIZE NETBUF_SIZE*10 @implementation GSTcpTune static int tuneDelay = 0; static int tuneLinger = -1; static int tuneReceive = 0; static BOOL tuneSendAll = NO; static int tuneRcvBuf = 0; static int tuneSndBuf = 0; + (void) defaultsChanged: (NSNotification*)n { NSUserDefaults *defs = (NSUserDefaults*)[n object]; NSString *str; if (nil == defs) { defs = [NSUserDefaults standardUserDefaults]; } str = [defs stringForKey: @"GSTcpLinger"]; if (nil == str) { tuneLinger = -1; } else { tuneLinger = [str intValue]; } tuneRcvBuf = (int)[defs integerForKey: @"GSTcpRcvBuf"]; tuneSndBuf = (int)[defs integerForKey: @"GSTcpSndBuf"]; tuneReceive = (int)[defs integerForKey: @"GSTcpReceive"]; tuneSendAll = [defs boolForKey: @"GSTcpSendAll"]; tuneDelay = [defs boolForKey: @"GSTcpDelay"]; } + (void) initialize { static BOOL beenHere = NO; if (NO == beenHere) { NSNotificationCenter *nc; NSUserDefaults *defs; beenHere = YES; nc = [NSNotificationCenter defaultCenter]; defs = [NSUserDefaults standardUserDefaults]; [nc addObserver: self selector: @selector(defaultsChanged:) name: NSUserDefaultsDidChangeNotification object: defs]; [self defaultsChanged: nil]; } } + (int) delay { return tuneDelay; // Milliseconds to delay close } + (int) recvSize { if (tuneReceive > 0) { return tuneReceive; // Return receive buffer size } if (tuneRcvBuf > 0) { return tuneRcvBuf; // Return socket receive buffer size } return READ_SIZE; // Return hard-coded default } + (int) sendSize: (int)bytesToSend { if (YES == tuneSendAll) { return bytesToSend; // Try to send all in one go } if (tuneSndBuf > 0 && tuneSndBuf <= bytesToSend) { return tuneSndBuf; // Limit to socket send buffer } if (NETBUF_SIZE <= bytesToSend) { return NETBUF_SIZE; // Limit to hard coded default } return bytesToSend; } + (void) tune: (void*)handle with: (id)opts { int desc = (int)(intptr_t)handle; int value; id o; #ifndef BROKEN_SO_REUSEADDR /* * Under decent systems, SO_REUSEADDR means that the port can be reused * immediately that this process exits. Under some it means * that multiple processes can serve the same port simultaneously. * We don't want that broken behavior! */ value = 1; if (setsockopt(desc, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(value)) < 0) { NSDebugMLLog(@"GSTcpTune", @"setsockopt reuseaddr failed for %d", desc); } #endif /* * Enable tcp-level tracking of whether connection is alive. */ value = 1; if (setsockopt(desc, SOL_SOCKET, SO_KEEPALIVE, (char *)&value, sizeof(value)) < 0) { NSDebugMLLog(@"GSTcpTune", @"setsockopt keepalive failed for %d", desc); } #define OPTS(X) ([opts isKindOfClass: [NSStream class]] \ ? [(NSStream*)opts propertyForKey: X] \ : [(NSDictionary*)opts objectForKey: X]) o = OPTS(@"GSTcpLinger"); value = (o ? [o intValue] : tuneLinger); if (value >= 0) { struct linger l; l.l_onoff = 1; l.l_linger = tuneLinger; if (setsockopt(desc, SOL_SOCKET, SO_LINGER, (char *)&l, sizeof(l)) < 0) { NSLog(@"Failed to set GSTcpLinger for %d to %d: %@", desc, value, [NSError _last]); } else { NSDebugMLLog(@"GSTcpTune", @"Set GSTcpLinger for %d to %d", desc, value); } } o = OPTS(@"GSTcpRcvBuf"); value = (o ? [o intValue] : tuneRcvBuf); if (value > 0) { /* Set the receive buffer for the socket. */ if (setsockopt(desc, SOL_SOCKET, SO_RCVBUF, (char *)&value, sizeof(value)) < 0) { NSLog(@"Failed to set GSTcpRcvBuf for %d to %d: %@", desc, value, [NSError _last]); } else { NSDebugMLLog(@"GSTcpTune", @"Set GSTcpRcvBuf for %d to %d", desc, value); } } o = OPTS(@"GSTcpSndBuf"); value = (o ? [o intValue] : tuneSndBuf); if (value > 0) { /* Set the send buffer for the socket. */ if (setsockopt(desc, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(value)) < 0) { NSLog(@"Failed to set GSTcpSndBuf for %d to %d: %@", desc, value, [NSError _last]); } else { NSDebugMLLog(@"GSTcpTune", @"Set GSTcpSndBuf for %d to %d", desc, value); } } } @end unsigned GSPrivateSockaddrLength(struct sockaddr *addr) { switch (addr->sa_family) { case AF_INET: return sizeof(struct sockaddr_in); #ifdef AF_INET6 case AF_INET6: return sizeof(struct sockaddr_in6); #endif #ifndef _WIN32 case AF_LOCAL: return sizeof(struct sockaddr_un); #endif default: return 0; } } NSString * GSPrivateSockaddrHost(struct sockaddr *addr) { char buf[40]; #if defined(AF_INET6) if (AF_INET6 == addr->sa_family) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)(void*)addr; inet_ntop(AF_INET, &addr6->sin6_addr, buf, sizeof(buf)); return [NSString stringWithUTF8String: buf]; } #endif inet_ntop(AF_INET, &((struct sockaddr_in*)(void*)addr)->sin_addr, buf, sizeof(buf)); return [NSString stringWithUTF8String: buf]; } NSString * GSPrivateSockaddrName(struct sockaddr *addr) { return [NSString stringWithFormat: @"%@:%d", GSPrivateSockaddrHost(addr), GSPrivateSockaddrPort(addr)]; } uint16_t GSPrivateSockaddrPort(struct sockaddr *addr) { uint16_t port; #if defined(AF_INET6) if (AF_INET6 == addr->sa_family) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)(void*)addr; port = addr6->sin6_port; port = GSSwapBigI16ToHost(port); return port; } #endif port = ((struct sockaddr_in*)(void*)addr)->sin_port; port = GSSwapBigI16ToHost(port); return port; } BOOL GSPrivateSockaddrSetup(NSString *machine, uint16_t port, NSString *service, NSString *protocol, struct sockaddr *sin) { memset(sin, '\0', sizeof(*sin)); sin->sa_family = AF_INET; /* If we were given a hostname, we use any address for that host. * Otherwise we expect the given name to be an address unless it is * a null (any address). */ if (0 != [machine length]) { const char *n; n = [machine UTF8String]; if ((!isdigit(n[0]) || sscanf(n, "%*d.%*d.%*d.%*d") != 4) && 0 == strchr(n, ':')) { machine = [[NSHost hostWithName: machine] address]; n = [machine UTF8String]; } if (0 == n) { return NO; } if (0 == strchr(n, ':')) { struct sockaddr_in *addr = (struct sockaddr_in*)(void*)sin; if (inet_pton(AF_INET, n, &addr->sin_addr) <= 0) { return NO; } } else { #if defined(AF_INET6) struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)(void*)sin; sin->sa_family = AF_INET6; if (inet_pton(AF_INET6, n, &addr6->sin6_addr) <= 0) { return NO; } #else return NO; #endif } } else { ((struct sockaddr_in*)(void*)sin)->sin_addr.s_addr = GSSwapHostI32ToBig(INADDR_ANY); } /* The optional service and protocol parameters may be used to * look up the port */ if (nil != service) { const char *sname; const char *proto; struct servent *sp; if (nil == protocol) { proto = "tcp"; } else { proto = [protocol UTF8String]; } sname = [service UTF8String]; if ((sp = getservbyname(sname, proto)) == 0) { const char* ptr = sname; int val = atoi(ptr); while (isdigit(*ptr)) { ptr++; } if (*ptr == '\0' && val <= 0xffff) { port = val; } else if (strcmp(ptr, "gdomap") == 0) { #ifdef GDOMAP_PORT_OVERRIDE port = GDOMAP_PORT_OVERRIDE; #else port = 538; // IANA allocated port #endif } else { return NO; } } else { port = GSSwapBigI16ToHost(sp->s_port); } } #if defined(AF_INET6) if (AF_INET6 == sin->sa_family) { ((struct sockaddr_in6*)(void*)sin)->sin6_port = GSSwapHostI16ToBig(port); } else { ((struct sockaddr_in*)(void*)sin)->sin_port = GSSwapHostI16ToBig(port); } #else ((struct sockaddr_in*)sin)->sin_port = GSSwapHostI16ToBig(port); #endif return YES; } /** The GSStreamHandler abstract class defines the methods used to * implement a handler object for a pair of streams. * The idea is that the handler is installed once the connection is * open, and a handshake is initiated. During the handshake process * all stream events are sent to the handler rather than to the * stream delegate (the streams know to do this because the -handshake * method returns YES to tell them so). * While a handler is installed, the -read:maxLength: and -write:maxLength: * methods of the handle rare called instead of those of the streams (and * the handler may perform I/O using the streams by calling the private * -_read:maxLength: and _write:maxLength: methods instead of the public * methods). */ @interface GSStreamHandler : NSObject { GSSocketInputStream *istream; // Not retained GSSocketOutputStream *ostream; // Not retained BOOL initialised; BOOL handshake; BOOL active; } + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o; - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o; - (GSSocketInputStream*) istream; - (GSSocketOutputStream*) ostream; - (void) bye; /* Close down the handled session. */ - (BOOL) handshake; /* A handshake/hello is in progress. */ - (void) hello; /* Start up the session handshake. */ - (BOOL) readable; /* is there buffered data to read? */ - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len; - (void) remove: (NSStream*)stream; /* Stream no longer available */ - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event; - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len; @end @implementation GSStreamHandler + (void) initialize { } + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { [self subclassResponsibility: _cmd]; } - (void) bye { [self subclassResponsibility: _cmd]; } - (BOOL) handshake { return handshake; } - (void) hello { [self subclassResponsibility: _cmd]; } - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { istream = i; ostream = o; handshake = YES; return self; } - (GSSocketInputStream*) istream { return istream; } - (GSSocketOutputStream*) ostream { return ostream; } - (BOOL) readable { return NO; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { [self subclassResponsibility: _cmd]; return 0; } - (void) remove: (NSStream*)stream { if ((id)stream == (id)istream) { istream = nil; } if ((id)stream == (id)ostream) { ostream = nil; } } - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event { [self subclassResponsibility: _cmd]; } - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len { [self subclassResponsibility: _cmd]; return 0; } @end #if defined(HAVE_GNUTLS) @interface GSTLSHandler : GSStreamHandler { @public GSTLSSession *session; } /** Populates the dictionary 'dict', copying in all the properties * of the supplied streams. If a property is set for both then * the output stream's one has precedence. */ + (void) populateProperties: (NSMutableDictionary**)dict withSecurityLevel: (NSString*)l fromInputStream: (NSStream*)i orOutputStream: (NSStream*)o; /** Called on verification of the remote end's certificate to tell the * delegate of the input stream who the certificate issuer and owner are. */ - (void) stream: (NSStream*)stream issuer: (NSString*)i owner: (NSString*)o; @end /* Callback to allow the TLS code to pull data from the remote system. * If the operation fails, this sets the error number. */ static ssize_t GSTLSPull(gnutls_transport_ptr_t handle, void *buffer, size_t len) { ssize_t result; GSTLSHandler *tls = (GSTLSHandler*)handle; result = [[tls istream] _read: buffer maxLength: len]; if (result < 0) { int e; if ([[tls istream] streamStatus] == NSStreamStatusError) { e = [[[(GSTLSHandler*)handle istream] streamError] code]; } else { e = EAGAIN; // Tell GNUTLS this would block. } #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno (tls->session->session, e); #else errno = e; // Not thread-safe #endif } return result; } /* Callback to allow the TLS code to push data to the remote system. * If the operation fails, this sets the error number. */ static ssize_t GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) { ssize_t result; GSTLSHandler *tls = (GSTLSHandler*)handle; result = [[tls ostream] _write: buffer maxLength: len]; if (result < 0) { int e; if ([[tls ostream] streamStatus] == NSStreamStatusError) { e = [[[tls ostream] streamError] code]; NSDebugFLLog(@"NSStream", @"GSTLSPush write for %@ error %d (%s)", [tls ostream], e, strerror(e)); } else { e = EAGAIN; // Tell GNUTLS this would block. NSDebugFLLog(@"NSStream", @"GSTLSPush write for %@ of %lu would block", [tls ostream], (unsigned long)len); } #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno(tls->session->session, e); #else errno = e; // Not thread-safe #endif return -1; } if (len != result) { NSDebugFLLog(@"NSStream", @"GSTLSPush write for %@ of %ld (tried %lu)", [tls ostream], (long)result, (unsigned long)len); } else { NSDebugFLLog(@"NSStream", @"GSTLSPush write for %@ of %ld success", [tls ostream], (long)result); } return result; } @implementation GSTLSHandler static NSArray *keys = nil; + (void) initialize { [GSTLSObject class]; if (nil == keys) { keys = [[NSArray alloc] initWithObjects: GSTLSCAFile, GSTLSCertificateFile, GSTLSCertificateKeyFile, GSTLSCertificateKeyPassword, GSTLSDebug, GSTLSIssuers, GSTLSOwners, GSTLSPriority, GSTLSRemoteHosts, GSTLSRevokeFile, GSTLSServerName, GSTLSVerify, nil]; [[NSObject leakAt: &keys] release]; } } + (void) populateProperties: (NSMutableDictionary**)dict withSecurityLevel: (NSString*)l fromInputStream: (NSStream*)i orOutputStream: (NSStream*)o { if (NULL != dict) { NSString *str; NSMutableDictionary *opts = *dict; NSUInteger count; if (nil != l) { [opts setObject: l forKey: NSStreamSocketSecurityLevelKey]; } count = [keys count]; while (count-- > 0) { NSString *key = [keys objectAtIndex: count]; str = [o propertyForKey: key]; if (nil == str) str = [i propertyForKey: key]; if (nil != str) [opts setObject: str forKey: key]; } } else { NSWarnLog(@"%@ requires not nil 'dict'", NSStringFromSelector(_cmd)); } } + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSString *tls; tls = [i propertyForKey: NSStreamSocketSecurityLevelKey]; if (tls == nil) { tls = [o propertyForKey: NSStreamSocketSecurityLevelKey]; if (tls != nil) { [i setProperty: tls forKey: NSStreamSocketSecurityLevelKey]; } } else { [o setProperty: tls forKey: NSStreamSocketSecurityLevelKey]; } if (tls != nil) { GSTLSHandler *h; h = [[GSTLSHandler alloc] initWithInput: i output: o]; [i _setHandler: h]; [o _setHandler: h]; RELEASE(h); } } - (void) bye { handshake = NO; active = NO; [session disconnect: NO]; } - (void) dealloc { [self bye]; DESTROY(session); [super dealloc]; } - (BOOL) handshake { return handshake; } - (void) hello { if (active == NO) { if (handshake == NO) { /* Set flag to say we are now doing a handshake. */ handshake = YES; } if ([session handshake] == YES) { handshake = NO; // Handshake is now complete. active = [session active]; // Is the TLS session now active? if (NO == active) { NSString *problem = [session problem]; NSError *theError; if (nil == problem) { problem = @"TLS handshake failure"; } theError = [NSError errorWithDomain: NSCocoaErrorDomain code: 0 userInfo: [NSDictionary dictionaryWithObject: problem forKey: NSLocalizedDescriptionKey]]; if ([istream streamStatus] != NSStreamStatusError) { [istream _recordError: theError]; } if ([ostream streamStatus] != NSStreamStatusError) { [ostream _recordError: theError]; } [self bye]; } else { NSString *issuer = [session issuer]; NSString *owner = [session owner]; id del = [istream delegate]; if (nil != issuer && nil != owner && [del respondsToSelector: @selector(stream:issuer:owner:)]) { [del stream: istream issuer: issuer owner: owner]; } } } } } - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSString *str; NSMutableDictionary *opts; BOOL server; // Check whether the input stream has been accepted by a listening socket server = [[i propertyForKey: @"IsServer"] boolValue]; str = [o propertyForKey: NSStreamSocketSecurityLevelKey]; if (nil == str) str = [i propertyForKey: NSStreamSocketSecurityLevelKey]; if ([str isEqual: NSStreamSocketSecurityLevelNone] == YES) { GSOnceMLog(@"NSStreamSocketSecurityLevelNone is insecure ..." @" not implemented"); DESTROY(self); return nil; } else if ([str isEqual: NSStreamSocketSecurityLevelSSLv2] == YES) { GSOnceMLog(@"NSStreamSocketSecurityLevelTLSv2 is insecure ..." @" not implemented"); DESTROY(self); return nil; } else if ([str isEqual: NSStreamSocketSecurityLevelSSLv3] == YES) { str = @"SSLv3"; } else if ([str isEqual: NSStreamSocketSecurityLevelTLSv1] == YES) { str = @"TLSV1"; } else { str = nil; } if ((self = [super initWithInput: i output: o]) == nil) { return nil; } /* Create the options dictionary, copying in any option from the stream * properties. GSTLSPriority overrides NSStreamSocketSecurityLevelKey. */ opts = [NSMutableDictionary new]; [[self class] populateProperties: &opts withSecurityLevel: str fromInputStream: i orOutputStream: o]; session = [[GSTLSSession alloc] initWithOptions: opts direction: (server ? NO : YES) transport: (void*)self push: GSTLSPush pull: GSTLSPull]; [opts release]; initialised = YES; return self; } - (GSSocketInputStream*) istream { return istream; } - (GSSocketOutputStream*) ostream { return ostream; } - (BOOL) readable { if (NO == active || YES == handshake) { return NO; } return ([session pending] > 0) ? YES : NO; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { return [session read: buffer length: len]; } - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event { NSDebugMLLog(@"NSStream", @"GSTLSHandler got %@ on %@", [stream stringFromEvent: event], stream); if (handshake == YES) { switch (event) { case NSStreamEventHasSpaceAvailable: case NSStreamEventHasBytesAvailable: case NSStreamEventOpenCompleted: /* try to complete the handshake. */ [self hello]; break; case NSStreamEventErrorOccurred: case NSStreamEventEndEncountered: /* stream error or close ... handshake fails. */ handshake = NO; break; default: break; } if (NO == handshake) { NSDebugMLLog(@"NSStream", @"GSTLSHandler completed on %@", stream); /* Make sure that, if ostream gets released as a result of * the event we send to istream, it doesn't get deallocated * and cause a crash when we try to send to it. */ AUTORELEASE(RETAIN(ostream)); if ([istream streamStatus] == NSStreamStatusOpen) { [istream _resetEvents: NSStreamEventOpenCompleted]; [istream _sendEvent: NSStreamEventOpenCompleted]; [istream _schedule]; } else { [istream _resetEvents: NSStreamEventErrorOccurred]; [istream _sendEvent: NSStreamEventErrorOccurred]; } if ([ostream streamStatus] == NSStreamStatusOpen) { [ostream _resetEvents: NSStreamEventOpenCompleted | NSStreamEventHasSpaceAvailable]; [ostream _sendEvent: NSStreamEventOpenCompleted]; [ostream _sendEvent: NSStreamEventHasSpaceAvailable]; [ostream _schedule]; } else { [ostream _resetEvents: NSStreamEventErrorOccurred]; [ostream _sendEvent: NSStreamEventErrorOccurred]; } } } } - (void) stream: (NSStream*)stream issuer: (NSString*)i owner: (NSString*)o { return; } - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len { NSInteger offset = 0; /* The low level code to perform the TLS session write may return a * partial write even though the output stream is still writable. * That means we wouldn't get an event to say there's more space and * our overall write (for a large amount of data) could hang. * To avoid that, we try writing more data as long as the stream * still has space available. */ while ([ostream hasSpaceAvailable] && offset < len) { NSInteger written; written = [session write: buffer + offset length: len - offset]; if (written > 0) { offset += written; } } return offset; } @end #else /* HAVE_GNUTLS */ /* GNUTLS not available ... */ @interface GSTLSHandler : GSStreamHandler @end @implementation GSTLSHandler static NSArray *keys = nil; + (void) initialize { if (nil == keys) { keys = [[NSArray alloc] initWithObjects: GSTLSCAFile, GSTLSCertificateFile, GSTLSCertificateKeyFile, GSTLSCertificateKeyPassword, GSTLSDebug, GSTLSIssuers, GSTLSOwners, GSTLSPriority, GSTLSRemoteHosts, GSTLSRevokeFile, GSTLSVerify, nil]; [[NSObject leakAt: &keys] release]; } } + (void) populateProperties: (NSMutableDictionary**)dict withSecurityLevel: (NSString*)l fromInputStream: (NSStream*)i orOutputStream: (NSStream*)o { NSString *str; NSMutableDictionary *opts = *dict; NSUInteger count; if (NULL != dict) { if (nil != l) { [opts setObject: l forKey: NSStreamSocketSecurityLevelKey]; } count = [keys count]; while (count-- > 0) { NSString *key = [keys objectAtIndex: count]; str = [o propertyForKey: key]; if (nil == str) str = [i propertyForKey: key]; if (nil != str) [opts setObject: str forKey: key]; } } else { NSWarnLog(@"%@ requires not nil 'dict'", NSStringFromSelector(_cmd)); } } + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSString *tls; tls = [i propertyForKey: NSStreamSocketSecurityLevelKey]; if (tls == nil) { tls = [o propertyForKey: NSStreamSocketSecurityLevelKey]; } if (tls != nil && [tls isEqualToString: NSStreamSocketSecurityLevelNone] == NO) { NSLog(@"Attempt to use SSL/TLS without support."); NSLog(@"Please reconfigure gnustep-base with GNU TLS."); } return; } - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { DESTROY(self); return nil; } @end #endif /* HAVE_GNUTLS */ /* * States for socks connection negotiation */ static NSString * const GSSOCKSOfferAuth = @"GSSOCKSOfferAuth"; static NSString * const GSSOCKSRecvAuth = @"GSSOCKSRecvAuth"; static NSString * const GSSOCKSSendAuth = @"GSSOCKSSendAuth"; static NSString * const GSSOCKSAckAuth = @"GSSOCKSAckAuth"; static NSString * const GSSOCKSSendConn = @"GSSOCKSSendConn"; static NSString * const GSSOCKSAckConn = @"GSSOCKSAckConn"; @interface GSSOCKS : GSStreamHandler { NSString *state; /* Not retained */ NSString *address; NSString *port; int roffset; int woffset; int rwant; unsigned char rbuffer[128]; } - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event; @end @implementation GSSOCKS + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSDictionary *conf; conf = [i propertyForKey: NSStreamSOCKSProxyConfigurationKey]; if (conf == nil) { conf = [o propertyForKey: NSStreamSOCKSProxyConfigurationKey]; if (conf != nil) { [i setProperty: conf forKey: NSStreamSOCKSProxyConfigurationKey]; } } else { [o setProperty: conf forKey: NSStreamSOCKSProxyConfigurationKey]; } if (conf != nil) { GSSOCKS *h; struct sockaddr *sa = [i _address]; NSString *v; BOOL i6 = NO; v = [conf objectForKey: NSStreamSOCKSProxyVersionKey]; if ([v isEqualToString: NSStreamSOCKSProxyVersion4] == YES) { v = NSStreamSOCKSProxyVersion4; } else { v = NSStreamSOCKSProxyVersion5; } #if defined(AF_INET6) if (sa->sa_family == AF_INET6) { i6 = YES; } else #endif if (sa->sa_family != AF_INET) { GSOnceMLog(@"SOCKS not supported for socket type %d", sa->sa_family); return; } if (v == NSStreamSOCKSProxyVersion5) { GSOnceMLog(@"SOCKS 5 not supported yet"); return; } else if (i6 == YES) { GSOnceMLog(@"INET6 not supported with SOCKS 4"); return; } h = [[GSSOCKS alloc] initWithInput: i output: o]; [i _setHandler: h]; [o _setHandler: h]; RELEASE(h); } } - (void) bye { if (handshake == YES) { GSSocketInputStream *is = RETAIN(istream); GSSocketOutputStream *os = RETAIN(ostream); handshake = NO; [is _setHandler: nil]; [os _setHandler: nil]; [GSTLSHandler tryInput: is output: os]; if ([is streamStatus] == NSStreamStatusOpen) { [is _resetEvents: NSStreamEventOpenCompleted]; [is _sendEvent: NSStreamEventOpenCompleted]; } else { [is _resetEvents: NSStreamEventErrorOccurred]; [is _sendEvent: NSStreamEventErrorOccurred]; } if ([os streamStatus] == NSStreamStatusOpen) { [os _resetEvents: NSStreamEventOpenCompleted | NSStreamEventHasSpaceAvailable]; [os _sendEvent: NSStreamEventOpenCompleted]; [os _sendEvent: NSStreamEventHasSpaceAvailable]; } else { [os _resetEvents: NSStreamEventErrorOccurred]; [os _sendEvent: NSStreamEventErrorOccurred]; } RELEASE(is); RELEASE(os); } } - (void) dealloc { RELEASE(address); RELEASE(port); [super dealloc]; } - (void) hello { if (handshake == NO) { handshake = YES; /* Now send self an event to say we can write, to kick off the * handshake with the SOCKS server. */ [self stream: ostream handleEvent: NSStreamEventHasSpaceAvailable]; } } - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { if ((self = [super initWithInput: i output: o]) != nil) { if ([istream isKindOfClass: [GSInetInputStream class]] == NO) { NSLog(@"Attempt to use SOCKS with non-INET stream ignored"); DESTROY(self); } #if defined(AF_INET6) else if ([istream isKindOfClass: [GSInet6InputStream class]] == YES) { GSOnceMLog(@"INET6 not supported with SOCKS yet..."); DESTROY(self); } #endif /* AF_INET6 */ else { struct sockaddr_in *addr; NSDictionary *conf; NSString *host; int pnum; /* Record the host and port that the streams are supposed to be * connecting to. */ addr = (struct sockaddr_in*)(void*)[istream _address]; address = [[NSString alloc] initWithUTF8String: (char*)inet_ntoa(addr->sin_addr)]; port = [[NSString alloc] initWithFormat: @"%d", (int)GSSwapBigI16ToHost(addr->sin_port)]; /* Now reconfigure the streams so they will actually connect * to the socks proxy server. */ conf = [istream propertyForKey: NSStreamSOCKSProxyConfigurationKey]; host = [conf objectForKey: NSStreamSOCKSProxyHostKey]; pnum = [[conf objectForKey: NSStreamSOCKSProxyPortKey] intValue]; [istream _setSocketAddress: host port: pnum family: AF_INET]; [ostream _setSocketAddress: host port: pnum family: AF_INET]; } } return self; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { return [istream _read: buffer maxLength: len]; } - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event { NSString *error = nil; NSDictionary *conf; NSString *user; NSString *pass; if (event == NSStreamEventErrorOccurred || [stream streamStatus] == NSStreamStatusError || [stream streamStatus] == NSStreamStatusClosed) { [self bye]; return; } conf = [stream propertyForKey: NSStreamSOCKSProxyConfigurationKey]; user = [conf objectForKey: NSStreamSOCKSProxyUserKey]; pass = [conf objectForKey: NSStreamSOCKSProxyPasswordKey]; if ([[conf objectForKey: NSStreamSOCKSProxyVersionKey] isEqual: NSStreamSOCKSProxyVersion4] == YES) { } else { again: if (state == GSSOCKSOfferAuth) { int result; int want; unsigned char buf[4]; /* * Authorisation record is at least three bytes - * socks version (5) * authorisation method bytes to follow (1) * say we do no authorisation (0) * say we do user/pass authorisation (2) */ buf[0] = 5; if (user && pass) { buf[1] = 2; buf[2] = 2; buf[3] = 0; want = 4; } else { buf[1] = 1; buf[2] = 0; want = 3; } result = [ostream _write: buf + woffset maxLength: 4 - woffset]; if (result > 0) { woffset += result; if (woffset == want) { woffset = 0; state = GSSOCKSRecvAuth; goto again; } } } else if (state == GSSOCKSRecvAuth) { int result; result = [istream _read: rbuffer + roffset maxLength: 2 - roffset]; if (result == 0) { error = @"SOCKS end-of-file during negotiation"; } else if (result > 0) { roffset += result; if (roffset == 2) { roffset = 0; if (rbuffer[0] != 5) { error = @"SOCKS authorisation response had wrong version"; } else if (rbuffer[1] == 0) { state = GSSOCKSSendConn; goto again; } else if (rbuffer[1] == 2) { state = GSSOCKSSendAuth; goto again; } else { error = @"SOCKS authorisation response had wrong method"; } } } } else if (state == GSSOCKSSendAuth) { NSData *u = [user dataUsingEncoding: NSUTF8StringEncoding]; unsigned ul = [u length]; NSData *p = [pass dataUsingEncoding: NSUTF8StringEncoding]; unsigned pl = [p length]; if (ul < 1 || ul > 255) { error = @"NSStreamSOCKSProxyUserKey value too long"; } else if (pl < 1 || pl > 255) { error = @"NSStreamSOCKSProxyPasswordKey value too long"; } else { int want = ul + pl + 3; unsigned char buf[want]; int result; buf[0] = 5; buf[1] = ul; memcpy(buf + 2, [u bytes], ul); buf[ul + 2] = pl; memcpy(buf + ul + 3, [p bytes], pl); result = [ostream _write: buf + woffset maxLength: want - woffset]; if (result == 0) { error = @"SOCKS end-of-file during negotiation"; } else if (result > 0) { woffset += result; if (woffset == want) { state = GSSOCKSAckAuth; goto again; } } } } else if (state == GSSOCKSAckAuth) { int result; result = [istream _read: rbuffer + roffset maxLength: 2 - roffset]; if (result == 0) { error = @"SOCKS end-of-file during negotiation"; } else if (result > 0) { roffset += result; if (roffset == 2) { roffset = 0; if (rbuffer[0] != 5) { error = @"SOCKS authorisation response had wrong version"; } else if (rbuffer[1] == 0) { state = GSSOCKSSendConn; goto again; } else if (rbuffer[1] == 2) { error = @"SOCKS authorisation failed"; } } } } else if (state == GSSOCKSSendConn) { unsigned char buf[10]; int want = 10; int result; const char *ptr; /* * Connect command is ten bytes - * socks version * connect command * reserved byte * address type * address 4 bytes (big endian) * port 2 bytes (big endian) */ buf[0] = 5; // Socks version number buf[1] = 1; // Connect command buf[2] = 0; // Reserved buf[3] = 1; // Address type (IPV4) ptr = [address UTF8String]; buf[4] = atoi(ptr); while (isdigit(*ptr)) ptr++; ptr++; buf[5] = atoi(ptr); while (isdigit(*ptr)) ptr++; ptr++; buf[6] = atoi(ptr); while (isdigit(*ptr)) ptr++; ptr++; buf[7] = atoi(ptr); result = [port intValue]; buf[8] = ((result & 0xff00) >> 8); buf[9] = (result & 0xff); result = [ostream _write: buf + woffset maxLength: want - woffset]; if (result == 0) { error = @"SOCKS end-of-file during negotiation"; } else if (result > 0) { woffset += result; if (woffset == want) { rwant = 5; state = GSSOCKSAckConn; goto again; } } } else if (state == GSSOCKSAckConn) { int result; result = [istream _read: rbuffer + roffset maxLength: rwant - roffset]; if (result == 0) { error = @"SOCKS end-of-file during negotiation"; } else if (result > 0) { roffset += result; if (roffset == rwant) { if (rbuffer[0] != 5) { error = @"connect response from SOCKS had wrong version"; } else if (rbuffer[1] != 0) { switch (rbuffer[1]) { case 1: error = @"SOCKS server general failure"; break; case 2: error = @"SOCKS server says permission denied"; break; case 3: error = @"SOCKS server says network unreachable"; break; case 4: error = @"SOCKS server says host unreachable"; break; case 5: error = @"SOCKS server says connection refused"; break; case 6: error = @"SOCKS server says connection timed out"; break; case 7: error = @"SOCKS server says command not supported"; break; case 8: error = @"SOCKS server says address not supported"; break; default: error = @"connect response from SOCKS was failure"; break; } } else if (rbuffer[3] == 1) { rwant = 10; // Fixed size (IPV4) address } else if (rbuffer[3] == 3) { rwant = 7 + rbuffer[4]; // Domain name leading length } else if (rbuffer[3] == 4) { rwant = 22; // Fixed size (IPV6) address } else { error = @"SOCKS server returned unknown address type"; } if (error == nil) { if (roffset < rwant) { goto again; // Need address/port bytes } else { NSString *a; if (rbuffer[3] == 1) { a = [NSString stringWithFormat: @"%d.%d.%d.%d", rbuffer[4], rbuffer[5], rbuffer[6], rbuffer[7]]; } else if (rbuffer[3] == 3) { rbuffer[rwant] = '\0'; a = [NSString stringWithUTF8String: (const char*)rbuffer]; } else { unsigned char buf[40]; int i = 4; int j = 0; while (i < rwant) { int val; val = rbuffer[i++]; val = val * 256 + rbuffer[i++]; if (i > 4) { buf[j++] = ':'; } snprintf((char*)&buf[j], 5, "%04x", val); j += 4; } a = [NSString stringWithUTF8String: (const char*)buf]; } [istream setProperty: a forKey: GSStreamRemoteAddressKey]; [ostream setProperty: a forKey: GSStreamRemoteAddressKey]; a = [NSString stringWithFormat: @"%d", rbuffer[rwant-1] * 256 * rbuffer[rwant-2]]; [istream setProperty: a forKey: GSStreamRemotePortKey]; [ostream setProperty: a forKey: GSStreamRemotePortKey]; /* Return immediately after calling -bye as it * will cause this instance to be deallocated. */ [self bye]; return; } } } } } } if ([error length] > 0) { NSError *theError; theError = [NSError errorWithDomain: NSCocoaErrorDomain code: 0 userInfo: [NSDictionary dictionaryWithObject: error forKey: NSLocalizedDescriptionKey]]; if ([istream streamStatus] != NSStreamStatusError) { [istream _recordError: theError]; } if ([ostream streamStatus] != NSStreamStatusError) { [ostream _recordError: theError]; } [self bye]; } } - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len { return [ostream _write: buffer maxLength: len]; } @end static inline BOOL socketError(int result) { #if defined(_WIN32) return (result == SOCKET_ERROR) ? YES : NO; #else return (result < 0) ? YES : NO; #endif } static inline BOOL socketWouldBlock(long eno) { return GSWOULDBLOCK(eno) ? YES : NO; } static void setNonBlocking(SOCKET fd) { #if defined(_WIN32) unsigned long dummy = 1; if (ioctlsocket(fd, FIONBIO, &dummy) == SOCKET_ERROR) { NSLog(@"unable to set non-blocking mode - %@", [NSError _last]); } #else int flags = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { NSLog(@"unable to set non-blocking mode - %@", [NSError _last]); } #endif } @implementation GSSocketStream - (void) dealloc { if (_sock != INVALID_SOCKET) { [self close]; } [_sibling _setSibling: nil]; _sibling = nil; [_handler remove: self]; DESTROY(_handler); [super dealloc]; } - (NSString*) description { return [NSString stringWithFormat: @"<%s: %p sock %lld loopID %p mask %@>", class_getName(object_getClass(self)), self, (long long)_sock, _loopID, [self _stringFromEvents]]; } - (id) init { if ((self = [super init]) != nil) { // so that unopened access will fail _sibling = nil; _closing = NO; _passive = NO; #if defined(_WIN32) _loopID = WSA_INVALID_EVENT; #else _loopID = (void*)(intptr_t)-1; #endif _sock = INVALID_SOCKET; _handler = nil; _address.s.sa_family = AF_UNSPEC; } return self; } - (struct sockaddr*) _address { return &_address.s; } - (id) propertyForKey: (NSString *)key { id result = [super propertyForKey: key]; if (result == nil && _address.s.sa_family != AF_UNSPEC) { SOCKET s = [self _sock]; sockaddr_any sin; socklen_t size = sizeof(sin); memset(&sin, '\0', size); if ([key isEqualToString: GSStreamLocalAddressKey]) { if (getsockname(s, (struct sockaddr*)&sin, (OPTLEN*)&size) != -1) { result = GSPrivateSockaddrHost((struct sockaddr*)&sin); } } else if ([key isEqualToString: GSStreamLocalPortKey]) { if (getsockname(s, (struct sockaddr*)&sin, (OPTLEN*)&size) != -1) { result = [NSString stringWithFormat: @"%d", (int)GSPrivateSockaddrPort((struct sockaddr*)&sin)]; } } else if ([key isEqualToString: GSStreamRemoteAddressKey]) { if (getpeername(s, (struct sockaddr*)&sin, (OPTLEN*)&size) != -1) { result = GSPrivateSockaddrHost((struct sockaddr*)&sin); } } else if ([key isEqualToString: GSStreamRemotePortKey]) { if (getpeername(s, (struct sockaddr*)&sin, (OPTLEN*)&size) != -1) { result = [NSString stringWithFormat: @"%d", (int)GSPrivateSockaddrPort((struct sockaddr*)&sin)]; } } } return result; } - (NSInteger) _read: (uint8_t *)buffer maxLength: (NSUInteger)len { [self subclassResponsibility: _cmd]; return -1; } - (void) _sendEvent: (NSStreamEvent)event { /* If the receiver has a TLS handshake in progress, * we must send events to the TLS handler rather than * the stream delegate. */ if (_handler != nil && [_handler handshake] == YES) { /* Must retain self here to avoid premature deallocation of input * and/or output stream in case of an error during TLS handshake. */ RETAIN(self); [self _sendEvent: event delegate: _handler]; RELEASE(self); } else { [super _sendEvent: event]; } } - (BOOL) _setSocketAddress: (NSString*)address port: (NSInteger)port family: (NSInteger)family { uint16_t p = (uint16_t)port; switch (family) { case AF_INET: { int ptonReturn; const char *addr_c; struct sockaddr_in peer; addr_c = [address cStringUsingEncoding: NSUTF8StringEncoding]; memset(&peer, '\0', sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = GSSwapHostI16ToBig(p); ptonReturn = inet_pton(AF_INET, addr_c, &peer.sin_addr); if (ptonReturn <= 0) // error { return NO; } else { [self _setAddress: (struct sockaddr*)&peer]; return YES; } } #if defined(AF_INET6) case AF_INET6: { int ptonReturn; const char *addr_c; struct sockaddr_in6 peer; addr_c = [address cStringUsingEncoding: NSUTF8StringEncoding]; memset(&peer, '\0', sizeof(peer)); peer.sin6_family = AF_INET6; peer.sin6_port = GSSwapHostI16ToBig(p); ptonReturn = inet_pton(AF_INET6, addr_c, &peer.sin6_addr); if (ptonReturn <= 0) // error { return NO; } else { [self _setAddress: (struct sockaddr*)&peer]; return YES; } } #endif #ifndef _WIN32 case AF_LOCAL: { struct sockaddr_un peer; const char *c_addr; c_addr = [address fileSystemRepresentation]; memset(&peer, '\0', sizeof(peer)); peer.sun_family = AF_LOCAL; if (strlen(c_addr) > sizeof(peer.sun_path)-1) // too long { return NO; } else { strncpy(peer.sun_path, c_addr, sizeof(peer.sun_path)-1); [self _setAddress: (struct sockaddr*)&peer]; return YES; } } #endif default: return NO; } } - (void) _setAddress: (struct sockaddr*)address { memcpy(&_address.s, address, GSPrivateSockaddrLength(address)); } - (void) _setLoopID: (void *)ref { #if !defined(_WIN32) _sock = (SOCKET)(intptr_t)ref; // On gnu/linux _sock is _loopID #endif _loopID = ref; } - (void) _setClosing: (BOOL)closing { _closing = closing; } - (void) _setPassive: (BOOL)passive { _passive = passive; } - (void) _setSibling: (GSSocketStream*)sibling { _sibling = sibling; } - (void) _setSock: (SOCKET)sock { setNonBlocking(sock); _sock = sock; /* As well as recording the socket, we set up the stream for monitoring it. * On unix style systems we set the socket descriptor as the _loopID to be * monitored, and on mswindows systems we create an event object to be * monitored (the socket events are assoociated with this object later). */ #if defined(_WIN32) _loopID = CreateEvent(NULL, NO, NO, NULL); #else _loopID = (void*)(intptr_t)sock; // On gnu/linux _sock is _loopID #endif } - (void) _setHandler: (id)h { ASSIGN(_handler, h); } - (SOCKET) _sock { return _sock; } - (NSInteger) _write: (const uint8_t *)buffer maxLength: (NSUInteger)len { [self subclassResponsibility: _cmd]; return -1; } @end @implementation GSSocketInputStream + (void) initialize { if (self == [GSSocketInputStream class]) { GSObjCAddClassBehavior(self, [GSSocketStream class]); } } - (void) open { NSDebugMLLog(@"NSStream", @"%@", self); // could be opened because of sibling if ([self _isOpened]) return; if (_sibling && [_sibling streamStatus] == NSStreamStatusError) { [self _setStatus: NSStreamStatusError]; return; } if (_passive || (_sibling && [_sibling _isOpened])) goto open_ok; // check sibling status, avoid double connect if (_sibling && [_sibling streamStatus] == NSStreamStatusOpening) { [self _setStatus: NSStreamStatusOpening]; return; } else { int result; if ([self _sock] == INVALID_SOCKET) { SOCKET s; if (_handler == nil) { [GSSOCKS tryInput: self output: _sibling]; } s = socket(_address.s.sa_family, SOCK_STREAM, 0); if (BADSOCKET(s)) { [self _recordError]; return; } else { [GSTcpTune tune: (void*)(intptr_t)s with: self]; [self _setSock: s]; [_sibling _setSock: s]; } } if (nil == _handler) { [GSTLSHandler tryInput: self output: _sibling]; } result = connect([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); if (socketError(result)) { long eno = GSNETERROR; if (socketWouldBlock(eno)) { /* Need to set the status first, so that the run loop can tell * it needs to add the stream as waiting on writable, as an * indication of opened */ [self _setStatus: NSStreamStatusOpening]; } else { NSError *e = [NSError _systemError: eno]; /* Had an immediate connect error. */ [self _recordError: e]; [_sibling _recordError: e]; } #if defined(_WIN32) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif if (NSCountMapTable(_loops) > 0) { [self _schedule]; return; } else if (NSStreamStatusOpening == _currentStatus) { NSRunLoop *r; NSDate *d; /* The stream was not scheduled in any run loop, so we * implement a blocking connect by running in the default * run loop mode. */ r = [NSRunLoop currentRunLoop]; d = [NSDate distantFuture]; [r addStream: self mode: NSDefaultRunLoopMode]; while ([r runMode: NSDefaultRunLoopMode beforeDate: d] == YES) { if (_currentStatus != NSStreamStatusOpening) { break; } } [r removeStream: self mode: NSDefaultRunLoopMode]; return; } } } open_ok: #if defined(_WIN32) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif [super open]; } - (void) close { NSDebugMLLog(@"NSStream", @"%@", self); /* If the socket descriptor is still present, we need to close it to * avoid a leak no matter what the nominal state of the stream is. * The descriptor is created before the stream is formally opened. */ if (INVALID_SOCKET == _sock) { if (_currentStatus == NSStreamStatusNotOpen) { NSDebugMLLog(@"NSStream", @"Attempt to close unopened stream %@", self); return; } if (_currentStatus == NSStreamStatusClosed) { NSDebugMLLog(@"NSStream", @"Attempt to close already closed stream %@", self); return; } } [_handler bye]; #if defined(_WIN32) [super close]; if (_sibling && [_sibling streamStatus] != NSStreamStatusClosed) { /* * Windows only permits a single event to be associated with a socket * at any time, but the runloop system only allows an event handle to * be added to the loop once, and we have two streams for each socket. * So we use two events, one for each stream, and when one stream is * closed, we must call WSAEventSelect to ensure that the event handle * of the sibling is used to signal events from now on. */ shutdown(_sock, SHUT_RD); [_sibling _unschedule]; if (WSAEventSelect(_sock, [_sibling _loopID], FD_ALL_EVENTS) == SOCKET_ERROR) { NSDebugMLLog(@"NSStream", @"%@ Error %d transferring to %@", self, WSAGetLastError(), _sibling); } [_sibling _schedule]; } else { closesocket(_sock); } WSACloseEvent(_loopID); _loopID = WSA_INVALID_EVENT; #else [super close]; // read shutdown is ignored, because the other side may shutdown first. if (!_sibling || [_sibling streamStatus] == NSStreamStatusClosed) close((intptr_t)_loopID); else shutdown((intptr_t)_loopID, SHUT_RD); _loopID = (void*)(intptr_t)-1; #endif _sock = INVALID_SOCKET; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { NSInteger result; if (buffer == 0) { [NSException raise: NSInvalidArgumentException format: @"null pointer for buffer"]; } if (len == 0) { [NSException raise: NSInvalidArgumentException format: @"zero byte read requested"]; } if (_handler == nil) result = [self _read: buffer maxLength: len]; else result = [_handler read: buffer maxLength: len]; NSDebugMLLog(@"NSStream", @"%@ tried %lld result %lld", self, (long long)len, (long long) result); return result; } - (NSInteger) _read: (uint8_t *)buffer maxLength: (NSUInteger)len { int readLen; _events &= ~NSStreamEventHasBytesAvailable; if ([self streamStatus] == NSStreamStatusClosed) { return 0; } if ([self streamStatus] == NSStreamStatusAtEnd) { readLen = 0; } else { #if defined(_WIN32) readLen = recv([self _sock], (char*) buffer, (socklen_t) len, 0); #else readLen = read([self _sock], buffer, len); #endif } if (socketError(readLen)) { long eno = GSNETERROR; if (_closing == YES) { /* If a read fails on a closing socket, * we have reached the end of all data sent by * the remote end before it shut down. */ [self _setClosing: NO]; [self _setStatus: NSStreamStatusAtEnd]; [self _sendEvent: NSStreamEventEndEncountered]; readLen = 0; } else { #if defined(_WIN32) if (WSAECONNABORTED == eno) { readLen = 0; // Read end of file readLen = -1; } else #endif if (socketWouldBlock(eno)) { /* We need an event from the operating system * to tell us we can start reading again. */ [self _setStatus: NSStreamStatusReading]; } else { NSError *e = [NSError _systemError: eno]; [self _recordError: e]; readLen = -1; } } } else if (readLen == 0) { [self _setStatus: NSStreamStatusAtEnd]; [self _sendEvent: NSStreamEventEndEncountered]; } else { [self _setStatus: NSStreamStatusOpen]; } return readLen; } - (BOOL) getBuffer: (uint8_t **)buffer length: (NSUInteger *)len { return NO; } - (void) _dispatch { #if defined(_WIN32) AUTORELEASE(RETAIN(self)); /* * Windows only permits a single event to be associated with a socket * at any time, but the runloop system only allows an event handle to * be added to the loop once, and we have two streams for each socket. * So we use two events, one for each stream, and the _dispatch method * must handle things for both streams. */ if ([self streamStatus] == NSStreamStatusClosed) { /* * It is possible the stream is closed yet recieving event because * of not closed sibling */ NSAssert([_sibling streamStatus] != NSStreamStatusClosed, @"Received event for closed stream"); [_sibling _dispatch]; } else if ([self streamStatus] == NSStreamStatusAtEnd) { [self _sendEvent: NSStreamEventEndEncountered]; } else if ([self streamStatus] == NSStreamStatusError) { [self _sendEvent: NSStreamEventErrorOccurred]; } else { WSANETWORKEVENTS events; int error = 0; int getReturn = -1; if (WSAEnumNetworkEvents(_sock, _loopID, &events) == SOCKET_ERROR) { error = WSAGetLastError(); NSDebugMLLog(@"NSStream", @"%@ Error %d", self, error); } #ifndef NDEBUG else { NSDebugMLLog(@"NSStream", @"%@ EVENTS 0x%lx", self, events.lNetworkEvents); } #endif if ([self streamStatus] == NSStreamStatusOpening) { [self _unschedule]; if (error == 0) { socklen_t len = sizeof(error); getReturn = getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char*)&error, (OPTLEN*)&len); } if (getReturn >= 0 && error == 0 && (events.lNetworkEvents & FD_CONNECT)) { // finish up the opening _passive = YES; [self open]; // notify sibling if (_sibling) { [_sibling open]; [_sibling _sendEvent: NSStreamEventOpenCompleted]; } [self _sendEvent: NSStreamEventOpenCompleted]; } } if (error != 0) { NSError *e = [NSError _systemError: error]; errno = error; [self _recordError: e]; [self _sendEvent: NSStreamEventErrorOccurred]; if ([_sibling streamStatus] == NSStreamStatusOpening) { [_sibling _recordError: e]; [_sibling _sendEvent: NSStreamEventErrorOccurred]; } } else { if (events.lNetworkEvents & FD_WRITE) { if (_sibling && NSStreamStatusWriting == [_sibling streamStatus]) { [_sibling _setStatus: NSStreamStatusOpen]; } } /* On winsock a socket is always writable unless it has had * failure/closure or a write blocked and we have not been * signalled again. */ while ([_sibling _unhandledData] == NO && [_sibling hasSpaceAvailable]) { [_sibling _sendEvent: NSStreamEventHasSpaceAvailable]; } if (events.lNetworkEvents & FD_READ) { [self _setStatus: NSStreamStatusOpen]; while ([self hasBytesAvailable] && [self _unhandledData] == NO) { [self _sendEvent: NSStreamEventHasBytesAvailable]; } } if (events.lNetworkEvents & FD_CLOSE) { [self _setClosing: YES]; [_sibling _setClosing: YES]; while ([self hasBytesAvailable] && [self _unhandledData] == NO) { [self _sendEvent: NSStreamEventHasBytesAvailable]; } } if (events.lNetworkEvents == 0) { [self _sendEvent: NSStreamEventHasBytesAvailable]; } } } #else NSStreamEvent myEvent; if ([self streamStatus] == NSStreamStatusOpening) { int error; int result; socklen_t len = sizeof(error); IF_NO_ARC([[self retain] autorelease];) [self _unschedule]; result = getsockopt([self _sock], SOL_SOCKET, SO_ERROR, &error, (OPTLEN*)&len); if (result >= 0 && !error) { // finish up the opening myEvent = NSStreamEventOpenCompleted; _passive = YES; [self open]; // notify sibling [_sibling open]; [_sibling _sendEvent: myEvent]; } else // must be an error { NSError *e = [NSError _systemError: error]; [self _recordError: e]; myEvent = NSStreamEventErrorOccurred; [_sibling _recordError: e]; [_sibling _sendEvent: myEvent]; } } else if ([self streamStatus] == NSStreamStatusAtEnd) { myEvent = NSStreamEventEndEncountered; } else if ([self streamStatus] == NSStreamStatusError) { myEvent = NSStreamEventErrorOccurred; } else { [self _setStatus: NSStreamStatusOpen]; myEvent = NSStreamEventHasBytesAvailable; } [self _sendEvent: myEvent]; #endif } - (BOOL) runLoopShouldBlock: (BOOL*)trigger { /* If there is a handler in place which has data buffered for reading * the run loop should trigger immediately so we read it. */ if ([_handler readable]) { *trigger = YES; return NO; } #if defined(_WIN32) *trigger = YES; return YES; #else return [super runLoopShouldBlock: trigger]; #endif } @end @implementation GSSocketOutputStream + (void) initialize { if (self == [GSSocketOutputStream class]) { GSObjCAddClassBehavior(self, [GSSocketStream class]); } } #ifdef _WIN32 /* On windows a stream is considered writable once it is opened, even though the * system doesn't signal to say so. We therefore override the event sending to * add the extra event. */ - (void) _sendEvent: (NSStreamEvent)event delegate: (id)delegate { [super _sendEvent: event delegate: delegate]; if (NSStreamEventOpenCompleted == event) { [super _sendEvent: NSStreamEventHasSpaceAvailable delegate: delegate]; } } #endif - (NSInteger) _write: (const uint8_t *)buffer maxLength: (NSUInteger)len { int writeLen; _events &= ~NSStreamEventHasSpaceAvailable; if ([self streamStatus] == NSStreamStatusClosed) { return 0; } if ([self streamStatus] == NSStreamStatusAtEnd) { [self _sendEvent: NSStreamEventEndEncountered]; return 0; } #if defined(_WIN32) writeLen = send([self _sock], (char*) buffer, (socklen_t) len, 0); #else writeLen = write([self _sock], buffer, (socklen_t) len); #endif if (socketError(writeLen)) { long eno = GSNETERROR; if (_closing == YES) { /* If a write fails on a closing socket, * we know the other end is no longer reading. */ [self _setClosing: NO]; [self _setStatus: NSStreamStatusAtEnd]; [self _sendEvent: NSStreamEventEndEncountered]; writeLen = 0; } else { #if defined(_WIN32) if (WSAECONNABORTED == eno) { [_sibling _setStatus: NSStreamStatusAtEnd]; writeLen = 0; } else #endif if (socketWouldBlock(eno)) { /* We need an event from the operating system * to tell us we can start writing again. */ [self _setStatus: NSStreamStatusWriting]; writeLen = -1; } else { NSError *e = [NSError _systemError: eno]; [self _recordError: e]; writeLen = -1; } } } else { [self _setStatus: NSStreamStatusOpen]; } return writeLen; } - (void) open { NSDebugMLLog(@"NSStream", @"%@", self); // could be opened because of sibling if ([self _isOpened]) return; if (_sibling && [_sibling streamStatus] == NSStreamStatusError) { [self _setStatus: NSStreamStatusError]; return; } if (_passive || (_sibling && [_sibling _isOpened])) goto open_ok; // check sibling status, avoid double connect if (_sibling && [_sibling streamStatus] == NSStreamStatusOpening) { [self _setStatus: NSStreamStatusOpening]; return; } else { int result; if ([self _sock] == INVALID_SOCKET) { SOCKET s; if (_handler == nil) { [GSSOCKS tryInput: _sibling output: self]; } s = socket(_address.s.sa_family, SOCK_STREAM, 0); if (BADSOCKET(s)) { [self _recordError]; return; } else { [GSTcpTune tune: (void*)(intptr_t)s with: self]; [self _setSock: s]; [_sibling _setSock: s]; } } if (nil == _handler) { [GSTLSHandler tryInput: _sibling output: self]; } result = connect([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); if (socketError(result)) { long eno = GSNETERROR; if (socketWouldBlock(eno)) { /* * Need to set the status first, so that the run loop can tell * it needs to add the stream as waiting on writable, as an * indication of opened */ [self _setStatus: NSStreamStatusOpening]; } else { NSError *e = [NSError _systemError: eno]; /* Had an immediate connect error. */ [self _recordError: e]; [_sibling _recordError: e]; } #if defined(_WIN32) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif if (NSCountMapTable(_loops) > 0) { [self _schedule]; return; } else if (NSStreamStatusOpening == _currentStatus) { NSRunLoop *r; NSDate *d; /* The stream was not scheduled in any run loop, so we * implement a blocking connect by running in the default * run loop mode. */ r = [NSRunLoop currentRunLoop]; d = [NSDate distantFuture]; [r addStream: self mode: NSDefaultRunLoopMode]; while ([r runMode: NSDefaultRunLoopMode beforeDate: d] == YES) { if (_currentStatus != NSStreamStatusOpening) { break; } } [r removeStream: self mode: NSDefaultRunLoopMode]; return; } } } open_ok: #if defined(_WIN32) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif [super open]; } - (void) close { NSDebugMLLog(@"NSStream", @"%@", self); /* If the socket descriptor is still present, we need to close it to * avoid a leak no matter what the nominal state of the stream is. * The descriptor is created before the stream is formally opened. */ if (INVALID_SOCKET == _sock) { if (_currentStatus == NSStreamStatusNotOpen) { NSDebugMLLog(@"NSStream", @"Attempt to close unopened stream %@", self); return; } if (_currentStatus == NSStreamStatusClosed) { NSDebugMLLog(@"NSStream", @"Attempt to close already closed stream %@", self); return; } } [_handler bye]; #if defined(_WIN32) if (_sibling && [_sibling streamStatus] != NSStreamStatusClosed) { /* * Windows only permits a single event to be associated with a socket * at any time, but the runloop system only allows an event handle to * be added to the loop once, and we have two streams for each socket. * So we use two events, one for each stream, and when one stream is * closed, we must call WSAEventSelect to ensure that the event handle * of the sibling is used to signal events from now on. */ shutdown(_sock, SHUT_WR); _sock = INVALID_SOCKET; [_sibling _unschedule]; if (WSAEventSelect([_sibling _sock], [_sibling _loopID], FD_ALL_EVENTS) == SOCKET_ERROR) { NSDebugMLLog(@"NSStream", @"%@ Error %d transferring to %@", self, WSAGetLastError(), _sibling); } [_sibling _schedule]; } else { closesocket(_sock); } WSACloseEvent(_loopID); [super close]; _loopID = WSA_INVALID_EVENT; #else // read shutdown is ignored, because the other side may shutdown first. if (!_sibling || [_sibling streamStatus] == NSStreamStatusClosed) close((intptr_t)_loopID); else shutdown((intptr_t)_loopID, SHUT_WR); [super close]; _loopID = (void*)(intptr_t)-1; #endif _sock = INVALID_SOCKET; } - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len { NSInteger result; if (len == 0) { /* * The method allows the 'len' equal to 0. In this case the 'buffer' * is ignored. This can be useful if there is a necessity to postpone * actual writing (for no data are ready for example) without leaving * the stream in the state of unhandled NSStreamEventHasSpaceAvailable * (to keep receiving of that event from a runloop). * The delegate's -[stream:handleEvent:] would keep calling of * -[write: NULL maxLength: 0] until the delegate's state allows it * to write actual bytes. * The downside of that is that it produces a busy wait ... with the * run loop immediately notifying the stream that it has space to * write, so care should be taken to ensure that the delegate has a * near constant supply of data to write, or has some mechanism to * detect that no more data is arriving, and shut down. */ _events &= ~NSStreamEventHasSpaceAvailable; result = 0; } else { if (buffer == 0) { [NSException raise: NSInvalidArgumentException format: @"null pointer for buffer"]; } if (_handler == nil) result = [self _write: buffer maxLength: len]; else result = [_handler write: buffer maxLength: len]; } NSDebugMLLog(@"NSStream", @"%@ tried %lld result %lld", self, (long long)len, (long long) result); return result; } - (void) _dispatch { #if defined(_WIN32) AUTORELEASE(RETAIN(self)); /* * Windows only permits a single event to be associated with a socket * at any time, but the runloop system only allows an event handle to * be added to the loop once, and we have two streams for each socket. * So we use two events, one for each stream, and the _dispatch method * must handle things for both streams. */ if ([self streamStatus] == NSStreamStatusClosed) { /* * It is possible the stream is closed yet recieving event because * of not closed sibling */ NSAssert([_sibling streamStatus] != NSStreamStatusClosed, @"Received event for closed stream"); [_sibling _dispatch]; } else if ([self streamStatus] == NSStreamStatusAtEnd) { [self _sendEvent: NSStreamEventEndEncountered]; } else if ([self streamStatus] == NSStreamStatusError) { [self _sendEvent: NSStreamEventErrorOccurred]; } else { WSANETWORKEVENTS events; int error = 0; int getReturn = -1; if (WSAEnumNetworkEvents(_sock, _loopID, &events) == SOCKET_ERROR) { error = WSAGetLastError(); NSDebugMLLog(@"NSStream", @"%@ Error %d", self, error); } #ifndef NDEBUG else { NSDebugMLLog(@"NSStream", @"%@ EVENTS 0x%lx", self, events.lNetworkEvents); } #endif if ([self streamStatus] == NSStreamStatusOpening) { [self _unschedule]; if (error == 0) { socklen_t len = sizeof(error); getReturn = getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char*)&error, (OPTLEN*)&len); } if (getReturn >= 0 && error == 0 && (events.lNetworkEvents & FD_CONNECT)) { // finish up the opening events.lNetworkEvents ^= FD_CONNECT; _passive = YES; [self open]; // notify sibling if (_sibling) { [_sibling open]; [_sibling _sendEvent: NSStreamEventOpenCompleted]; } [self _sendEvent: NSStreamEventOpenCompleted]; } } if (error != 0) { NSError *e = [NSError _systemError: error]; [self _recordError: e]; [self _sendEvent: NSStreamEventErrorOccurred]; if ([_sibling streamStatus] == NSStreamStatusOpening) { [_sibling _recordError: e]; [_sibling _sendEvent: NSStreamEventErrorOccurred]; } } else { if (events.lNetworkEvents & FD_WRITE) { /* Clear NSStreamStatusWriting if it was set */ [self _setStatus: NSStreamStatusOpen]; } /* On winsock a socket is always writable unless it has had * failure/closure or a write blocked and we have not been * signalled again. */ while ([self _unhandledData] == NO && [self hasSpaceAvailable]) { [self _sendEvent: NSStreamEventHasSpaceAvailable]; } if (events.lNetworkEvents & FD_READ) { [_sibling _setStatus: NSStreamStatusOpen]; while ([_sibling hasBytesAvailable] && [_sibling _unhandledData] == NO) { [_sibling _sendEvent: NSStreamEventHasBytesAvailable]; } } if (events.lNetworkEvents & FD_CLOSE) { [self _setClosing: YES]; [_sibling _setClosing: YES]; while ([_sibling hasBytesAvailable] && [_sibling _unhandledData] == NO) { [_sibling _sendEvent: NSStreamEventHasBytesAvailable]; } } if (events.lNetworkEvents == 0) { [self _sendEvent: NSStreamEventHasSpaceAvailable]; } } } #else NSStreamEvent myEvent; if ([self streamStatus] == NSStreamStatusOpening) { int error; socklen_t len = sizeof(error); int result; IF_NO_ARC([[self retain] autorelease];) [self _schedule]; result = getsockopt((intptr_t)_loopID, SOL_SOCKET, SO_ERROR, &error, (OPTLEN*)&len); if (result >= 0 && !error) { // finish up the opening myEvent = NSStreamEventOpenCompleted; _passive = YES; [self open]; // notify sibling [_sibling open]; [_sibling _sendEvent: myEvent]; } else // must be an error { NSError *e = [NSError _systemError: error]; [self _recordError: e]; myEvent = NSStreamEventErrorOccurred; [_sibling _recordError: e]; [_sibling _sendEvent: myEvent]; } } else if ([self streamStatus] == NSStreamStatusAtEnd) { myEvent = NSStreamEventEndEncountered; } else if ([self streamStatus] == NSStreamStatusError) { myEvent = NSStreamEventErrorOccurred; } else { [self _setStatus: NSStreamStatusOpen]; myEvent = NSStreamEventHasSpaceAvailable; } [self _sendEvent: myEvent]; #endif } #if defined(_WIN32) - (BOOL) runLoopShouldBlock: (BOOL*)trigger { *trigger = YES; if ([self _unhandledData] == YES && [self streamStatus] == NSStreamStatusOpen) { /* In winsock, a writable status is only signalled if an earlier * write failed (because it would block), so we must simulate the * writable event by having the run loop trigger without blocking. */ return NO; } return YES; } #endif @end @implementation GSSocketServerStream + (void) initialize { if (self == [GSSocketServerStream class]) { GSObjCAddClassBehavior(self, [GSSocketStream class]); } } - (Class) _inputStreamClass { [self subclassResponsibility: _cmd]; return Nil; } - (Class) _outputStreamClass { [self subclassResponsibility: _cmd]; return Nil; } - (void) open { int bindReturn; int listenReturn; SOCKET s; NSDebugMLLog(@"NSStream", @"%@", self); if (_currentStatus != NSStreamStatusNotOpen) { NSDebugMLLog(@"NSStream", @"Attempt to re-open stream %@", self); return; } s = socket(_address.s.sa_family, SOCK_STREAM, 0); if (BADSOCKET(s)) { [self _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; return; } else { [GSTcpTune tune: (void*)(intptr_t)s with: self]; [(GSSocketStream*)self _setSock: s]; } #ifndef BROKEN_SO_REUSEADDR if (_address.s.sa_family == AF_INET #ifdef AF_INET6 || _address.s.sa_family == AF_INET6 #endif ) { /* * Under decent systems, SO_REUSEADDR means that the port can be reused * immediately that this process exits. Under some it means * that multiple processes can serve the same port simultaneously. * We don't want that broken behavior! */ int status = 1; if (setsockopt([self _sock], SOL_SOCKET, SO_REUSEADDR, (char *)&status, (OPTLEN)sizeof(status)) < 0) { NSDebugMLLog(@"GSTcpTune", @"setsockopt reuseaddr failed"); } } #endif bindReturn = bind([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); if (socketError(bindReturn)) { [self _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; return; } listenReturn = listen([self _sock], GSBACKLOG); if (socketError(listenReturn)) { [self _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; return; } #if defined(_WIN32) if (_loopID != WSA_INVALID_EVENT) { WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); } #endif [super open]; } - (void) close { NSDebugMLLog(@"NSStream", @"%@", self); #if defined(_WIN32) if (_loopID != WSA_INVALID_EVENT) { WSACloseEvent(_loopID); } if (_sock != INVALID_SOCKET) { closesocket(_sock); [super close]; _loopID = WSA_INVALID_EVENT; } #else if (_loopID != (void*)(intptr_t)-1) { close((intptr_t)_loopID); [super close]; _loopID = (void*)(intptr_t)-1; } #endif _sock = INVALID_SOCKET; } - (void) acceptWithInputStream: (NSInputStream **)inputStream outputStream: (NSOutputStream **)outputStream { NSArray *keys; NSUInteger count; NSMutableDictionary *opts; NSString *str; GSSocketStream *ins = AUTORELEASE([[self _inputStreamClass] new]); GSSocketStream *outs = AUTORELEASE([[self _outputStreamClass] new]); /* Align on a 2 byte boundary for a 16bit port number in the sockaddr */ struct { uint8_t bytes[BUFSIZ]; } __attribute__((aligned(2)))buf; struct sockaddr *addr = (struct sockaddr*)&buf; socklen_t len = sizeof(buf); int acceptReturn; acceptReturn = accept([self _sock], addr, (OPTLEN*)&len); _events &= ~NSStreamEventHasBytesAvailable; if (socketError(acceptReturn)) { // test for real error long eno = GSNETERROR; if (!socketWouldBlock(eno)) { NSError *e = [NSError _systemError: eno]; [self _recordError: e]; } ins = nil; outs = nil; } else { // no need to connect again [ins _setPassive: YES]; [outs _setPassive: YES]; // copy the addr to outs [ins _setAddress: addr]; [outs _setAddress: addr]; [ins _setSock: acceptReturn]; [outs _setSock: acceptReturn]; /* Set property to indicate that the input stream was accepted by * a listening socket (server) rather than produced by an outgoing * connection (client). */ [ins setProperty: @"YES" forKey: @"IsServer"]; /* At this point, we can insert the handler to deal with TLS */ str = [self propertyForKey: NSStreamSocketSecurityLevelKey]; if (nil != str) { opts = [NSMutableDictionary new]; [opts setObject: str forKey: NSStreamSocketSecurityLevelKey]; // copy the properties in the 'opts' [GSTLSHandler populateProperties: &opts withSecurityLevel: str fromInputStream: self orOutputStream: nil]; // and set the input/output streams's properties from the 'opts' keys = [opts allKeys]; count = [keys count]; while(count-- > 0) { NSString *key = [keys objectAtIndex: count]; str = [opts objectForKey: key]; [ins setProperty: str forKey: key]; [outs setProperty: str forKey: key]; } /* Set the streams to be 'open' in order to have the TLS * handshake done. On completion the state will be reset. */ [ins _setStatus: NSStreamStatusOpen]; [outs _setStatus: NSStreamStatusOpen]; [GSTLSHandler tryInput: (GSSocketInputStream *)ins output: (GSSocketOutputStream *)outs]; DESTROY(opts); } } if (inputStream) { [ins _setSibling: outs]; *inputStream = (NSInputStream*)ins; } if (outputStream) { [outs _setSibling: ins]; *outputStream = (NSOutputStream*)outs; } /* Now the streams are ready to be opened. */ } - (void) _dispatch { #if defined(_WIN32) WSANETWORKEVENTS events; if (WSAEnumNetworkEvents(_sock, _loopID, &events) == SOCKET_ERROR) { errno = WSAGetLastError(); [self _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; } else if (events.lNetworkEvents & FD_ACCEPT) { events.lNetworkEvents ^= FD_ACCEPT; [self _setStatus: NSStreamStatusReading]; [self _sendEvent: NSStreamEventHasBytesAvailable]; } #else NSStreamEvent myEvent; [self _setStatus: NSStreamStatusOpen]; myEvent = NSStreamEventHasBytesAvailable; [self _sendEvent: myEvent]; #endif } @end @implementation GSInetInputStream - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ((self = [super init]) != nil) { if ([self _setSocketAddress: addr port: port family: AF_INET] == NO) { DESTROY(self); } } return self; } @end @implementation GSInet6InputStream #if defined(AF_INET6) - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ((self = [super init]) != nil) { if ([self _setSocketAddress: addr port: port family: AF_INET6] == NO) { DESTROY(self); } } return self; } #else - (id) initToAddr: (NSString*)addr port: (NSInteger)port { DESTROY(self); return nil; } #endif @end @implementation GSInetOutputStream - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ((self = [super init]) != nil) { if ([self _setSocketAddress: addr port: port family: AF_INET] == NO) { DESTROY(self); } } return self; } @end @implementation GSInet6OutputStream #if defined(AF_INET6) - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ((self = [super init]) != nil) { if ([self _setSocketAddress: addr port: port family: AF_INET6] == NO) { DESTROY(self); } } return self; } #else - (id) initToAddr: (NSString*)addr port: (NSInteger)port { DESTROY(self); return nil; } #endif @end @implementation GSInetServerStream - (Class) _inputStreamClass { return [GSInetInputStream class]; } - (Class) _outputStreamClass { return [GSInetOutputStream class]; } - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ((self = [super init]) != nil) { if ([addr length] == 0) { addr = @"0.0.0.0"; } if ([self _setSocketAddress: addr port: port family: AF_INET] == NO) { DESTROY(self); } } return self; } @end @implementation GSInet6ServerStream #if defined(AF_INET6) - (Class) _inputStreamClass { return [GSInet6InputStream class]; } - (Class) _outputStreamClass { return [GSInet6OutputStream class]; } - (id) initToAddr: (NSString*)addr port: (NSInteger)port { if ([super init] != nil) { if ([addr length] == 0) { addr = @"0:0:0:0:0:0:0:0"; /* Bind on all addresses */ } if ([self _setSocketAddress: addr port: port family: AF_INET6] == NO) { DESTROY(self); } } return self; } #else - (id) initToAddr: (NSString*)addr port: (NSInteger)port { DESTROY(self); return nil; } #endif @end