1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "common.h"
23
24#if HAVE_SYS_TYPES_H || defined(__APPLE__)
25#  include <sys/types.h>
26#endif
27#if HAVE_NETINET_IN_H
28#  include <netinet/in.h>
29#endif
30#if HAVE_UNISTD_H || defined(__APPLE__)
31#  include <unistd.h>
32#endif
33#if defined(__APPLE__)
34#  include <netdb.h>
35#endif
36
37#if !defined(__CYGWIN32__)
38#  if HAVE_WINDOWS_H
39#    include <windows.h>
40#  endif
41#  if HAVE_WINSOCK_H
42#    include <winsock.h>
43#  endif
44#endif
45
46#include "NGSocketExceptions.h"
47#include "NGInternetSocketAddress.h"
48#include "NGInternetSocketDomain.h"
49#include "common.h"
50
51#if defined(HAVE_GETHOSTBYNAME_R) && !defined(linux) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__GLIBC__)
52#define USE_GETHOSTBYNAME_R 1
53#endif
54
55@implementation NGInternetSocketAddress
56
57#if LIB_FOUNDATION_LIBRARY
58extern NSRecursiveLock *libFoundationLock;
59#define systemLock libFoundationLock
60#else
61static NSRecursiveLock *systemLock = nil;
62#endif
63
64static NSMapTable *nameCache = NULL;
65
66+ (void)initialize {
67  [NGSocket initialize];
68
69  if (nameCache == NULL) {
70    nameCache = NSCreateMapTable(NSIntMapKeyCallBacks,
71                                 NSObjectMapValueCallBacks,
72                                 128);
73  }
74
75#if !LIB_FOUNDATION_LIBRARY
76  [[NSNotificationCenter defaultCenter]
77                         addObserver:self selector:@selector(taskNowMultiThreaded:)
78                         name:NSWillBecomeMultiThreadedNotification
79                         object:nil];
80#endif
81}
82
83+ (void)taskNowMultiThreaded:(NSNotification *)_notification {
84  if (systemLock == nil) systemLock = [[NSRecursiveLock alloc] init];
85}
86
87static inline NSString *_nameOfLocalhost(void) {
88#if 1
89  return [[NSHost currentHost] name];
90#else
91  NSString *hostName = nil;
92
93  [systemLock lock];
94  {
95    char buffer[1024];
96    gethostname(buffer, sizeof(buffer));
97    hostName = [[NSString alloc] initWithCString:buffer];
98  }
99  [systemLock unlock];
100
101  return [hostName autorelease];
102#endif
103}
104
105- (void)_fillHost {
106  /*
107    Fill up the host and port ivars based on the INET address.
108
109    TODO: cache some information, takes quite some time (11% of execution
110    time on MacOSX proftest) to get the hostname of an address.
111  */
112  struct hostent *hostEntity = NULL; // only valid during lock
113  NSString       *newHost  = nil;
114  int            errorCode = 0;
115  struct sockaddr_in *sockAddr = self->address;
116
117  if (self->isHostFilled)
118    /* host is already filled .. */
119    return;
120
121#if DEBUG
122  NSAssert(self->isAddressFilled, @"either host or address must be filled ...");
123#endif
124
125  if (sockAddr->sin_addr.s_addr != 0) { // not a wildcard address
126#if !defined(HAVE_GETHOSTBYADDR_R)
127    [systemLock lock];
128    newHost = NSMapGet(nameCache,
129		       (void *)(unsigned long)sockAddr->sin_addr.s_addr);
130#else
131    [systemLock lock];
132    newHost = NSMapGet(nameCache,
133		       (void *)(unsigned long)sockAddr->sin_addr.s_addr);
134    [systemLock unlock];
135#endif
136    if (newHost == nil) {
137      BOOL done = NO;
138
139      while (!done) {
140#if USE_GETHOSTBYNAME_R
141        struct hostent hostEntityBuffer;
142        char buffer[8200];
143
144        hostEntity = gethostbyaddr_r((char *)&(sockAddr->sin_addr.s_addr),
145                                     4,
146                                     [[self domain] socketDomain],
147                                     &hostEntityBuffer,
148                                     buffer, 8200,
149                                     &errorCode);
150#else
151# ifdef __MINGW32__
152#   warning "doesn't resolve host name on mingw32 !"
153	hostEntity = NULL;
154	errorCode  = -1;
155# else
156        hostEntity = gethostbyaddr((char *)&(sockAddr->sin_addr.s_addr),
157                                   4,
158                                   [[self domain] socketDomain]);
159#  if defined(WIN32) && !defined(__CYGWIN32__)
160        errorCode = WSAGetLastError();
161#  else
162        errorCode = h_errno;
163#  endif
164# endif
165#endif
166        if (hostEntity == NULL) {
167          done = YES;
168
169          switch (errorCode) {
170#ifdef __MINGW32__
171	    case -1:
172	      break;
173#endif
174            case HOST_NOT_FOUND:
175              NSLog(@"%s: host not found ..", __PRETTY_FUNCTION__);
176              break;
177
178            case TRY_AGAIN:
179#ifndef __linux
180              NSLog(@"%s:\n  couldn't lookup host, retry ..",
181                    __PRETTY_FUNCTION__);
182              done = NO;
183#else
184              NSLog(@"%s: couldn't lookup host ..", __PRETTY_FUNCTION__);
185#endif
186              break;
187
188            case NO_RECOVERY:
189              NSLog(@"%s: no recovery", __PRETTY_FUNCTION__);
190              break;
191
192            case NO_DATA:
193              NSLog(@"%s: no data", __PRETTY_FUNCTION__);
194              break;
195
196            default:
197              NSLog(@"%s: unknown error: h_errno=%i errno=%s",
198                    __PRETTY_FUNCTION__,
199                    errorCode, strerror(errno));
200              break;
201          }
202
203          newHost = [NSString stringWithCString:inet_ntoa(sockAddr->sin_addr)];
204        }
205        else {
206          newHost = [NSString stringWithCString:hostEntity->h_name];
207          done = YES;
208        }
209      }
210
211      if (hostEntity == NULL) {
212        // throw could not get address ..
213        NSLog(@"could not get DNS name of address %@ in domain %@: %i",
214              newHost, [self domain], errorCode);
215      }
216      else if (newHost) {
217        /* add to cache */
218        NSMapInsert(nameCache,
219		    (void *)(unsigned long)sockAddr->sin_addr.s_addr, newHost);
220      }
221      /* TODO: should also cache unknown IPs ! */
222    }
223
224    //else printf("%s: CACHE HIT !\n", __PRETTY_FUNCTION__);
225
226#if !defined(HAVE_GETHOSTBYADDR_R)
227    [systemLock unlock];
228#endif
229  }
230  else {
231    /* wildcard address */
232    newHost = nil;
233  }
234
235  ASSIGNCOPY(self->hostName, newHost);
236  self->isHostFilled = YES;
237}
238
239- (NSException *)_fillAddress {
240  /*
241    Fill up the INET address based on the host and port ivars.
242  */
243  // throws
244  //   NGCouldNotResolveHostNameException  when a DNS lookup fails
245
246#if defined(WIN32) && !defined(__CYGWIN32__)
247  u_long *ia = &(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
248#else
249  unsigned int *ia = &(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
250#endif
251
252  if (self->isAddressFilled)
253    /* address is already filled .. */
254    return nil;
255
256#if DEBUG
257  NSAssert(self->isHostFilled, @"either host or address must be filled ...");
258#endif
259
260  if (self->hostName == nil) {
261    //  if ([self isWildcardAddress])
262    *ia = htonl(INADDR_ANY); // wildcard (0)
263    self->isAddressFilled = YES;
264  }
265  else {
266    const unsigned char *chost;
267
268    chost = (unsigned char *)[[self hostName] cString];
269
270    // try to interpret hostname as INET dotted address (eg 122.133.44.87)
271    *ia = inet_addr((char *)chost);
272
273    if ((int)*ia != -1) { // succeeded
274      self->isAddressFilled = YES;
275    }
276    else { // failed, try to interpret hostname as DNS hostname
277      BOOL didFail   = NO;
278      int  errorCode = 0;
279      int  addrType  = AF_INET;
280#if defined(USE_GETHOSTBYNAME_R)
281      char buffer[4096];
282      struct hostent hostEntity;
283#else
284      struct hostent *hostEntity; // only valid during lock
285#endif
286
287#if defined(USE_GETHOSTBYNAME_R)
288      if (gethostbyname_r(chost, &hostEntity,
289                          buffer, sizeof(buffer), &errorCode) == NULL) {
290        didFail = YES;
291      }
292      else {
293        addrType = hostEntity.h_addrtype;
294
295        if (addrType == AF_INET)
296          *ia = ((struct in_addr *)(hostEntity.h_addr_list[0]))->s_addr;
297        else
298          didFail = YES; // invalid domain (eg AF_INET6)
299      }
300#else
301      [systemLock lock];
302      {
303        if ((hostEntity = gethostbyname((char *)chost)) == NULL) {
304          didFail = YES;
305#if defined(WIN32) && !defined(__CYGWIN32__)
306          errorCode = WSAGetLastError();
307#else
308          errorCode = h_errno;
309#endif
310        }
311        else {
312          addrType = hostEntity->h_addrtype;
313
314          if (addrType == AF_INET)
315            *ia = ((struct in_addr *)(hostEntity->h_addr_list[0]))->s_addr;
316          else
317            didFail = YES; // invalid domain (eg AF_INET6)
318        }
319      }
320      [systemLock unlock];
321#endif
322
323      if (didFail) { // could not resolve hostname
324        // did not find host
325        NSString *reason = nil;
326
327        if (addrType != AF_INET) {
328          // invalid domain (eg AF_INET6)
329          reason = @"resolved address is in invalid domain";
330        }
331        else {
332          switch (errorCode) {
333            case HOST_NOT_FOUND: reason = @"host not found"; break;
334            case TRY_AGAIN:      reason = @"try again";      break;
335            case NO_RECOVERY:    reason = @"no recovery";    break;
336            case NO_DATA:        reason = @"no address available"; break;
337            default:
338              reason = [NSString stringWithFormat:@"error code %i", errorCode];
339              break;
340          }
341        }
342        return [[[NGCouldNotResolveHostNameException alloc]
343		  initWithHostName:[self hostName] reason:reason] autorelease];
344      }
345
346      self->isAddressFilled = YES;
347    }
348  }
349  return nil;
350}
351
352/* constructors */
353
354+ (id)addressWithPort:(int)_port onHost:(id)_host {
355  return [[[self alloc] initWithPort:_port onHost:_host] autorelease];
356}
357+ (id)addressWithPort:(int)_port {
358  return [[[self alloc] initWithPort:_port] autorelease];
359}
360
361+ (id)addressWithService:(NSString *)_sname onHost:(id)_host
362  protocol:(NSString *)_protocol
363{
364  return [[[self alloc] initWithService:_sname
365                        onHost:_host
366                        protocol:_protocol]
367                        autorelease];
368}
369+ (id)addressWithService:(NSString *)_sname protocol:(NSString *)_protocol {
370  return [[[self alloc] initWithService:_sname protocol:_protocol] autorelease];
371}
372
373+ (id)wildcardAddress {
374  return [[[self alloc] initWithPort:0 onHost:@"*"] autorelease];
375}
376+ (id)wildcardAddressWithPort:(int)_port {
377  return [[[self alloc] initWithPort:_port onHost:@"*"] autorelease];
378}
379
380- (id)init {
381  if ((self = [super init])) {
382    self->address = malloc(sizeof(struct sockaddr_in));
383  }
384  return self;
385}
386
387- (id)initWithPort:(int)_port onHost:(id)_host { /* designated initializer */
388  if ((self = [self init])) {
389    self->isAddressFilled = NO;
390    self->isHostFilled    = YES;
391
392    if (_host != nil) {
393      if ([_host isKindOfClass:[NSHost class]])
394        _host = [(NSHost *)_host address];
395
396      if ([_host isEqualToString:@"*"]) {
397        self->hostName = nil; /* wildcard host */
398      }
399      else {
400        self->hostName = [_host copy];
401        self->isWildcardHost = NO;
402      }
403    }
404    else {
405      /* wildcard host */
406      self->isWildcardHost = YES;
407    }
408
409    ((struct sockaddr_in *)self->address)->sin_family =
410      [[self domain] socketDomain];
411    ((struct sockaddr_in *)self->address)->sin_port =
412      htons((short)(_port & 0xffff));
413  }
414  return self;
415}
416
417- (id)initWithService:(NSString *)_serviceName onHost:(id)_host
418  protocol:(NSString *)_protocol
419{
420  /* careful: the port in servent is in network byteorder! */
421  NSException *exc = nil;
422  int port = -1;
423#if defined(HAVE_GETSERVBYNAME_R)
424  char   buffer[2048];
425  struct servent entry;
426#else
427  struct servent *entry;
428#endif
429
430#if defined(HAVE_GETSERVBYNAME_R)
431  if (getservbyname_r((char *)[_serviceName cString], [_protocol cString],
432                      &entry, buffer, sizeof(buffer)) == NULL) {
433    exc = [[NGDidNotFindServiceException alloc] initWithServiceName:_serviceName];
434  }
435  else
436    port = entry.s_port;
437#else
438  [systemLock lock];
439  {
440    entry = getservbyname((char *)[_serviceName cString], [_protocol cString]);
441    if (entry == NULL) {
442      exc = [[NGDidNotFindServiceException alloc]
443	      initWithServiceName:_serviceName];
444    }
445    else
446      port = entry->s_port;
447  }
448  [systemLock unlock];
449#endif
450
451  if (exc != nil) {
452    self = [self autorelease];
453    [exc raise];
454    return nil;
455  }
456  return [self initWithPort:ntohs(port) onHost:_host];
457}
458
459- (id)initWithPort:(int)_port {
460  return [self initWithPort:_port onHost:_nameOfLocalhost()];
461}
462
463- (id)initWithService:(NSString *)_serviceName protocol:(NSString *)_protocol {
464  return [self initWithService:_serviceName
465               onHost:_nameOfLocalhost()
466               protocol:_protocol];
467}
468
469- (id)initWithDomain:(id)_domain
470  internalRepresentation:(void *)_representation
471  size:(int)_length
472{
473  struct sockaddr_in *sockAddr = _representation;
474#if DEBUG
475  NSAssert(_length == sizeof(struct sockaddr_in),
476           @"invalid socket address length");
477#else
478  if (_length != sizeof(struct sockaddr_in)) {
479    NSLog(@"%s: got invalid sockaddr_in size ...", __PRETTY_FUNCTION__);
480    [self release];
481    return nil;
482  }
483#endif
484
485  if ((self = [self init]) == nil)
486    return nil;
487
488  self->isHostFilled = NO; /* need to lookup DNS */
489
490  /* fill address */
491
492  self->isAddressFilled = YES;
493  memcpy(self->address, _representation, sizeof(struct sockaddr_in));
494
495  if (sockAddr->sin_addr.s_addr != 0) {
496    /* not a wildcard address */
497    self->isWildcardHost = NO;
498  }
499  else {
500    /* wildcard address */
501    self->hostName       = nil;
502    self->isWildcardHost = YES;
503    self->isHostFilled   = YES; /* wildcard host, no DNS lookup ... */
504  }
505
506  return self;
507}
508
509- (void)dealloc {
510  [self->hostName release];
511  if (self->address) free(self->address);
512  [super dealloc];
513}
514
515/* accessors */
516
517- (NSString *)hostName {
518  if (!self->isHostFilled) [self _fillHost];
519  return [[self->hostName copy] autorelease];
520}
521
522- (BOOL) _isLoopback {
523  if (!self->isAddressFilled)
524    [[self _fillAddress] raise];
525#if defined(WIN32) && !defined(__CYGWIN32__)
526  u_long *ia = &(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
527#else
528  unsigned int *ia = &(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
529#endif
530  return ((((long int) (ntohl(*ia))) & 0xff000000) == 0x7f000000);
531}
532
533- (NSString *)address {
534#if defined(WIN32) && !defined(__CYGWIN32__)
535  u_long *ia;
536  ia = (u_long *)&(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
537#else
538  unsigned long ia;
539  ia = (unsigned long)
540    &(((struct sockaddr_in *)self->address)->sin_addr.s_addr);
541#endif
542
543  if (self->hostName == nil) /* wildcard */
544    return nil;
545
546  if (!self->isAddressFilled)
547    [[self _fillAddress] raise];
548
549  {
550    char     *ptr = NULL;
551    NSString *str = nil;
552
553    [systemLock lock];
554    {
555      ptr = inet_ntoa(*((struct in_addr *)ia));
556      str = [NSString stringWithCString:ptr];
557    }
558    [systemLock unlock];
559
560    return str;
561  }
562}
563
564- (int)port {
565  /* how to do ? */
566  if (!self->isAddressFilled)
567    [[self _fillAddress] raise];
568  return ntohs(((struct sockaddr_in *)self->address)->sin_port);
569}
570
571- (BOOL)isWildcardAddress {
572  if (self->isWildcardHost) return YES;
573  return ([self hostName] == nil) || ([self port] == 0);
574}
575
576/* NGSocketAddress protocol */
577
578- (void *)internalAddressRepresentation {
579  // throws
580  //   NGCouldNotResolveHostNameException  when a DNS lookup fails
581
582  if (!self->isAddressFilled)
583    [[self _fillAddress] raise];
584
585  return self->address;
586}
587
588- (int)addressRepresentationSize {
589  return [[self domain] addressRepresentationSize];
590}
591- (id)domain {
592  static id domain = nil;
593  if (domain == nil) domain = [[NGInternetSocketDomain domain] retain];
594  return domain;
595}
596
597- (BOOL) isLocalhost {
598  NSString *normalized_hostname;
599
600  if (![self hostName])
601    return NO;
602
603  if ([self _isLoopback])
604    return YES;
605  // normalize the string
606  normalized_hostname = [[self hostName] lowercaseString];
607
608  if ([normalized_hostname hasSuffix: @"."]) {
609    normalized_hostname = [normalized_hostname substringToIndex: [normalized_hostname length] - 1];
610  }
611
612  if ([normalized_hostname isEqualToString: @"localhost6"] ||
613      [normalized_hostname isEqualToString: @"localhost6.localdomain6"]) {
614    return YES;
615  }
616  if ([normalized_hostname isEqualToString: @"localhost"] ||
617      [normalized_hostname isEqualToString: @"localhost.localdomain"] ||
618      [normalized_hostname hasSuffix: @".localhost"]) {
619    return YES;
620  }
621  return NO;
622}
623
624/* comparing */
625
626- (NSUInteger)hash {
627  return [self port];
628}
629
630- (BOOL)isEqualToAddress:(NGInternetSocketAddress *)_otherAddress {
631  if (self == _otherAddress)
632    return YES;
633  if (![[_otherAddress hostName] isEqualToString:[self hostName]])
634    return NO;
635  if ([_otherAddress port] != [self port])
636    return NO;
637  return YES;
638}
639
640- (BOOL)isEqual:(id)_object {
641  if (_object == self) return YES;
642  if ([_object class] != [self class]) return NO;
643  return [self isEqualToAddress:_object];
644}
645
646/* NSCopying */
647
648- (id)copyWithZone:(NSZone *)_zone {
649  // socket addresses are immutable, therefore just retain self
650  return [self retain];
651}
652
653/* NSCoding */
654
655- (void)encodeWithCoder:(NSCoder *)_encoder {
656  int aPort = [self port];
657
658  [_encoder encodeValueOfObjCType:@encode(int) at:&aPort];
659  [_encoder encodeObject:[self hostName]];
660}
661- (id)initWithCoder:(NSCoder *)_decoder {
662  int aPort;
663  id  aHost;
664
665  [_decoder decodeValueOfObjCType:@encode(int) at:&aPort];
666  aHost = [_decoder decodeObject];
667
668  return [self initWithPort:aPort onHost:aHost];
669}
670
671/* description */
672
673- (NSString *)stringValue {
674  NSString *name;
675
676  if ((name = [self hostName]) == nil)
677    name = @"*";
678
679  return [NSString stringWithFormat:@"%@:%i", name, [self port]];
680}
681
682- (NSString *)description {
683  NSMutableString *ms;
684  id tmp;
685
686  ms = [NSMutableString stringWithCapacity:128];
687  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
688
689  if ((tmp = [self hostName]) != nil)
690    [ms appendFormat:@" host=%@", tmp];
691  else
692    [ms appendString:@" *host"];
693
694  if (!self->isAddressFilled)
695    [ms appendString:@" not-filled"];
696  else
697    [ms appendFormat:@" port=%d", [self port]];
698
699  [ms appendString:@">"];
700  return ms;
701}
702
703@end /* NGInternetSocketAddress */
704
705@implementation NGActiveSocket(NGInternetActiveSocket)
706
707+ (id)socketConnectedToPort:(int)_port onHost:(id)_host {
708  // this method calls +socketConnectedToAddress: with an
709  // NGInternetSocketAddress
710
711  return [self socketConnectedToAddress:
712                 [NGInternetSocketAddress addressWithPort:_port onHost:_host]];
713}
714
715- (BOOL)connectToPort:(int)_port onHost:(id)_host {
716  // this method calls -connectToAddress: with an NGInternetSocketAddress
717
718  return [self connectToAddress:
719                 [NGInternetSocketAddress addressWithPort:_port onHost:_host]];
720}
721
722@end /* NGActiveSocket(NGInternetActiveSocket) */
723