libs-base/Source/NSFileHandle.m
rfm 971a8374a8 Reorganise a bit to ensure that TLS is properly shut down before the network
connection it relies on is closed.  Needed in case information is buffered
in the TLS layer and needs flushing to the remote end before shutdown.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@37390 72102866-910b-0410-8b05-ffd578937521
2013-11-21 15:13:14 +00:00

1076 lines
28 KiB
Objective-C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** Implementation for NSFileHandle for GNUStep
Copyright (C) 1997 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
Date: 1997
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
<title>NSFileHandle class reference</title>
$Date$ $Revision$
*/
#import "common.h"
#define EXPOSE_NSFileHandle_IVARS 1
#import "Foundation/NSData.h"
#import "Foundation/NSException.h"
#import "Foundation/NSHost.h"
#import "Foundation/NSFileHandle.h"
#import "Foundation/NSPathUtilities.h"
#import "GNUstepBase/NSString+GNUstepBase.h"
#import "GSPrivate.h"
#import "GSNetwork.h"
#import "GSTLS.h"
#define EXPOSE_GSFileHandle_IVARS 1
#import "GSFileHandle.h"
// GNUstep Notification names
NSString * const GSFileHandleConnectCompletionNotification
= @"GSFileHandleConnectCompletionNotification";
NSString * const GSFileHandleWriteCompletionNotification
= @"GSFileHandleWriteCompletionNotification";
// GNUstep key for getting error message.
NSString * const GSFileHandleNotificationError
= @"GSFileHandleNotificationError";
static Class NSFileHandle_abstract_class = nil;
static Class NSFileHandle_concrete_class = nil;
static Class NSFileHandle_ssl_class = nil;
/**
* <p>
* <code>NSFileHandle</code> is a class that provides a wrapper for accessing
* system files and socket connections. You can open connections to a
* file using class methods such as +fileHandleForReadingAtPath:.
* </p>
* <p>
* GNUstep extends the use of this class to allow you to create
* network connections (sockets), secure connections and also allows
* you to use compression with these files and connections (as long as
* GNUstep Base was compiled with the zlib library).
* </p>
*/
@implementation NSFileHandle
+ (void) initialize
{
if (self == [NSFileHandle class])
{
NSFileHandle_abstract_class = self;
NSFileHandle_concrete_class = [GSFileHandle class];
}
}
+ (id) allocWithZone: (NSZone*)z
{
if (self == NSFileHandle_abstract_class)
{
return NSAllocateObject (NSFileHandle_concrete_class, 0, z);
}
else
{
return NSAllocateObject (self, 0, z);
}
}
// Allocating and Initializing a FileHandle Object
/**
* Returns an <code>NSFileHandle</code> object set up for reading from the
* file listed at path. If the file does not exist or cannot
* be opened for some other reason, nil is returned.
*/
+ (id) fileHandleForReadingAtPath: (NSString*)path
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initForReadingAtPath: path]);
}
/**
* Returns an <code>NSFileHandle</code> object set up for writing to the
* file listed at path. If the file does not exist or cannot
* be opened for some other reason, nil is returned.
*/
+ (id) fileHandleForWritingAtPath: (NSString*)path
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initForWritingAtPath: path]);
}
/**
* Returns an <code>NSFileHandle</code> object setup for updating (reading and
* writing) from the file listed at path. If the file does not exist
* or cannot be opened for some other reason, nil is returned.
*/
+ (id) fileHandleForUpdatingAtPath: (NSString*)path
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initForUpdatingAtPath: path]);
}
/**
* Returns an <code>NSFileHandle</code> object for the standard error
* descriptor. The returned object is a shared instance as there can only be
* one standard error per process.
*/
+ (id) fileHandleWithStandardError
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initWithStandardError]);
}
/**
* Returns an <code>NSFileHandle</code> object for the standard input
* descriptor. The returned object is a shared instance as there can only be
* one standard input per process.
*/
+ (id) fileHandleWithStandardInput
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initWithStandardInput]);
}
/**
* Returns an <code>NSFileHandle</code> object for the standard output
* descriptor. The returned object is a shared instance as there can only be
* one standard output per process.
*/
+ (id) fileHandleWithStandardOutput
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initWithStandardOutput]);
}
/**
* Returns a file handle object that is connected to the null device
* (i.e. a device that does nothing.) It is typically used in arrays
* and other collections of file handle objects as a place holder
* (null) object, so that all objects can respond to the same
* messages.
*/
+ (id) fileHandleWithNullDevice
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initWithNullDevice]);
}
/**
* Initialize with desc, which can point to either a regular file or
* socket connection.
*/
- (id) initWithFileDescriptor: (int)desc
{
return [self initWithFileDescriptor: desc closeOnDealloc: NO];
}
/**
* Initialize with desc, which can point to either a regular file or
* socket connection. Close desc when this instance is deallocated if
* flag is YES.
*/
- (id) initWithFileDescriptor: (int)desc closeOnDealloc: (BOOL)flag
{
[self subclassResponsibility: _cmd];
return nil;
}
/**
* Windows-Unix compatibility support.
*/
- (id) initWithNativeHandle: (void*)hdl
{
return [self initWithNativeHandle: hdl closeOnDealloc: NO];
}
// This is the designated initializer.
/**
* <init/>
* Windows-Unix compatibility support.
*/
- (id) initWithNativeHandle: (void*)hdl closeOnDealloc: (BOOL)flag
{
[self subclassResponsibility: _cmd];
return nil;
}
// Returning file handles
/**
* Return the underlying file descriptor for this instance.
*/
- (int) fileDescriptor
{
[self subclassResponsibility: _cmd];
return -1;
}
/**
* Windows-Unix compatibility support.
*/
- (void*) nativeHandle
{
[self subclassResponsibility: _cmd];
return 0;
}
// Synchronous I/O operations
/**
* Synchronously returns data available through this file or connection.
* If the handle represents a file, the entire contents from current file
* pointer to end are returned. If this is a network connection, reads
* what is available, blocking if nothing is available. Raises
* <code>NSFileHandleOperationException</code> if problem encountered.
*/
- (NSData*) availableData
{
[self subclassResponsibility: _cmd];
return nil;
}
/**
* Reads up to maximum unsigned int bytes from file or communications
* channel into return data.<br />
* If the file is empty, returns an empty data item.
*/
- (NSData*) readDataToEndOfFile
{
[self subclassResponsibility: _cmd];
return nil;
}
/**
* Reads up to len bytes from file or communications channel into return data.
*/
- (NSData*) readDataOfLength: (unsigned int)len
{
[self subclassResponsibility: _cmd];
return nil;
}
/**
* Synchronously writes given data item to file or connection.
*/
- (void) writeData: (NSData*)item
{
[self subclassResponsibility: _cmd];
}
// Asynchronous I/O operations
/**
* Asynchronously accept a stream-type socket connection and act as the
* (server) end of the communications channel. This instance should have
* been created by -initWithFileDescriptor: with a stream-type socket created
* by the appropriate system routine. Posts a
* <code>NSFileHandleConnectionAcceptedNotification</code> when connection
* initiated, returning an <code>NSFileHandle</code> for the client side with
* that notification.
*/
- (void) acceptConnectionInBackgroundAndNotify
{
[self acceptConnectionInBackgroundAndNotifyForModes: nil];
}
/**
* <p>Asynchronously accept a stream-type socket connection and act as the
* (server) end of the communications channel. This instance should have
* been created by -initWithFileDescriptor: with a stream-type socket created
* by the appropriate system routine. Posts a
* <code>NSFileHandleConnectionAcceptedNotification</code> when connection
* initiated, returning an <code>NSFileHandle</code> for the client side with
* that notification.</p>
*
* <p>The modes array specifies [NSRunLoop] modes that the notification can
* be posted in.</p>
*/
- (void) acceptConnectionInBackgroundAndNotifyForModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
/**
* Call -readInBackgroundAndNotifyForModes: with nil modes.
*/
- (void) readInBackgroundAndNotify
{
[self readInBackgroundAndNotifyForModes: nil];
}
/**
* Set up an asynchronous read operation which will cause a notification to
* be sent when any amount of data (or end of file) is read. Note that
* the file handle will not continuously send notifications when data
* is available. If you want to continue to receive notifications, you
* need to send this message again after receiving a notification.
*/
- (void) readInBackgroundAndNotifyForModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
/**
* Call -readToEndOfFileInBackgroundAndNotifyForModes: with nil modes.
*/
- (void) readToEndOfFileInBackgroundAndNotify
{
[self readToEndOfFileInBackgroundAndNotifyForModes: nil];
}
/**
* Set up an asynchronous read operation which will cause a notification to
* be sent when end of file is read.
*/
- (void) readToEndOfFileInBackgroundAndNotifyForModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
/**
* Call -waitForDataInBackgroundAndNotifyForModes: with nil modes.
*/
- (void) waitForDataInBackgroundAndNotify
{
[self waitForDataInBackgroundAndNotifyForModes: nil];
}
/**
* Set up to provide a notification when data can be read from the handle.
*/
- (void) waitForDataInBackgroundAndNotifyForModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
// Seeking within a file
/**
* Return current position in file, or raises exception if instance does
* not represent a regular file.
*/
- (unsigned long long) offsetInFile
{
[self subclassResponsibility: _cmd];
return 0;
}
/**
* Position file pointer at end of file, raising exception if instance does
* not represent a regular file.
*/
- (unsigned long long) seekToEndOfFile
{
[self subclassResponsibility: _cmd];
return 0;
}
/**
* Position file pointer at pos, raising exception if instance does
* not represent a regular file.
*/
- (void) seekToFileOffset: (unsigned long long)pos
{
[self subclassResponsibility: _cmd];
}
// Operations on file
/**
* Disallows further reading from read-access files or connections, and sends
* EOF on write-access files or connections. Descriptor is only
* <em>deleted</em> when this instance is deallocated.
*/
- (void) closeFile
{
[self subclassResponsibility: _cmd];
}
/**
* Flush in-memory buffer to file or connection, then return.
*/
- (void) synchronizeFile
{
[self subclassResponsibility: _cmd];
}
/**
* Chops file beyond pos then sets file pointer to that point.
*/
- (void) truncateFileAtOffset: (unsigned long long)pos
{
[self subclassResponsibility: _cmd];
}
@end
// Keys for accessing userInfo dictionary in notification handlers.
NSString * const NSFileHandleNotificationDataItem
= @"NSFileHandleNotificationDataItem";
NSString * const NSFileHandleNotificationFileHandleItem
= @"NSFileHandleNotificationFileHandleItem";
NSString * const NSFileHandleNotificationMonitorModes
= @"NSFileHandleNotificationMonitorModes";
// Notification names
NSString * const NSFileHandleConnectionAcceptedNotification
= @"NSFileHandleConnectionAcceptedNotification";
NSString * const NSFileHandleDataAvailableNotification
= @"NSFileHandleDataAvailableNotification";
NSString * const NSFileHandleReadCompletionNotification
= @"NSFileHandleReadCompletionNotification";
NSString * const NSFileHandleReadToEndOfFileCompletionNotification
= @"NSFileHandleReadToEndOfFileCompletionNotification";
// Exceptions
/**
* An exception used when a file error occurs.
*/
NSString * const NSFileHandleOperationException
= @"NSFileHandleOperationException";
// GNUstep class extensions
/**
* A set of convenience methods for utilizing the socket communications
* capabilities of the [NSFileHandle] class.
*/
@implementation NSFileHandle (GNUstepExtensions)
/**
* Opens an outgoing network connection by initiating an asynchronous
* connection (see
* [+fileHandleAsClientInBackgroundAtAddress:service:protocol:forModes:])
* and waiting for it to succeed, fail, or time out.
*/
+ (id) fileHandleAsClientAtAddress: (NSString*)address
service: (NSString*)service
protocol: (NSString*)protocol
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initAsClientAtAddress: address
service: service
protocol: protocol]);
}
/**
* Opens an outgoing network connection asynchronously using
* [+fileHandleAsClientInBackgroundAtAddress:service:protocol:forModes:]
*/
+ (id) fileHandleAsClientInBackgroundAtAddress: (NSString*)address
service: (NSString*)service
protocol: (NSString*)protocol
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initAsClientInBackgroundAtAddress: address
service: service
protocol: protocol
forModes: nil]);
}
/**
* <p>
* Opens an outgoing network connection asynchronously.
* </p>
* <list>
* <item>
* The address is the name (or IP dotted quad) of the machine to
* which the connection should be made.
* </item>
* <item>
* The service is the name (or number) of the port to
* which the connection should be made.
* </item>
* <item>
* The protocol is provided so support different network protocols,
* but at present only 'tcp' is supported. However, a protocol
* specification of the form 'socks-...' can be used to control socks5
* support.<br />
* If '...' is empty (ie the string is just 'socks-' then the connection
* is <em>not</em> made via a socks server.<br />
* Otherwise, the text '...' must be the name of the host on which the
* socks5 server is running, with an optional port number separated
* from the host name by a colon.<br />
* Alternatively a prefix of the form 'bind-' followed by an IP address
* may be used (for non-socks connections) to ensure that the connection
* is made from the specified address.
* </item>
* <item>
* If modes is nil or empty, uses NSDefaultRunLoopMode.
* </item>
* </list>
* <p>
* This method supports connection through a firewall via socks5. The
* socks5 connection may be controlled via the protocol argument, but if
* no socks information is supplied here, the <em>GSSOCKS</em> user default
* will be used, and failing that, the <em>SOCKS5_SERVER</em> or
* <em>SOCKS_SERVER</em> environment variables will be used to set the
* socks server. If none of these mechanisms specify a socks server, the
* connection will be made directly rather than through socks.
* </p>
*/
+ (id) fileHandleAsClientInBackgroundAtAddress: (NSString*)address
service: (NSString*)service
protocol: (NSString*)protocol
forModes: (NSArray*)modes
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initAsClientInBackgroundAtAddress: address
service: service
protocol: protocol
forModes: modes]);
}
/**
* Opens a network server socket and listens for incoming connections
* using the specified service and protocol.
* <list>
* <item>
* The service is the name (or number) of the port to
* which the connection should be made.
* </item>
* <item>
* The protocol may at present only be 'tcp'
* </item>
* </list>
*/
+ (id) fileHandleAsServerAtAddress: (NSString*)address
service: (NSString*)service
protocol: (NSString*)protocol
{
id o = [self allocWithZone: NSDefaultMallocZone()];
return AUTORELEASE([o initAsServerAtAddress: address
service: service
protocol: protocol]);
}
/**
* Call -readDataInBackgroundAndNotifyLength:forModes: with nil modes.
*/
- (void) readDataInBackgroundAndNotifyLength: (unsigned)len
{
[self readDataInBackgroundAndNotifyLength: len forModes: nil];
}
/**
* Set up an asynchronous read operation which will cause a notification to
* be sent when the specified amount of data (or end of file) is read.
*/
- (void) readDataInBackgroundAndNotifyLength: (unsigned)len
forModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
/**
* Returns a boolean to indicate whether a read operation of any kind is
* in progress on the handle.
*/
- (BOOL) readInProgress
{
[self subclassResponsibility: _cmd];
return NO;
}
/**
* Returns the host address of the network connection represented by
* the file handle. If this handle is an incoming connection which
* was received by a local server handle, this is the name or address
* of the client machine.
*/
- (NSString*) socketAddress
{
return nil;
}
/**
* Returns the local address of the network connection or nil.
*/
- (NSString*) socketLocalAddress
{
return nil;
}
/**
* Returns the local service/port of the network connection or nil.
*/
- (NSString*) socketLocalService
{
return nil;
}
/**
* Returns the name (or number) of the service (network port) in use for
* the network connection represented by the file handle.
*/
- (NSString*) socketService
{
return nil;
}
/**
* Returns the name of the protocol in use for the network connection
* represented by the file handle.
*/
- (NSString*) socketProtocol
{
return nil;
}
/**
* <p>
* Return a flag to indicate whether compression has been turned on for
* the file handle ... this is only available on systems where GNUstep
* was built with 'zlib' support for compressing/decompressing data.
* </p>
* <p>
* On systems which support it, this method may be called after
* a file handle has been initialised to turn on compression or
* decompression of the data being written/read.
* </p>
* Returns YES on success, NO on failure.<br />
* Reasons for failure are - <br />
* <list>
* <item>Not supported/built in to GNUstep</item>
* <item>File handle has been closed</item>
* <item>File handle is open for both read and write</item>
* <item>Failure in compression/decompression library</item>
* </list>
*/
- (BOOL) useCompression
{
return NO;
}
/**
* Call -writeInBackgroundAndNotify:forModes: with nil modes.
*/
- (void) writeInBackgroundAndNotify: (NSData*)item
{
[self writeInBackgroundAndNotify: item forModes: nil];
}
/**
* Write the specified data asynchronously, and notify on completion.
*/
- (void) writeInBackgroundAndNotify: (NSData*)item forModes: (NSArray*)modes
{
[self subclassResponsibility: _cmd];
}
/**
* Returns a boolean to indicate whether a write operation of any kind is
* in progress on the handle. An outgoing network connection attempt
* (as a client) is considered to be a write operation.
*/
- (BOOL) writeInProgress
{
[self subclassResponsibility: _cmd];
return NO;
}
@end
@implementation NSFileHandle (GNUstepTLS)
+ (void) setData: (NSData*)data forTLSFile: (NSString*)fileName
{
#if defined(HAVE_GNUTLS)
[GSTLSObject setData: data forTLSFile: fileName];
#else
[NSException raise: NSInternalInconsistencyException
format: @"[NSFileHandle+setData:forTLSFile:] called for a copy of gnustep-base which had GNUTLS support explicitly disabled at configure time"];
#endif
}
/**
* returns the concrete class used to implement SSL/TLS connections.
*/
+ (Class) sslClass
{
if (0 == NSFileHandle_ssl_class)
{
NSFileHandle_ssl_class = NSClassFromString(@"GSTLSHandle");
if (0 == NSFileHandle_ssl_class)
{
NSString *path;
NSBundle *bundle;
path = [[NSBundle bundleForClass: [NSObject class]] bundlePath];
path = [path stringByAppendingPathComponent: @"SSL.bundle"];
bundle = [NSBundle bundleWithPath: path];
NSFileHandle_ssl_class = [bundle principalClass];
if (NSFileHandle_ssl_class == 0 && bundle != nil)
{
NSLog(@"Failed to load principal class from bundle (%@)", path);
}
}
}
return NSFileHandle_ssl_class;
}
- (BOOL) sslAccept
{
BOOL result = NO;
if (NO == [self sslHandshakeEstablished: &result outgoing: NO])
{
NSRunLoop *loop;
IF_NO_GC([self retain];) // Don't get destroyed during runloop
loop = [NSRunLoop currentRunLoop];
[loop runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
if (NO == [self sslHandshakeEstablished: &result outgoing: NO])
{
NSDate *final;
NSDate *when;
NSTimeInterval last = 0.0;
NSTimeInterval limit = 0.1;
final = [[NSDate alloc] initWithTimeIntervalSinceNow: 30.0];
when = [NSDate alloc];
while (NO == [self sslHandshakeEstablished: &result outgoing: NO]
&& [final timeIntervalSinceNow] > 0.0)
{
NSTimeInterval tmp = limit;
limit += last;
last = tmp;
if (limit > 0.5)
{
limit = 0.1;
last = 0.1;
}
when = [when initWithTimeIntervalSinceNow: limit];
[loop runUntilDate: when];
}
RELEASE(when);
RELEASE(final);
}
DESTROY(self);
}
return result;
}
- (BOOL) sslConnect
{
BOOL result = NO;
if (NO == [self sslHandshakeEstablished: &result outgoing: YES])
{
NSRunLoop *loop;
IF_NO_GC([self retain];) // Don't get destroyed during runloop
loop = [NSRunLoop currentRunLoop];
[loop runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
if (NO == [self sslHandshakeEstablished: &result outgoing: YES])
{
NSDate *final;
NSDate *when;
NSTimeInterval last = 0.0;
NSTimeInterval limit = 0.1;
final = [[NSDate alloc] initWithTimeIntervalSinceNow: 30.0];
when = [NSDate alloc];
while (NO == [self sslHandshakeEstablished: &result outgoing: YES]
&& [final timeIntervalSinceNow] > 0.0)
{
NSTimeInterval tmp = limit;
limit += last;
last = tmp;
if (limit > 0.5)
{
limit = 0.1;
last = 0.1;
}
when = [when initWithTimeIntervalSinceNow: limit];
[loop runUntilDate: when];
}
RELEASE(when);
RELEASE(final);
}
DESTROY(self);
}
return result;
}
- (void) sslDisconnect
{
return;
}
- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing
{
if (0 != result)
{
*result = NO;
}
return YES;
}
- (void) sslSetCertificate: (NSString*)certFile
privateKey: (NSString*)privateKey
PEMpasswd: (NSString*)PEMpasswd
{
NSMutableDictionary *opts;
NSString *err;
opts = [NSMutableDictionary dictionaryWithCapacity: 3];
if (nil != certFile)
{
[opts setObject: certFile forKey: GSTLSCertificateFile];
}
if (nil != privateKey)
{
[opts setObject: privateKey forKey: GSTLSCertificateKeyFile];
}
if (nil != PEMpasswd)
{
[opts setObject: PEMpasswd forKey: GSTLSCertificateKeyPassword];
}
err = [self sslSetOptions: opts];
if (nil != err)
{
NSLog(@"%@", err);
}
}
- (NSString*) sslSetOptions: (NSDictionary*)options
{
return nil;
}
@end
#if defined(HAVE_GNUTLS)
#if !defined(__MINGW__)
@interface GSTLSHandle : GSFileHandle
{
@public
NSDictionary *opts;
GSTLSSession *session;
}
- (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->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->session, errno);
#endif
}
return result;
}
@implementation GSTLSHandle
+ (void) initialize
{
if (self == [GSTLSHandle class])
{
[GSTLSObject class]; // Force initialisation of gnu tls stuff
}
}
- (void) closeFile
{
[self sslDisconnect]; // Shut down TLS before closing socket
[super closeFile];
}
- (void) dealloc
{
/* Any TLS connection needs to be shut down before the network connection
* is closed, which means that the concrete subclass must do that.
* Therefore, the session should be inactive by the time we get here.
*/
NSAssert(NO == [session active], NSInternalInconsistencyException);
DESTROY(session);
DESTROY(opts);
[super dealloc];
}
- (NSInteger) read: (void*)buf length: (NSUInteger)len
{
if (YES == [session active])
{
return [session read: buf length: len];
}
return [super read: buf length: len];
}
- (void) sslDisconnect
{
if (nil != session)
{
// TLS may need to read data during teardown, and we need to wait for it.
[self setNonBlocking: NO];
[session disconnect];
DESTROY(session);
}
DESTROY(opts);
}
- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing
{
NSAssert(0 != result, NSInvalidArgumentException);
if (YES == [session active])
{
return YES; /* Already connected. */
}
if (YES == isStandardFile)
{
NSLog(@"Attempt to perform ssl handshake with a standard file");
return YES;
}
/* Set the handshake direction so we know how to set up the connection.
*/
if (nil == session)
{
/* If No value is specified for GSTLSRemoteHosts, make a comma separated
* list of all known names for the remote host and use that.
*/
if (nil == [opts objectForKey: GSTLSRemoteHosts])
{
NSHost *host = [NSHost hostWithAddress: [self socketAddress]];
NSString *s = [[host names] description];
s = [s stringByReplacingString: @"\"" withString: @""];
if ([s length] > 1)
{
s = [s substringWithRange: NSMakeRange(1, [s length] - 2)];
}
if ([s length] > 0)
{
NSMutableDictionary *d = [opts mutableCopy];
[d setObject:s forKey: GSTLSRemoteHosts];
ASSIGNCOPY(opts, d);
[d release];
}
}
[self setNonBlocking: YES];
session = [[GSTLSSession alloc] initWithOptions: opts
direction: isOutgoing
transport: (void*)self
push: GSTLSHandlePush
pull: GSTLSHandlePull];
}
if (NO == [session handshake])
{
return NO; // Need more.
}
else
{
*result = [session active];
return YES;
}
}
- (NSString*) sslSetOptions: (NSDictionary*)options
{
if (isStandardFile == YES)
{
return @"Attempt to set ssl options for a standard file";
}
ASSIGNCOPY(opts, options);
return nil;
}
- (NSInteger) write: (const void*)buf length: (NSUInteger)len
{
if (YES == [session active])
{
return [session write: buf length: len];
}
return [super write: buf length: len];
}
@end
#endif /* MINGW */
#endif /* HAVE_GNUTLS */