/** 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 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 02111 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/NSFileHandle.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" #define EXPOSE_GSFileHandle_IVARS 1 #import "GSFileHandle.h" #import "GSStream.h" #import "GSSocketStream.h" #import "GNUstepBase/NSObject+GNUstepBase.h" /* Perhaps thse should be elsewhere ... maybe in a new file along with * the gnutls infrastructure. */ NSString * const GSTLSCertificateFileKey = @"GSTLSCertificateFileKey"; NSString * const GSTLSPrivateKeyFileKey = @"GSTLSPrivateKeyFile"; NSString * const GSTLSPrivateKeyPasswordKey = @"GSTLSPrivateKeyPassword"; #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 extern const char *inet_ntop(int, const void *, char *, size_t); extern int inet_pton(int , const char *, void *); #endif 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 __MINGW__ 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_ind*)sin)->sin6_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. */ - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len; - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event; - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len; @end @implementation GSStreamHandler + (void) initialize { GSMakeWeakPointer(self, "istream"); GSMakeWeakPointer(self, "ostream"); } + (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; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { [self subclassResponsibility: _cmd]; return 0; } - (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) /* Temporarily redefine 'id' in case the headers use the objc reserved word. */ #define id GNUTLSID /* gcrypt uses __attribute__((deprecated)) to mark structure members that are * private. This causes compiler warnings just from using the header. Turn * them off... */ #define _GCRYPT_IN_LIBGCRYPT #include #include #include #undef id /* Set up locking callbacks for gcrypt so that it will be thread-safe. */ static int gcry_mutex_init (void **priv) { NSLock *lock = [NSLock new]; *priv = (void*)lock; return 0; } static int gcry_mutex_destroy (void **lock) { [((NSLock*)*lock) release]; return 0; } static int gcry_mutex_lock (void **lock) { [((NSLock*)*lock) lock]; return 0; } static int gcry_mutex_unlock (void **lock) { [((NSLock*)*lock) unlock]; return 0; } static struct gcry_thread_cbs gcry_threads_other = { GCRY_THREAD_OPTION_DEFAULT, NULL, gcry_mutex_init, gcry_mutex_destroy, gcry_mutex_lock, gcry_mutex_unlock }; static void GSTLSLog(int level, const char *msg) { NSLog(@"%s", msg); } static NSString *cipherList = nil; static gnutls_anon_client_credentials_t anoncred; /* This function will verify the peer's certificate, and check * if the hostname matches, as well as the activation, expiration dates. */ static int _verify_certificate_callback (gnutls_session_t session) { unsigned int status; const gnutls_datum_t *cert_list; unsigned int cert_list_size; int ret; gnutls_x509_crt_t cert; const char *hostname; /* read hostname */ hostname = gnutls_session_get_ptr (session); /* This verification function uses the trusted CAs in the credentials * structure. So you must have installed one or more CA certificates. */ ret = gnutls_certificate_verify_peers2 (session, &status); if (ret < 0) { printf ("Error\n"); return GNUTLS_E_CERTIFICATE_ERROR; } if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) printf ("The certificate hasn't got a known issuer.\n"); if (status & GNUTLS_CERT_REVOKED) printf ("The certificate has been revoked.\n"); /* if (status & GNUTLS_CERT_EXPIRED) printf ("The certificate has expired\n"); if (status & GNUTLS_CERT_NOT_ACTIVATED) printf ("The certificate is not yet activated\n"); */ if (status & GNUTLS_CERT_INVALID) { printf ("The certificate is not trusted.\n"); return GNUTLS_E_CERTIFICATE_ERROR; } /* Up to here the process is the same for X.509 certificates and * OpenPGP keys. From now on X.509 certificates are assumed. This can * be easily extended to work with openpgp keys as well. */ if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) return GNUTLS_E_CERTIFICATE_ERROR; if (gnutls_x509_crt_init (&cert) < 0) { printf ("error in initialization\n"); return GNUTLS_E_CERTIFICATE_ERROR; } cert_list = gnutls_certificate_get_peers (session, &cert_list_size); if (cert_list == NULL) { printf ("No certificate was found!\n"); return GNUTLS_E_CERTIFICATE_ERROR; } if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) { printf ("error parsing certificate\n"); return GNUTLS_E_CERTIFICATE_ERROR; } if (!gnutls_x509_crt_check_hostname (cert, hostname)) { printf ("The certificate's owner does not match hostname '%s'\n", hostname); return GNUTLS_E_CERTIFICATE_ERROR; } gnutls_x509_crt_deinit (cert); /* notify gnutls to continue handshake normally */ return 0; } /* This class is used to ensure that the GNUTLS system is initialised * and thread-safe. */ @interface GNUTLSObject : NSObject @end @implementation GNUTLSObject + (void) _defaultsChanged: (NSNotification*)n { cipherList = [[NSUserDefaults standardUserDefaults] stringForKey: @"GSCipherList"]; } + (void) initialize { if ([GNUTLSObject class] == self) { static BOOL beenHere = NO; if (beenHere == NO) { NSUserDefaults *defs; beenHere = YES; defs = [NSUserDefaults standardUserDefaults]; cipherList = [defs stringForKey: @"GSCipherList"]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_defaultsChanged:) name: NSUserDefaultsDidChangeNotification object: nil]; /* Make gcrypt thread-safe */ gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_other); /* Initialise gnutls */ gnutls_global_init (); /* Allocate global credential information for anonymous tls */ gnutls_anon_allocate_client_credentials (&anoncred); /* Enable gnutls logging via NSLog */ gnutls_global_set_log_function (GSTLSLog); } } } @end @interface GNUTLSDHParams : GNUTLSObject { gnutls_dh_params_t params; } + (GNUTLSDHParams*) current; - (gnutls_dh_params_t) params; @end @implementation GNUTLSDHParams static NSLock *paramsLock = nil; static NSDate *when = nil; static GNUTLSDHParams *current = nil; + (GNUTLSDHParams*) current { GNUTLSDHParams *p; [paramsLock lock]; if (nil == current) { current = [self new]; } p = [current retain]; [paramsLock unlock]; return [current autorelease]; } + (void) housekeeping: (NSNotification*)n { NSDate *now; now = [NSDate date]; [paramsLock lock]; /* Regenerate DH params once per day. */ if ([now timeIntervalSinceDate: when] > 24 * 60 * 60) { ASSIGN(when, [NSDate date]); ASSIGN(current, [self new]); } [paramsLock unlock]; } + (void) initialize { if (nil == paramsLock) { paramsLock = [NSLock new]; when = [NSDate new]; current = [self new]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(housekeeping:) name: @"GSHousekeeping" object: nil]; } } - (void) dealloc { gnutls_dh_params_deinit (params); [super dealloc]; } - (id) init { /* Generate Diffie-Hellman parameters - for use with DHE * kx algorithms. When short bit length is used, it might * be wise to regenerate parameters often. */ gnutls_dh_params_init (¶ms); gnutls_dh_params_generate2 (params, 2048); return self; } - (gnutls_dh_params_t) params { return params; } @end /* Manage certificate lists (for servers and clients) and also provide * DH params. */ @interface GNUTLSCertificateList : GNUTLSObject { NSDate *when; NSString *path; gnutls_x509_crt_t *crts; unsigned int count; } + (GNUTLSCertificateList*) listFromFile: (NSString*)f; - (gnutls_x509_crt_t*) certificateList; - (unsigned int) count; @end @implementation GNUTLSCertificateList static NSLock *certificateListLock = nil; static NSMutableDictionary *certificateListCache = nil; /* Method to purge older lists from cache. */ + (void) housekeeping: (NSNotification*)n { NSEnumerator *enumerator; NSString *key; NSDate *now; now = [NSDate date]; [certificateListLock lock]; enumerator = [[certificateListCache allKeys] objectEnumerator]; while (nil != (key = [enumerator nextObject])) { GNUTLSCertificateList *list; list = [certificateListCache objectForKey: key]; if ([now timeIntervalSinceDate: list->when] > 300.0) { [certificateListCache removeObjectForKey: key]; } } [certificateListLock unlock]; } + (void) initialize { if (nil == certificateListLock) { certificateListLock = [NSLock new]; certificateListCache = [NSMutableDictionary new]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(housekeeping:) name: @"GSHousekeeping" object: nil]; } } + (GNUTLSCertificateList*) listFromFile: (NSString*)f { GNUTLSCertificateList *l; if (nil == f) { return nil; } [certificateListLock lock]; l = [[certificateListCache objectForKey: f] retain]; [certificateListLock unlock]; if (nil == l) { NSData *data; int ret; gnutls_datum_t datum; unsigned int count = 100; gnutls_x509_crt_t crts[count]; data = [NSData dataWithContentsOfFile: f]; if (nil == data) { NSLog(@"Unable to read certificate file '%@'", f); return nil; } datum.data = (unsigned char*)[data bytes]; datum.size = (unsigned int)[data length]; l = [self alloc]; l->when = [NSDate new]; l->path = [f copy]; ret = gnutls_x509_crt_list_import(crts, &count, &datum, GNUTLS_X509_FMT_PEM, // GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED | GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); if (ret < 0) { NSLog(@"Unable to parse certificate file '%@': %s", l->path, gnutls_strerror(ret)); [l release]; return nil; } l->crts = malloc(sizeof(gnutls_x509_crt_t) * count); memcpy(l->crts, crts, sizeof(gnutls_x509_crt_t) * count); l->count = count; [certificateListLock lock]; [certificateListCache setObject: l forKey: l->path]; [certificateListLock unlock]; } return [l autorelease]; } - (gnutls_x509_crt_t*) certificateList { return crts; } - (unsigned int) count { return count; } - (void) dealloc { if (nil != path) { DESTROY(when); DESTROY(path); if (count > 0) { while (count-- > 0) { gnutls_x509_crt_deinit(crts[count]); } free(crts); } } [super dealloc]; } @end @interface GNUTLSPrivateKey : GNUTLSObject { NSDate *when; NSString *path; NSString *password; gnutls_x509_privkey_t key; } + (GNUTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p; - (gnutls_x509_privkey_t) key; @end @implementation GNUTLSPrivateKey static NSLock *privateKeyLock = nil; static NSMutableDictionary *privateKeyCache0 = nil; static NSMutableDictionary *privateKeyCache1 = nil; /* Method to purge older keys from cache. */ + (void) housekeeping: (NSNotification*)n { NSEnumerator *outer; NSString *oKey; NSDate *now; now = [NSDate date]; [privateKeyLock lock]; outer = [[privateKeyCache0 allKeys] objectEnumerator]; while (nil != (oKey = [outer nextObject])) { GNUTLSPrivateKey *key; key = [privateKeyCache0 objectForKey: oKey]; if ([now timeIntervalSinceDate: key->when] > 300.0) { [privateKeyCache0 removeObjectForKey: oKey]; } } outer = [[privateKeyCache1 allKeys] objectEnumerator]; while (nil != (oKey = [outer nextObject])) { NSMutableDictionary *m; NSEnumerator *inner; NSString *iKey; m = [privateKeyCache1 objectForKey: oKey]; inner = [[m allKeys] objectEnumerator]; while (nil != (iKey = [inner nextObject])) { GNUTLSPrivateKey *key = [m objectForKey: iKey]; if ([now timeIntervalSinceDate: key->when] > 300.0) { [m removeObjectForKey: iKey]; if (0 == [m count]) { [privateKeyCache1 removeObjectForKey: oKey]; } } } } [privateKeyLock unlock]; } + (void) initialize { if (nil == privateKeyLock) { privateKeyLock = [NSLock new]; privateKeyCache0 = [NSMutableDictionary new]; privateKeyCache1 = [NSMutableDictionary new]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(housekeeping:) name: @"GSHousekeeping" object: nil]; } } + (GNUTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p { GNUTLSPrivateKey *k; if (nil == f) { return nil; } [privateKeyLock lock]; if (nil == p) { k = [privateKeyCache0 objectForKey: f]; } else { NSMutableDictionary *m; m = [privateKeyCache1 objectForKey: f]; if (nil == m) { k = nil; } else { k = [m objectForKey: p]; } } [k retain]; [privateKeyLock unlock]; if (nil == k) { NSData *data; int ret; gnutls_datum_t datum; data = [NSData dataWithContentsOfFile: f]; if (nil == data) { NSLog(@"Unable to read private key file '%@'", f); return nil; } datum.data = (unsigned char*)[data bytes]; datum.size = (unsigned int)[data length]; k = [self alloc]; k->when = [NSDate new]; k->path = [f copy]; k->password = [p copy]; gnutls_x509_privkey_init(&k->key); if (nil == k->password) { ret = gnutls_x509_privkey_import(k->key, &datum, GNUTLS_X509_FMT_PEM); } else { ret = gnutls_x509_privkey_import_pkcs8(k->key, &datum, GNUTLS_X509_FMT_PEM, [k->password UTF8String], 0); } if (ret < 0) { NSLog(@"Unable to parse private key file '%@': %s", k->path, gnutls_strerror(ret)); [k release]; return nil; } [privateKeyLock lock]; if (nil == k->password) { [privateKeyCache0 setObject: k forKey: k->path]; } else { NSMutableDictionary *m; m = [privateKeyCache1 objectForKey: f]; if (nil == m) { m = [NSMutableDictionary new]; [privateKeyCache1 setObject: m forKey: f]; [m release]; } [m setObject: k forKey: p]; } [privateKeyLock unlock]; } return [k autorelease]; } - (void) dealloc { if (nil != path) { DESTROY(when); DESTROY(path); DESTROY(password); gnutls_x509_privkey_deinit(key); } [super dealloc]; } - (gnutls_x509_privkey_t) key { return key; } @end @interface GSTLS : GSStreamHandler { @public gnutls_session_t session; gnutls_certificate_credentials_t certcred; } @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; GSTLS *tls = (GSTLS*)handle; result = [[tls istream] _read: buffer maxLength: len]; if (result < 0) { int e; if ([[tls istream] streamStatus] == NSStreamStatusError) { e = [[[(GSTLS*)handle istream] streamError] code]; } else { e = EAGAIN; // Tell GNUTLS this would block. } #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno (tls->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; GSTLS *tls = (GSTLS*)handle; result = [[tls ostream] _write: buffer maxLength: len]; if (result < 0) { int e; if ([[tls ostream] streamStatus] == NSStreamStatusError) { e = [[[tls ostream] streamError] code]; } else { e = EAGAIN; // Tell GNUTLS this would block. } #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno (tls->session, e); #else errno = e; // Not thread-safe #endif } return result; } @implementation GSTLS + (void) initialize { [GNUTLSObject class]; } + (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) { GSTLS *h; h = [[GSTLS alloc] initWithInput: i output: o]; [i _setHandler: h]; [o _setHandler: h]; RELEASE(h); } } - (void) bye { if (active == YES || handshake == YES) { active = NO; handshake = NO; gnutls_bye (session, GNUTLS_SHUT_RDWR); } } - (void) dealloc { [self bye]; gnutls_db_remove_session (session); gnutls_deinit (session); gnutls_certificate_free_credentials (certcred); [super dealloc]; } - (BOOL) handshake { return handshake; } - (void) hello { if (active == NO) { int ret; if (handshake == NO) { /* Set flag to say we are now doing a handshake. */ handshake = YES; } ret = gnutls_handshake (session); if (ret < 0) { NSDebugMLLog(@"NSStream", @"Handshake status %d", ret); if (GSDebugSet(@"NSStream") == YES) { gnutls_perror(ret); } } else { handshake = NO; // Handshake is now complete. active = YES; // The TLS session is now active. } } } - (id) initWithInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSString *proto = [i propertyForKey: NSStreamSocketSecurityLevelKey]; BOOL server = NO; /* FIXME if ([[o propertyForKey: NSStreamSocketCertificateServerKey] boolValue] == YES) { server = YES; } */ if (GSDebugSet(@"NSStream") == YES) { gnutls_global_set_log_level (11); // Full debug output } else { gnutls_global_set_log_level (0); // No debug } if ([[o propertyForKey: NSStreamSocketSecurityLevelKey] isEqual: proto] == NO) { NSLog(@"NSStreamSocketSecurityLevel on input stream does not match output stream"); DESTROY(self); return nil; } if ([proto isEqualToString: NSStreamSocketSecurityLevelNone] == YES) { // proto = NSStreamSocketSecurityLevelNone; DESTROY(self); return nil; } else if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv2] == YES) { // proto = NSStreamSocketSecurityLevelSSLv2; GSOnceMLog(@"NSStreamSocketSecurityLevelTLSv2 is insecure ..." @" not implemented"); DESTROY(self); return nil; } else if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv3] == YES) { proto = NSStreamSocketSecurityLevelSSLv3; } else if ([proto isEqualToString: NSStreamSocketSecurityLevelTLSv1] == YES) { proto = NSStreamSocketSecurityLevelTLSv1; } else { proto = NSStreamSocketSecurityLevelNegotiatedSSL; } if ((self = [super initWithInput: i output: o]) == nil) { return nil; } initialised = YES; /* Configure this session to support certificate based * operation. */ gnutls_certificate_allocate_credentials (&certcred); /* FIXME ... should get the trusted authority certificates * from somewhere sensible to validate the remote end! */ gnutls_certificate_set_x509_trust_file (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); /* Initialise session and set default priorities foir key exchange. */ if (server) { gnutls_init (&session, GNUTLS_SERVER); /* FIXME ... need to set up DH information and key/certificate. */ } else { gnutls_init (&session, GNUTLS_CLIENT); } gnutls_set_default_priority (session); if ([proto isEqualToString: NSStreamSocketSecurityLevelTLSv1] == YES) { #if GNUTLS_VERSION_NUMBER < 0x020C00 const int proto_prio[4] = { #if defined(GNUTLS_TLS1_2) GNUTLS_TLS1_2, #endif GNUTLS_TLS1_1, GNUTLS_TLS1_0, 0 }; gnutls_protocol_set_priority (session, proto_prio); #else gnutls_priority_set_direct(session, "NORMAL:-VERS-SSL3.0:+VERS-TLS-ALL", NULL); #endif } if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv3] == YES) { #if GNUTLS_VERSION_NUMBER < 0x020C00 const int proto_prio[2] = { GNUTLS_SSL3, 0 }; gnutls_protocol_set_priority (session, proto_prio); #else gnutls_priority_set_direct(session, "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0", NULL); #endif } /* { const int kx_prio[] = { GNUTLS_KX_RSA, GNUTLS_KX_RSA_EXPORT, GNUTLS_KX_DHE_RSA, GNUTLS_KX_DHE_DSS, GNUTLS_KX_ANON_DH, 0 }; gnutls_kx_set_priority (session, kx_prio); gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred); } */ /* Set certificate credentials for this session. */ gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, certcred); /* Set transport layer to use our low level stream code. */ #if GNUTLS_VERSION_NUMBER < 0x020C00 gnutls_transport_set_lowat (session, 0); #endif gnutls_transport_set_pull_function (session, GSTLSPull); gnutls_transport_set_push_function (session, GSTLSPush); gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)self); return self; } - (GSSocketInputStream*) istream { return istream; } - (GSSocketOutputStream*) ostream { return ostream; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { return gnutls_record_recv (session, buffer, len); } - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event { NSDebugMLLog(@"NSStream", @"GSTLS got %d on %p", event, stream); if (handshake == YES) { switch (event) { case NSStreamEventHasSpaceAvailable: case NSStreamEventHasBytesAvailable: case NSStreamEventOpenCompleted: [self hello]; /* try to complete the handshake */ if (handshake == NO) { NSDebugMLLog(@"NSStream", @"GSTLS completed on %p", stream); if ([istream streamStatus] == NSStreamStatusOpen) { [istream _resetEvents: NSStreamEventOpenCompleted]; [istream _sendEvent: NSStreamEventOpenCompleted]; } else { [istream _resetEvents: NSStreamEventErrorOccurred]; [istream _sendEvent: NSStreamEventErrorOccurred]; } if ([ostream streamStatus] == NSStreamStatusOpen) { [ostream _resetEvents: NSStreamEventOpenCompleted | NSStreamEventHasSpaceAvailable]; [ostream _sendEvent: NSStreamEventOpenCompleted]; [ostream _sendEvent: NSStreamEventHasSpaceAvailable]; } else { [ostream _resetEvents: NSStreamEventErrorOccurred]; [ostream _sendEvent: NSStreamEventErrorOccurred]; } } break; default: break; } } } - (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len { return gnutls_record_send (session, buffer, len); } @end #if !defined(__MINGW__) @interface GSTLSHandle : GSFileHandle { GNUTLSDHParams *dhParams; @public gnutls_session_t session; gnutls_certificate_credentials_t certcred; BOOL active; // able to read/write BOOL handshake; // performing handshake BOOL outgoing; // handshake direction BOOL setup; // have set certificate } - (void) sslDisconnect; - (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing; - (NSString*) sslSetOptions: (NSDictionary*)options; @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 GSTLSHandlePull(gnutls_transport_ptr_t handle, void *buffer, size_t len) { ssize_t result = 0; GSTLSHandle *tls = (GSTLSHandle*)handle; int descriptor = [tls fileDescriptor]; result = read(descriptor, buffer, len); if (result < 0) { #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno (tls->session, errno); #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 GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) { ssize_t result = 0; GSTLSHandle *tls = (GSTLSHandle*)handle; int descriptor = [tls fileDescriptor]; result = write(descriptor, buffer, len); if (result < 0) { #if HAVE_GNUTLS_TRANSPORT_SET_ERRNO gnutls_transport_set_errno (tls->session, errno); #endif } return result; } @implementation GSTLSHandle + (void) initialize { if (self == [GSTLSHandle class]) { [GSTLS class]; // Force initialisation of gnu tls stuff } } - (void) closeFile { [self sslDisconnect]; [super closeFile]; } - (void) finalize { [self sslDisconnect]; if (YES == setup) { setup = NO; gnutls_certificate_free_credentials (certcred); } [super finalize]; } - (NSInteger) read: (void*)buf length: (NSUInteger)len { if (YES == active) { return gnutls_record_recv (session, buf, len); } return [super read: buf length: len]; } - (void) sslDisconnect { if (YES == active || YES == handshake) { active = NO; handshake = NO; gnutls_bye (session, GNUTLS_SHUT_RDWR); gnutls_db_remove_session (session); gnutls_deinit (session); } DESTROY(dhParams); } - (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing { int ret; NSAssert(0 != result, NSInvalidArgumentException); if (YES == active) { return YES; /* Already connected. */ } if (YES == isStandardFile) { NSLog(@"Attempt to perform ssl handshake with a standard file"); return NO; } /* Set the handshake direction so we know how to set up the connection. */ outgoing = isOutgoing; if (NO == setup) { [self sslSetCertificate: nil privateKey: nil PEMpasswd: nil]; } if (NO == handshake) { handshake = YES; // Set flag to say a handshake is in progress /* Now initialise session and set it up */ if (YES == outgoing) { gnutls_init (&session, GNUTLS_CLIENT); } else { gnutls_init (&session, GNUTLS_SERVER); /* We don't request any certificate from the client. * If we did we would need to verify it. */ gnutls_certificate_server_set_request (session, GNUTLS_CERT_IGNORE); /* FIXME ... need to set up DH information and key/certificate. */ } gnutls_set_default_priority (session); #if GNUTLS_VERSION_NUMBER < 0x020C00 { const int proto_prio[2] = { GNUTLS_SSL3, 0 }; gnutls_protocol_set_priority (session, proto_prio); } #else gnutls_priority_set_direct(session, "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0", NULL); #endif /* { const int kx_prio[] = { GNUTLS_KX_RSA, GNUTLS_KX_RSA_EXPORT, GNUTLS_KX_DHE_RSA, GNUTLS_KX_DHE_DSS, GNUTLS_KX_ANON_DH, 0 }; gnutls_kx_set_priority (session, kx_prio); gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred); } */ /* Set certificate credentials for this session. */ gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, certcred); /* Set transport layer to use our low level stream code. */ #if GNUTLS_VERSION_NUMBER < 0x020C00 gnutls_transport_set_lowat (session, 0); #endif gnutls_transport_set_pull_function (session, GSTLSHandlePull); gnutls_transport_set_push_function (session, GSTLSHandlePush); gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)self); [self setNonBlocking: YES]; } if (outgoing != isOutgoing) { [NSException raise: NSInvalidArgumentException format: @"Attempt to change direction of TLS handshake"]; } ret = gnutls_handshake (session); if (ret < 0) { if (gnutls_error_is_fatal(ret)) { NSLog(@"unable to make SSL connection to %@:%@ - %s", address, service, gnutls_strerror(ret)); *result = NO; } else { if (GSDebugSet(@"NSStream") == YES) { gnutls_perror(ret); } return NO; // Non-fatal error needs a retry. } } else { handshake = NO; // Handshake is now complete. active = YES; // The TLS session is now active. *result = active; } return YES; } - (NSString*) sslSetOptions: (NSDictionary*)options { if (isStandardFile == YES) { return @"Attempt to set ssl options for a standard file"; } if (NO == setup) { NSString *certFile; NSString *privateKey; NSString *PEMpasswd; GNUTLSPrivateKey *key = nil; GNUTLSCertificateList *list = nil; int ret; certFile = [options objectForKey: GSTLSCertificateFileKey]; privateKey = [options objectForKey: GSTLSPrivateKeyFileKey]; PEMpasswd = [options objectForKey: GSTLSPrivateKeyPasswordKey]; if (nil != privateKey) { key = [GNUTLSPrivateKey keyFromFile: privateKey withPassword: PEMpasswd]; if (nil == key) { return @"Unable to load key file"; } } if (nil != certFile) { list = [GNUTLSCertificateList listFromFile: certFile]; if (nil == list) { return @"Unable to load certificate file"; } } setup = YES; /* Configure this session to support certificate based * operation. */ gnutls_certificate_allocate_credentials (&certcred); /* FIXME ... should get the trusted authority certificates * from somewhere sensible to validate the remote end! */ gnutls_certificate_set_x509_trust_file (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); /* gnutls_certificate_set_x509_crl_file (x509_cred, [certFile UTF8String], GNUTLS_X509_FMT_PEM); */ if (nil != list) { ret = gnutls_certificate_set_x509_key (certcred, [list certificateList], [list count], [key key]); if (ret < 0) { return [NSString stringWithFormat: @"Unable to set certificate for session: %s", gnutls_strerror(ret)]; } else if (NO == outgoing) { dhParams = [[GNUTLSDHParams current] retain]; gnutls_certificate_set_dh_params (certcred, [dhParams params]); } } #if 0 if (nil != cipherList) { SSL_CTX_set_cipher_list(ctx, [cipherList UTF8String]); } #endif } return nil; } - (NSInteger) write: (const void*)buf length: (NSUInteger)len { if (YES == active) { return gnutls_record_send (session, buf, len); } return [super write: buf length: len]; } @end #endif /* MINGW */ #else /* HAVE_GNUTLS */ /* GNUTLS not available ... */ @interface GSTLS : GSStreamHandler @end @implementation GSTLS + (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]; [GSTLS 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", (NSInteger)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 (ul < 1 || ul > 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(__MINGW__) return (result == SOCKET_ERROR) ? YES : NO; #else return (result < 0) ? YES : NO; #endif } static inline BOOL socketWouldBlock() { return GSWOULDBLOCK ? YES : NO; } static void setNonBlocking(SOCKET fd) { #if defined(__MINGW__) 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 ([self _isOpened]) { [self close]; } [_sibling _setSibling: nil]; _sibling = nil; DESTROY(_handler); [super dealloc]; } - (id) init { if ((self = [super init]) != nil) { // so that unopened access will fail _sibling = nil; _closing = NO; _passive = NO; #if defined(__MINGW__) _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]; struct sockaddr sin; socklen_t size = sizeof(sin); if ([key isEqualToString: GSStreamLocalAddressKey]) { if (getsockname(s, (struct sockaddr*)&sin, &size) != -1) { result = GSPrivateSockaddrHost(&sin); } } else if ([key isEqualToString: GSStreamLocalPortKey]) { if (getsockname(s, (struct sockaddr*)&sin, &size) != -1) { result = [NSString stringWithFormat: @"%d", (int)GSPrivateSockaddrPort(&sin)]; } } else if ([key isEqualToString: GSStreamRemoteAddressKey]) { if (getpeername(s, (struct sockaddr*)&sin, &size) != -1) { result = GSPrivateSockaddrHost(&sin); } } else if ([key isEqualToString: GSStreamRemotePortKey]) { if (getpeername(s, (struct sockaddr*)&sin, &size) != -1) { result = [NSString stringWithFormat: @"%d", (int)GSPrivateSockaddrPort(&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) { id del = _delegate; BOOL val = _delegateValid; _delegate = _handler; _delegateValid = YES; [super _sendEvent: event]; _delegate = del; _delegateValid = val; } 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 __MINGW__ 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(__MINGW__) _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(__MINGW__) _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 { GSMakeWeakPointer(self, "_sibling"); if (self == [GSSocketInputStream class]) { GSObjCAddClassBehavior(self, [GSSocketStream class]); } } - (void) open { // could be opened because of sibling if ([self _isOpened]) 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 { [self _setSock: s]; [_sibling _setSock: s]; } } if (_handler == nil) { [GSTLS tryInput: self output: _sibling]; } result = connect([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); if (socketError(result)) { if (!socketWouldBlock()) { [self _recordError]; [self _setHandler: nil]; [_sibling _setHandler: nil]; return; } /* * 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]; #if defined(__MINGW__) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif if (NSCountMapTable(_loops) > 0) { [self _schedule]; return; } else { 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(__MINGW__) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif [super open]; } - (void) close { 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(__MINGW__) 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. */ WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); shutdown(_sock, SHUT_RD); WSAEventSelect(_sock, [_sibling _loopID], FD_ALL_EVENTS); } 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_RD); [super close]; _loopID = (void*)(intptr_t)-1; #endif _sock = INVALID_SOCKET; } - (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len { 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) return [self _read: buffer maxLength: len]; else return [_handler read: buffer maxLength: len]; } - (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(__MINGW__) readLen = recv([self _sock], (char*) buffer, (socklen_t) len, 0); #else readLen = read([self _sock], buffer, len); #endif } if (socketError(readLen)) { 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 (socketWouldBlock()) { /* We need an event from the operating system * to tell us we can start reading again. */ [self _setStatus: NSStreamStatusReading]; } else { [self _recordError]; } 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(__MINGW__) 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 { WSANETWORKEVENTS events; int error = 0; int getReturn = -1; if (WSAEnumNetworkEvents(_sock, _loopID, &events) == SOCKET_ERROR) { error = WSAGetLastError(); } // else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); if ([self streamStatus] == NSStreamStatusOpening) { [self _unschedule]; if (error == 0) { socklen_t len = sizeof(error); getReturn = getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char*)&error, &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) { errno = error; [self _recordError]; [_sibling _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; [_sibling _sendEvent: NSStreamEventErrorOccurred]; } else { if (events.lNetworkEvents & FD_WRITE) { NSAssert([_sibling _isOpened], NSInternalInconsistencyException); /* Clear NSStreamStatusWriting if it was set */ [_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_GC([[self retain] autorelease];) [self _unschedule]; result = getsockopt([self _sock], SOL_SOCKET, SO_ERROR, &error, &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 { if (error) errno = error; [self _recordError]; myEvent = NSStreamEventErrorOccurred; [_sibling _recordError]; [_sibling _sendEvent: myEvent]; } } else if ([self streamStatus] == NSStreamStatusAtEnd) { myEvent = NSStreamEventEndEncountered; } else { [self _setStatus: NSStreamStatusOpen]; myEvent = NSStreamEventHasBytesAvailable; } [self _sendEvent: myEvent]; #endif } #if defined(__MINGW__) - (BOOL) runLoopShouldBlock: (BOOL*)trigger { *trigger = YES; return YES; } #endif @end @implementation GSSocketOutputStream + (void) initialize { GSMakeWeakPointer(self, "_sibling"); if (self == [GSSocketOutputStream class]) { GSObjCAddClassBehavior(self, [GSSocketStream class]); } } - (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(__MINGW__) writeLen = send([self _sock], (char*) buffer, (socklen_t) len, 0); #else writeLen = write([self _sock], buffer, (socklen_t) len); #endif if (socketError(writeLen)) { 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 (socketWouldBlock()) { /* We need an event from the operating system * to tell us we can start writing again. */ [self _setStatus: NSStreamStatusWriting]; } else { [self _recordError]; } writeLen = -1; } } else { [self _setStatus: NSStreamStatusOpen]; } return writeLen; } - (void) open { // could be opened because of sibling if ([self _isOpened]) 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 { [self _setSock: s]; [_sibling _setSock: s]; } } if (_handler == nil) { [GSTLS tryInput: _sibling output: self]; } result = connect([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); if (socketError(result)) { if (!socketWouldBlock()) { [self _recordError]; [self _setHandler: nil]; [_sibling _setHandler: nil]; return; } /* * 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]; #if defined(__MINGW__) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif if (NSCountMapTable(_loops) > 0) { [self _schedule]; return; } else { 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(__MINGW__) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif [super open]; } - (void) close { 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(__MINGW__) 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. */ WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); shutdown(_sock, SHUT_WR); WSAEventSelect(_sock, [_sibling _loopID], FD_ALL_EVENTS); } 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 { if (buffer == 0) { [NSException raise: NSInvalidArgumentException format: @"null pointer for buffer"]; } if (len == 0) { [NSException raise: NSInvalidArgumentException format: @"zero byte length write requested"]; } if (_handler == nil) return [self _write: buffer maxLength: len]; else return [_handler write: buffer maxLength: len]; } - (void) _dispatch { #if defined(__MINGW__) 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 { WSANETWORKEVENTS events; int error = 0; int getReturn = -1; if (WSAEnumNetworkEvents(_sock, _loopID, &events) == SOCKET_ERROR) { error = WSAGetLastError(); } // else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); if ([self streamStatus] == NSStreamStatusOpening) { [self _unschedule]; if (error == 0) { socklen_t len = sizeof(error); getReturn = getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char*)&error, &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) { errno = error; [self _recordError]; [_sibling _recordError]; [self _sendEvent: NSStreamEventErrorOccurred]; [_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_GC([[self retain] autorelease];) [self _schedule]; result = getsockopt((intptr_t)_loopID, SOL_SOCKET, SO_ERROR, &error, &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 { if (error) errno = error; [self _recordError]; myEvent = NSStreamEventErrorOccurred; [_sibling _recordError]; [_sibling _sendEvent: myEvent]; } } else if ([self streamStatus] == NSStreamStatusAtEnd) { myEvent = NSStreamEventEndEncountered; } else { [self _setStatus: NSStreamStatusOpen]; myEvent = NSStreamEventHasSpaceAvailable; } [self _sendEvent: myEvent]; #endif } #if defined(__MINGW__) - (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 { GSMakeWeakPointer(self, "_sibling"); 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; 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 { [(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; setsockopt([self _sock], SOL_SOCKET, SO_REUSEADDR, (char *)&status, sizeof(status)); } #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(__MINGW__) WSAEventSelect(_sock, _loopID, FD_ALL_EVENTS); #endif [super open]; } - (void) close { #if defined(__MINGW__) 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 { GSSocketStream *ins = AUTORELEASE([[self _inputStreamClass] new]); GSSocketStream *outs = AUTORELEASE([[self _outputStreamClass] new]); uint8_t buf[BUFSIZ]; struct sockaddr *addr = (struct sockaddr*)buf; socklen_t len = sizeof(buf); int acceptReturn; acceptReturn = accept([self _sock], addr, &len); _events &= ~NSStreamEventHasBytesAvailable; if (socketError(acceptReturn)) { // test for real error if (!socketWouldBlock()) { [self _recordError]; } 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]; } if (inputStream) { [ins _setSibling: outs]; *inputStream = (NSInputStream*)ins; } if (outputStream) { [outs _setSibling: ins]; *outputStream = (NSOutputStream*)outs; } } - (void) _dispatch { #if defined(__MINGW__) 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