mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 08:26:27 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@38442 72102866-910b-0410-8b05-ffd578937521
748 lines
17 KiB
Objective-C
748 lines
17 KiB
Objective-C
/** Implementation of host class
|
|
Copyright (C) 1996, 1997,1999 Free Software Foundation, Inc.
|
|
|
|
Written by: Luke Howard <lukeh@xedoc.com.au>
|
|
Date: 1996
|
|
Rewrite by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
|
Date: 1999
|
|
|
|
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>NSHost class reference</title>
|
|
$Date$ $Revision$
|
|
*/
|
|
|
|
#import "common.h"
|
|
#define EXPOSE_NSHost_IVARS 1
|
|
#import "Foundation/NSLock.h"
|
|
#import "Foundation/NSHost.h"
|
|
#import "Foundation/NSArray.h"
|
|
#import "Foundation/NSDictionary.h"
|
|
#import "Foundation/NSEnumerator.h"
|
|
#import "Foundation/NSNull.h"
|
|
#import "Foundation/NSSet.h"
|
|
#import "Foundation/NSCoder.h"
|
|
|
|
#if defined(__MINGW__)
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <netdb.h>
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#endif /* !__MINGW__*/
|
|
|
|
#ifndef INADDR_NONE
|
|
#define INADDR_NONE -1
|
|
#endif
|
|
|
|
static NSString *localHostName = @"GNUstep local host";
|
|
static Class hostClass;
|
|
static NSRecursiveLock *_hostCacheLock = nil;
|
|
static BOOL _hostCacheEnabled = YES;
|
|
static NSMutableDictionary *_hostCache = nil;
|
|
static id null = nil;
|
|
|
|
|
|
@interface NSHost (Private)
|
|
- (void) _addName: (NSString*)name;
|
|
#if defined(HAVE_GETADDRINFO)
|
|
- (id) _initWithAddrinfo: (struct addrinfo*)entry key: (NSString*)name;
|
|
#endif
|
|
- (id) _initWithHostEntry: (struct hostent*)entry key: (NSString*)name;
|
|
+ (NSMutableSet*) _localAddresses;
|
|
@end
|
|
|
|
@implementation NSHost (Private)
|
|
|
|
- (void) _addName: (NSString*)name
|
|
{
|
|
NSMutableSet *s = [_names mutableCopy];
|
|
|
|
name = [name copy];
|
|
[s addObject: name];
|
|
ASSIGNCOPY(_names, s);
|
|
RELEASE(s);
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
[_hostCache setObject: self forKey: name];
|
|
}
|
|
RELEASE(name);
|
|
}
|
|
|
|
- (id) _initWithAddress: (NSString*)name
|
|
{
|
|
if ((self = [super init]) == nil)
|
|
{
|
|
return nil;
|
|
}
|
|
name = [name copy];
|
|
_names = [[NSSet alloc] initWithObjects: &name count: 1];
|
|
_addresses = RETAIN(_names);
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
[_hostCache setObject: self forKey: name];
|
|
}
|
|
RELEASE(name);
|
|
return self;
|
|
}
|
|
|
|
#if defined(HAVE_GETADDRINFO)
|
|
- (id) _initWithAddrinfo: (struct addrinfo*)entry key: (NSString*)name
|
|
{
|
|
NSMutableSet *names;
|
|
NSMutableSet *addresses;
|
|
NSMutableSet *extra;
|
|
|
|
if ((self = [super init]) == nil)
|
|
{
|
|
return nil;
|
|
}
|
|
if ([name isEqualToString: localHostName] == NO
|
|
&& entry == (struct addrinfo*)NULL)
|
|
{
|
|
NSLog(@"Host '%@' init failed - perhaps the name/address is wrong or "
|
|
@"networking is not set up on your machine", name);
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
else if (name == nil && entry != (struct addrinfo*)NULL)
|
|
{
|
|
NSLog(@"Nil hostname supplied but network database entry is not empty");
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
|
|
names = [NSMutableSet new];
|
|
addresses = [NSMutableSet new];
|
|
|
|
if ([name isEqualToString: localHostName] == YES)
|
|
{
|
|
extra = [hostClass _localAddresses];
|
|
}
|
|
else
|
|
{
|
|
extra = nil;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
struct addrinfo *tmp;
|
|
|
|
/*
|
|
* We remove all the IP addresses that we have added to the host so
|
|
* far from the set of extra addresses available on the current host.
|
|
* Then we try to find a new network database entry for one of the
|
|
* remaining extra addresses, and loop round to add all the names
|
|
* and addresses for that entry.
|
|
*/
|
|
[extra minusSet: addresses];
|
|
while (entry == 0 && [extra count] > 0)
|
|
{
|
|
NSString *a = [extra anyObject];
|
|
struct addrinfo hints;
|
|
|
|
memset(&hints, '\0', sizeof(hints));
|
|
hints.ai_flags = AI_CANONNAME;
|
|
getaddrinfo([a UTF8String], 0, &hints, &entry);
|
|
if (0 == entry)
|
|
{
|
|
/*
|
|
* Can't find a database entry for this IP address, but since
|
|
* we know the address is valid, we add it to the list of
|
|
* addresses for this host anyway.
|
|
*/
|
|
[addresses addObject: a];
|
|
[extra removeObject: a];
|
|
}
|
|
}
|
|
if (0 == entry)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (tmp = entry; tmp != 0; tmp = tmp->ai_next)
|
|
{
|
|
if (tmp->ai_canonname && *tmp->ai_canonname)
|
|
{
|
|
NSString *n;
|
|
|
|
n = [[NSString alloc] initWithUTF8String: tmp->ai_canonname];
|
|
[names addObject: n];
|
|
[n release];
|
|
}
|
|
|
|
if (tmp->ai_addrlen > 0)
|
|
{
|
|
char host[NI_MAXHOST];
|
|
char port[NI_MAXSERV];
|
|
|
|
if (0 == getnameinfo(tmp->ai_addr, tmp->ai_addrlen,
|
|
host, NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICSERV))
|
|
{
|
|
NSString *a;
|
|
|
|
a = [[NSString alloc] initWithUTF8String: host];
|
|
[addresses addObject: a];
|
|
[a release];
|
|
}
|
|
}
|
|
}
|
|
freeaddrinfo(entry);
|
|
entry = 0;
|
|
}
|
|
|
|
_names = [names copy];
|
|
RELEASE(names);
|
|
_addresses = [addresses copy];
|
|
RELEASE(addresses);
|
|
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
[_hostCache setObject: self forKey: name];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
#endif
|
|
|
|
- (id) _initWithHostEntry: (struct hostent*)entry key: (NSString*)name
|
|
{
|
|
int i;
|
|
char *ptr;
|
|
struct in_addr in;
|
|
NSString *h_name;
|
|
NSMutableSet *names;
|
|
NSMutableSet *addresses;
|
|
NSMutableSet *extra;
|
|
|
|
if ((self = [super init]) == nil)
|
|
{
|
|
return nil;
|
|
}
|
|
if ([name isEqualToString: localHostName] == NO
|
|
&& entry == (struct hostent*)NULL)
|
|
{
|
|
NSLog(@"Host '%@' init failed - perhaps the name/address is wrong or "
|
|
@"networking is not set up on your machine", name);
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
else if (name == nil && entry != (struct hostent*)NULL)
|
|
{
|
|
NSLog(@"Nil hostname supplied but network database entry is not empty");
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
|
|
names = [NSMutableSet new];
|
|
addresses = [NSMutableSet new];
|
|
|
|
if ([name isEqualToString: localHostName] == YES)
|
|
{
|
|
extra = [hostClass _localAddresses];
|
|
}
|
|
else
|
|
{
|
|
extra = nil;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
/*
|
|
* We remove all the IP addresses that we have added to the host so
|
|
* far from the set of extra addresses available on the current host.
|
|
* Then we try to find a new network database entry for one of the
|
|
* remaining extra addresses, and loop round to add all the names
|
|
* and addresses for that entry.
|
|
*/
|
|
[extra minusSet: addresses];
|
|
while (entry == 0 && [extra count] > 0)
|
|
{
|
|
NSString *a = [extra anyObject];
|
|
|
|
entry = gethostbyname([a UTF8String]);
|
|
if (entry == 0)
|
|
{
|
|
/*
|
|
* Can't find a database entry for this IP address, but since
|
|
* we know the address is valid, we add it to the list of
|
|
* addresses for this host anyway.
|
|
*/
|
|
[addresses addObject: a];
|
|
[extra removeObject: a];
|
|
}
|
|
}
|
|
if (entry == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
h_name = [NSString stringWithUTF8String: entry->h_name];
|
|
[names addObject: h_name];
|
|
|
|
if (entry->h_aliases != 0)
|
|
{
|
|
i = 0;
|
|
while ((ptr = entry->h_aliases[i++]) != 0)
|
|
{
|
|
[names addObject: [NSString stringWithUTF8String: ptr]];
|
|
}
|
|
}
|
|
if (entry->h_addr_list != 0)
|
|
{
|
|
i = 0;
|
|
while ((ptr = entry->h_addr_list[i++]) != 0)
|
|
{
|
|
NSString *addr;
|
|
|
|
memset((void*)&in, '\0', sizeof(in));
|
|
memcpy((void*)&in.s_addr, (const void*)ptr, entry->h_length);
|
|
addr = [NSString stringWithUTF8String: (char*)inet_ntoa(in)];
|
|
[addresses addObject: addr];
|
|
}
|
|
}
|
|
entry = 0;
|
|
}
|
|
|
|
_names = [names copy];
|
|
RELEASE(names);
|
|
_addresses = [addresses copy];
|
|
RELEASE(addresses);
|
|
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
[_hostCache setObject: self forKey: name];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
+ (NSMutableSet*) _localAddresses
|
|
{
|
|
NSMutableSet *set;
|
|
|
|
set = [[self currentHost]->_addresses mutableCopy];
|
|
[set addObject: @"127.0.0.1"];
|
|
return AUTORELEASE(set);
|
|
}
|
|
@end
|
|
|
|
@implementation NSHost
|
|
|
|
/*
|
|
* Max hostname length in line with RFC 1123
|
|
*/
|
|
#define GSMAXHOSTNAMELEN 255
|
|
|
|
/**
|
|
* Return the current host name ... may change if we are using dhcp etc
|
|
*/
|
|
static NSString*
|
|
myHostName()
|
|
{
|
|
static NSString *name = nil;
|
|
static char old[GSMAXHOSTNAMELEN+1];
|
|
char buf[GSMAXHOSTNAMELEN+1];
|
|
int res;
|
|
|
|
[_hostCacheLock lock];
|
|
res = gethostname(buf, GSMAXHOSTNAMELEN);
|
|
if (res < 0 || *buf == '\0')
|
|
{
|
|
NSLog(@"Unable to get name of current host - using 'localhost'");
|
|
ASSIGN(name, @"localhost");
|
|
}
|
|
else if (name == nil || strcmp(old, buf) != 0)
|
|
{
|
|
strncpy(old, buf, sizeof(old) - 1);
|
|
old[sizeof(old) - 1] = '\0';
|
|
RELEASE(name);
|
|
name = [[NSString alloc] initWithCString: buf];
|
|
}
|
|
[_hostCacheLock unlock];
|
|
return name;
|
|
}
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (self == [NSHost class])
|
|
{
|
|
hostClass = self;
|
|
null = [[NSNull null] retain];
|
|
[[NSObject leakAt: &null] release];
|
|
_hostCacheLock = [[NSRecursiveLock alloc] init];
|
|
[[NSObject leakAt: &_hostCacheLock] release];
|
|
_hostCache = [NSMutableDictionary new];
|
|
[[NSObject leakAt: &_hostCache] release];
|
|
}
|
|
}
|
|
|
|
+ (NSHost*) currentHost
|
|
{
|
|
return [self hostWithName: myHostName()];
|
|
}
|
|
|
|
+ (NSHost*) hostWithName: (NSString*)name
|
|
{
|
|
NSHost *host = nil;
|
|
const char *n;
|
|
|
|
if (name == nil)
|
|
{
|
|
NSLog(@"Nil host name sent to [NSHost +hostWithName:]");
|
|
return nil;
|
|
}
|
|
if ([name isEqual: @""] == YES)
|
|
{
|
|
NSLog(@"Empty host name sent to [NSHost +hostWithName:]");
|
|
return nil;
|
|
}
|
|
|
|
/* If this looks like an address rather than a host name ...
|
|
* call the correct method instead of this one.
|
|
*/
|
|
n = [name UTF8String];
|
|
if ((isdigit(n[0]) && sscanf(n, "%*d.%*d.%*d.%*d") == 4)
|
|
|| 0 != strchr(n, ':'))
|
|
{
|
|
return [self hostWithAddress: name];
|
|
}
|
|
|
|
[_hostCacheLock lock];
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
host = [_hostCache objectForKey: name];
|
|
}
|
|
if (host == nil)
|
|
{
|
|
if ([name isEqualToString: localHostName] == YES)
|
|
{
|
|
/*
|
|
* Special GNUstep extension host - we try to have a host entry
|
|
* with ALL the IP addresses of any interfaces on the local machine
|
|
*/
|
|
host = [[self alloc] _initWithHostEntry: 0 key: localHostName];
|
|
IF_NO_GC([host autorelease];)
|
|
}
|
|
else
|
|
{
|
|
struct hostent *h;
|
|
|
|
h = gethostbyname((char*)n);
|
|
if (0 == h)
|
|
{
|
|
if ([name isEqualToString: myHostName()] == YES)
|
|
{
|
|
host = [self hostWithAddress: @"127.0.0.1"];
|
|
[host _addName: name];
|
|
}
|
|
else
|
|
{
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
[_hostCache setObject: null forKey: name];
|
|
}
|
|
NSLog(@"Host '%@' not found using 'gethostbyname()' - "
|
|
@"perhaps the hostname is wrong or networking is not "
|
|
@"set up on your machine", name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
host = [[self alloc] _initWithHostEntry: h key: name];
|
|
IF_NO_GC([host autorelease];)
|
|
}
|
|
}
|
|
}
|
|
else if ((id)host == null)
|
|
{
|
|
host = nil;
|
|
}
|
|
else
|
|
{
|
|
IF_NO_GC([[host retain] autorelease];)
|
|
}
|
|
[_hostCacheLock unlock];
|
|
return host;
|
|
}
|
|
|
|
+ (NSHost*) hostWithAddress: (NSString*)address
|
|
{
|
|
NSHost *host = nil;
|
|
char buf[40];
|
|
const char *a;
|
|
|
|
if (address == nil)
|
|
{
|
|
NSLog(@"Nil host address sent to [NSHost +hostWithAddress:]");
|
|
return nil;
|
|
}
|
|
a = [address UTF8String];
|
|
if (0 == a || '\0' == *a)
|
|
{
|
|
NSLog(@"Empty host address sent to [NSHost +hostWithAddress:]");
|
|
return nil;
|
|
}
|
|
|
|
/* Now check that the address is of valid format, and standardise it
|
|
* by converting from characters to binary and back.
|
|
*/
|
|
if (0 == strchr(a, ':'))
|
|
{
|
|
struct in_addr hostaddr;
|
|
|
|
if (inet_pton(AF_INET, a, (void*)&hostaddr) <= 0)
|
|
{
|
|
NSLog(@"Invalid host address sent to [NSHost +hostWithAddress:]");
|
|
return nil;
|
|
}
|
|
inet_ntop(AF_INET, (void*)&hostaddr, buf, sizeof(buf));
|
|
a = buf;
|
|
address = [NSString stringWithUTF8String: a];
|
|
}
|
|
else
|
|
#if defined(AF_INET6)
|
|
{
|
|
struct in6_addr hostaddr6;
|
|
|
|
if (inet_pton(AF_INET6, a, (void*)&hostaddr6) <= 0)
|
|
{
|
|
NSLog(@"Invalid host address sent to [NSHost +hostWithAddress:]");
|
|
return nil;
|
|
}
|
|
inet_ntop(AF_INET6, (void*)&hostaddr6, buf, sizeof(buf));
|
|
a = buf;
|
|
address = [NSString stringWithUTF8String: a];
|
|
}
|
|
#else
|
|
NSLog(@"Unsupported host address sent to [NSHost +hostWithAddress:]");
|
|
return nil;
|
|
#endif
|
|
|
|
[_hostCacheLock lock];
|
|
if (YES == _hostCacheEnabled)
|
|
{
|
|
host = [_hostCache objectForKey: address];
|
|
}
|
|
if (nil == host)
|
|
{
|
|
struct hostent *h;
|
|
|
|
/* The gethostbyname() function should handle names, ipv4 addresses,
|
|
* and ipv6 addresses ... so we can use it whatever we have.
|
|
*/
|
|
h = gethostbyname(a);
|
|
if (0 == h)
|
|
{
|
|
host = [[self alloc] _initWithAddress: address];
|
|
IF_NO_GC([host autorelease];)
|
|
}
|
|
else
|
|
{
|
|
host = [[self alloc] _initWithHostEntry: h key: address];
|
|
IF_NO_GC([host autorelease];)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_NO_GC([[host retain] autorelease];)
|
|
}
|
|
[_hostCacheLock unlock];
|
|
return host;
|
|
}
|
|
|
|
+ (void) setHostCacheEnabled: (BOOL)flag
|
|
{
|
|
[_hostCacheLock lock];
|
|
_hostCacheEnabled = flag;
|
|
[_hostCacheLock unlock];
|
|
}
|
|
|
|
+ (BOOL) isHostCacheEnabled
|
|
{
|
|
return _hostCacheEnabled;
|
|
}
|
|
|
|
+ (void) flushHostCache
|
|
{
|
|
[_hostCacheLock lock];
|
|
[_hostCache removeAllObjects];
|
|
[_hostCacheLock unlock];
|
|
}
|
|
|
|
/* Methods for encoding/decoding*/
|
|
|
|
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
|
|
{
|
|
return self;
|
|
}
|
|
|
|
- (void) encodeWithCoder: (NSCoder*)aCoder
|
|
{
|
|
NSString *address = [self address];
|
|
|
|
if ([address isEqual: @"127.0.0.1"] == YES)
|
|
{
|
|
NSEnumerator *e = [_addresses objectEnumerator];
|
|
|
|
while ((address = [e nextObject]) != nil)
|
|
{
|
|
if ([address isEqual: @"127.0.0.1"] == NO)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
[aCoder encodeObject: address];
|
|
}
|
|
|
|
- (id) initWithCoder: (NSCoder*)aCoder
|
|
{
|
|
NSString *address;
|
|
NSHost *host;
|
|
|
|
address = [aCoder decodeObject];
|
|
if (address != nil)
|
|
{
|
|
host = [NSHost hostWithAddress: address];
|
|
}
|
|
else
|
|
{
|
|
host = [NSHost currentHost];
|
|
}
|
|
IF_NO_GC([host retain];)
|
|
DESTROY(self);
|
|
return host;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
RELEASE(_names);
|
|
RELEASE(_addresses);
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id) init
|
|
{
|
|
[self dealloc];
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* The OpenStep spec says that [-hash] must be the same for any two
|
|
* objects that [-isEqual: ] returns YES for. We have a problem in
|
|
* that [-isEqualToHost: ] is specified to return YES if any name or
|
|
* address part of two hosts is the same. That means we can't
|
|
* reasonably calculate a hash since two hosts with radically
|
|
* different ivar contents may be 'equal'. The best I can think of
|
|
* is for all hosts to hash to the same value - which makes it very
|
|
* inefficient to store them in a set, dictionary, map or hash table.
|
|
*/
|
|
- (NSUInteger) hash
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
{
|
|
if (other == self)
|
|
{
|
|
return YES;
|
|
}
|
|
if ([other isKindOfClass: [NSHost class]])
|
|
{
|
|
return [self isEqualToHost: (NSHost*)other];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL) isEqualToHost: (NSHost*)aHost
|
|
{
|
|
NSEnumerator *e;
|
|
NSString *a;
|
|
|
|
if (aHost == self)
|
|
{
|
|
return YES;
|
|
}
|
|
e = [aHost->_addresses objectEnumerator];
|
|
while ((a = [e nextObject]) != nil)
|
|
{
|
|
if ([_addresses member: a] != nil)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSString*) localizedName
|
|
{
|
|
NSString *n = myHostName();
|
|
|
|
if (self != [NSHost hostWithName: n])
|
|
{
|
|
n = nil;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
- (NSString*) name
|
|
{
|
|
return [_names anyObject];
|
|
}
|
|
|
|
- (NSArray*) names
|
|
{
|
|
return [_names allObjects];
|
|
}
|
|
|
|
- (NSString*) address
|
|
{
|
|
return [_addresses anyObject];
|
|
}
|
|
|
|
- (NSArray*) addresses
|
|
{
|
|
return [_addresses allObjects];
|
|
}
|
|
|
|
- (NSString*) description
|
|
{
|
|
return [NSString stringWithFormat: @"Host %@ (%@ %@)",
|
|
[self name], _names, _addresses];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSHost (GNUstep)
|
|
+ (NSHost*) localHost
|
|
{
|
|
return [self hostWithName: localHostName];
|
|
}
|
|
@end
|
|
|
|
@implementation NSHost (NSProcessInfo)
|
|
+ (NSString*) _myHostName
|
|
{
|
|
return myHostName();
|
|
}
|
|
@end
|