| jens@0 |      1 | //
 | 
| jens@0 |      2 | //  TCPListener.m
 | 
| jens@0 |      3 | //  MYNetwork
 | 
| jens@0 |      4 | //
 | 
| jens@0 |      5 | //  Created by Jens Alfke on 5/10/08.
 | 
| jens@0 |      6 | //  Copyright 2008 Jens Alfke. All rights reserved.
 | 
| jens@0 |      7 | //  Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
 | 
| jens@0 |      8 | 
 | 
| jens@0 |      9 | #import "TCPListener.h"
 | 
| jens@0 |     10 | #import "TCPConnection.h"
 | 
| jens@0 |     11 | 
 | 
| jens@1 |     12 | #import "Logging.h"
 | 
| jens@1 |     13 | #import "Test.h"
 | 
| jens@0 |     14 | #import "ExceptionUtils.h"
 | 
| jens@0 |     15 | #import "IPAddress.h"
 | 
| jens@1 |     16 | 
 | 
| jens@0 |     17 | #include <sys/socket.h>
 | 
| jens@0 |     18 | #include <netinet/in.h>
 | 
| jens@0 |     19 | #include <unistd.h>
 | 
| jens@0 |     20 | 
 | 
| jens@0 |     21 | 
 | 
| jens@0 |     22 | static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, 
 | 
| jens@0 |     23 |                                       CFDataRef address, const void *data, void *info);
 | 
| jens@0 |     24 | 
 | 
| jens@0 |     25 | @interface TCPListener()
 | 
| jens@22 |     26 | - (void) _openBonjour;
 | 
| jens@22 |     27 | - (void) _closeBonjour;
 | 
| jens@0 |     28 | @property BOOL bonjourPublished;
 | 
| jens@0 |     29 | @property NSInteger bonjourError;
 | 
| jens@0 |     30 | - (void) _updateTXTRecord;
 | 
| jens@0 |     31 | @end
 | 
| jens@0 |     32 | 
 | 
| jens@0 |     33 | 
 | 
| jens@0 |     34 | @implementation TCPListener
 | 
| jens@0 |     35 | 
 | 
| jens@0 |     36 | 
 | 
| jens@0 |     37 | - (id) initWithPort: (UInt16)port
 | 
| jens@0 |     38 | {
 | 
| jens@0 |     39 |     self = [super init];
 | 
| jens@0 |     40 |     if (self != nil) {
 | 
| jens@0 |     41 |         _port = port;
 | 
| jens@0 |     42 |     }
 | 
| jens@0 |     43 |     return self;
 | 
| jens@0 |     44 | }
 | 
| jens@0 |     45 | 
 | 
| jens@0 |     46 | 
 | 
| jens@0 |     47 | - (void) dealloc 
 | 
| jens@0 |     48 | {
 | 
| jens@0 |     49 |     [self close];
 | 
| jens@0 |     50 |     LogTo(TCP,@"DEALLOC %@",self);
 | 
| jens@0 |     51 |     [super dealloc];
 | 
| jens@0 |     52 | }
 | 
| jens@0 |     53 | 
 | 
| jens@0 |     54 | 
 | 
| jens@0 |     55 | @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
 | 
| jens@22 |     56 |             bonjourServiceType=_bonjourServiceType,
 | 
| jens@0 |     57 |             bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
 | 
| jens@22 |     58 |             bonjourService=_netService,
 | 
| jens@0 |     59 |             pickAvailablePort=_pickAvailablePort;
 | 
| jens@0 |     60 | 
 | 
| jens@0 |     61 | 
 | 
| jens@0 |     62 | - (NSString*) description
 | 
| jens@0 |     63 | {
 | 
| jens@0 |     64 |     return $sprintf(@"%@[port %hu]",self.class,_port);
 | 
| jens@0 |     65 | }
 | 
| jens@0 |     66 | 
 | 
| jens@0 |     67 | 
 | 
| jens@22 |     68 | // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
 | 
| jens@0 |     69 | static void* getLastCFSocketError( NSError **outError ) {
 | 
| jens@0 |     70 |     if( outError )
 | 
| jens@0 |     71 |         *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
 | 
| jens@0 |     72 |     return NULL;
 | 
| jens@0 |     73 | }
 | 
| jens@0 |     74 | 
 | 
| jens@0 |     75 | // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
 | 
| jens@0 |     76 | static CFSocketRef closeSocket( CFSocketRef socket ) {
 | 
| jens@0 |     77 |     if( socket ) {
 | 
| jens@0 |     78 |         CFSocketInvalidate(socket);
 | 
| jens@0 |     79 |         CFRelease(socket);
 | 
| jens@0 |     80 |     }
 | 
| jens@0 |     81 |     return NULL;
 | 
| jens@0 |     82 | }
 | 
| jens@0 |     83 | 
 | 
| jens@0 |     84 | // opens a socket of a given protocol, either ipv4 or ipv6.
 | 
| jens@0 |     85 | - (CFSocketRef) _openProtocol: (SInt32) protocolFamily 
 | 
| jens@0 |     86 |                       address: (struct sockaddr*)address
 | 
| jens@0 |     87 |                         error: (NSError**)error
 | 
| jens@0 |     88 | {
 | 
| jens@0 |     89 |     CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
 | 
| jens@0 |     90 |     CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
 | 
| jens@0 |     91 |                                         kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
 | 
| jens@0 |     92 |     if( ! socket ) 
 | 
| jens@0 |     93 |         return getLastCFSocketError(error);   // CFSocketCreate leaves error code in errno
 | 
| jens@0 |     94 |     
 | 
| jens@0 |     95 |     int yes = 1;
 | 
| jens@0 |     96 |     setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
 | 
| jens@0 |     97 |     
 | 
| jens@0 |     98 |     NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
 | 
| jens@0 |     99 |     if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
 | 
| jens@0 |    100 |         getLastCFSocketError(error);
 | 
| jens@0 |    101 |         return closeSocket(socket);
 | 
| jens@0 |    102 |     }
 | 
| jens@0 |    103 |     // set up the run loop source for the socket
 | 
| jens@0 |    104 |     CFRunLoopRef cfrl = CFRunLoopGetCurrent();
 | 
| jens@0 |    105 |     CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
 | 
| jens@0 |    106 |     CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
 | 
| jens@0 |    107 |     CFRelease(source);
 | 
| jens@0 |    108 |     return socket;
 | 
| jens@0 |    109 | }
 | 
| jens@0 |    110 | 
 | 
| jens@0 |    111 | - (BOOL) _failedToOpen: (NSError*)error
 | 
| jens@0 |    112 | {
 | 
| jens@0 |    113 |     LogTo(TCP,@"%@ failed to open: %@",self,error);
 | 
| jens@0 |    114 |     [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
 | 
| jens@0 |    115 |     return NO;
 | 
| jens@0 |    116 | }
 | 
| jens@0 |    117 | 
 | 
| jens@0 |    118 | 
 | 
| jens@0 |    119 | - (BOOL) open: (NSError**)outError 
 | 
| jens@0 |    120 | {
 | 
| jens@0 |    121 |     // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
 | 
| jens@0 |    122 |     do{
 | 
| jens@0 |    123 |         struct sockaddr_in addr4;
 | 
| jens@0 |    124 |         memset(&addr4, 0, sizeof(addr4));
 | 
| jens@0 |    125 |         addr4.sin_len = sizeof(addr4);
 | 
| jens@0 |    126 |         addr4.sin_family = AF_INET;
 | 
| jens@0 |    127 |         addr4.sin_port = htons(_port);
 | 
| jens@0 |    128 |         addr4.sin_addr.s_addr = htonl(INADDR_ANY);
 | 
| jens@0 |    129 | 
 | 
| jens@0 |    130 |         NSError *error;
 | 
| jens@0 |    131 |         _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
 | 
| jens@0 |    132 |         if( ! _ipv4socket ) {
 | 
| jens@0 |    133 |             if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
 | 
| jens@0 |    134 |                 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
 | 
| jens@8 |    135 |                 self.port += 1;        // try the next port
 | 
| jens@0 |    136 |             } else {
 | 
| jens@0 |    137 |                 if( outError ) *outError = error;
 | 
| jens@0 |    138 |                 return [self _failedToOpen: error];
 | 
| jens@0 |    139 |             }
 | 
| jens@0 |    140 |         }
 | 
| jens@0 |    141 |     }while( ! _ipv4socket );
 | 
| jens@0 |    142 |     
 | 
| jens@0 |    143 |     if (0 == _port) {
 | 
| jens@0 |    144 |         // now that the binding was successful, we get the port number 
 | 
| jens@0 |    145 |         NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
 | 
| jens@0 |    146 |         const struct sockaddr_in *addr4 = addr.bytes;
 | 
| jens@0 |    147 |         self.port = ntohs(addr4->sin_port);
 | 
| jens@0 |    148 |     }
 | 
| jens@0 |    149 |     
 | 
| jens@0 |    150 |     if( _useIPv6 ) {
 | 
| jens@0 |    151 |         // set up the IPv6 endpoint
 | 
| jens@0 |    152 |         struct sockaddr_in6 addr6;
 | 
| jens@0 |    153 |         memset(&addr6, 0, sizeof(addr6));
 | 
| jens@0 |    154 |         addr6.sin6_len = sizeof(addr6);
 | 
| jens@0 |    155 |         addr6.sin6_family = AF_INET6;
 | 
| jens@0 |    156 |         addr6.sin6_port = htons(_port);
 | 
| jens@0 |    157 |         memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
 | 
| jens@0 |    158 |         
 | 
| jens@26 |    159 |         NSError *error;
 | 
| jens@26 |    160 |         _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
 | 
| jens@0 |    161 |         if( ! _ipv6socket ) {
 | 
| jens@0 |    162 |             _ipv4socket = closeSocket(_ipv4socket);
 | 
| jens@26 |    163 |             [self _failedToOpen: error];
 | 
| jens@26 |    164 |             if (outError) *outError = error;
 | 
| jens@26 |    165 |             return NO;
 | 
| jens@0 |    166 |         }
 | 
| jens@0 |    167 |     }
 | 
| jens@0 |    168 |     
 | 
| jens@22 |    169 |     [self _openBonjour];
 | 
| jens@0 |    170 | 
 | 
| jens@0 |    171 |     LogTo(TCP,@"%@ is open",self);
 | 
| jens@0 |    172 |     [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
 | 
| jens@0 |    173 |     return YES;
 | 
| jens@0 |    174 | }
 | 
| jens@0 |    175 | 
 | 
| jens@0 |    176 | - (BOOL) open
 | 
| jens@0 |    177 | {
 | 
| jens@0 |    178 |     return [self open: nil];
 | 
| jens@0 |    179 | }
 | 
| jens@0 |    180 |     
 | 
| jens@0 |    181 | 
 | 
| jens@0 |    182 | - (void) close 
 | 
| jens@0 |    183 | {
 | 
| jens@0 |    184 |     if( _ipv4socket ) {
 | 
| jens@22 |    185 |         [self _closeBonjour];
 | 
| jens@0 |    186 |         _ipv4socket = closeSocket(_ipv4socket);
 | 
| jens@0 |    187 |         _ipv6socket = closeSocket(_ipv6socket);
 | 
| jens@0 |    188 | 
 | 
| jens@0 |    189 |         LogTo(BLIP,@"%@ is closed",self);
 | 
| jens@0 |    190 |         [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
 | 
| jens@0 |    191 |     }
 | 
| jens@0 |    192 | }
 | 
| jens@0 |    193 | 
 | 
| jens@0 |    194 | 
 | 
| jens@0 |    195 | - (BOOL) isOpen
 | 
| jens@0 |    196 | {
 | 
| jens@0 |    197 |     return _ipv4socket != NULL;
 | 
| jens@0 |    198 | }
 | 
| jens@0 |    199 | 
 | 
| jens@0 |    200 | 
 | 
| jens@0 |    201 | #pragma mark -
 | 
| jens@0 |    202 | #pragma mark ACCEPTING CONNECTIONS:
 | 
| jens@0 |    203 | 
 | 
| jens@0 |    204 | 
 | 
| jens@0 |    205 | @synthesize connectionClass = _connectionClass;
 | 
| jens@0 |    206 | 
 | 
| jens@0 |    207 | 
 | 
| jens@0 |    208 | - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
 | 
| jens@0 |    209 | {
 | 
| jens@0 |    210 |     IPAddress *addr = [IPAddress addressOfSocket: socket];
 | 
| jens@0 |    211 |     if( ! addr )
 | 
| jens@0 |    212 |         return NO;
 | 
| jens@0 |    213 |     if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
 | 
| jens@0 |    214 |        && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
 | 
| jens@0 |    215 |         return NO;
 | 
| jens@0 |    216 |     
 | 
| jens@0 |    217 |     Assert(_connectionClass);
 | 
| jens@0 |    218 |     TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
 | 
| jens@0 |    219 |                                                                       listener: self];
 | 
| jens@0 |    220 |     if( ! conn )
 | 
| jens@0 |    221 |         return NO;
 | 
| jens@0 |    222 |     
 | 
| jens@0 |    223 |     if( _sslProperties ) {
 | 
| jens@0 |    224 |         conn.SSLProperties = _sslProperties;
 | 
| jens@0 |    225 |         [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
 | 
| jens@0 |    226 |     }
 | 
| jens@0 |    227 |     [conn open];
 | 
| jens@0 |    228 |     [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
 | 
| jens@0 |    229 |     return YES;
 | 
| jens@0 |    230 | }
 | 
| jens@0 |    231 | 
 | 
| jens@0 |    232 | 
 | 
| jens@0 |    233 | static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
 | 
| jens@0 |    234 | {
 | 
| jens@0 |    235 |     TCPListener *server = (TCPListener *)info;
 | 
| jens@0 |    236 |     if (kCFSocketAcceptCallBack == type) { 
 | 
| jens@0 |    237 |         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
 | 
| jens@0 |    238 |         BOOL accepted = NO;
 | 
| jens@0 |    239 |         @try{
 | 
| jens@0 |    240 |             accepted = [server acceptConnection: nativeSocketHandle];
 | 
| jens@0 |    241 |         }catchAndReport(@"TCPListenerAcceptCallBack");
 | 
| jens@0 |    242 |         if( ! accepted )
 | 
| jens@0 |    243 |             close(nativeSocketHandle);
 | 
| jens@0 |    244 |     }
 | 
| jens@0 |    245 | }
 | 
| jens@0 |    246 | 
 | 
| jens@0 |    247 | 
 | 
| jens@0 |    248 | #pragma mark -
 | 
| jens@0 |    249 | #pragma mark BONJOUR:
 | 
| jens@0 |    250 | 
 | 
| jens@0 |    251 | 
 | 
| jens@22 |    252 | - (void) _openBonjour
 | 
| jens@22 |    253 | {
 | 
| jens@22 |    254 |     if( self.isOpen && _bonjourServiceType && !_netService) {
 | 
| jens@22 |    255 |         // instantiate the NSNetService object that will advertise on our behalf.
 | 
| jens@22 |    256 |         _netService = [[NSNetService alloc] initWithDomain: @"local." 
 | 
| jens@22 |    257 |                                                       type: _bonjourServiceType
 | 
| jens@22 |    258 |                                                       name: _bonjourServiceName ?:@""
 | 
| jens@22 |    259 |                                                       port: _port];
 | 
| jens@22 |    260 |         if( _netService ) {
 | 
| jens@22 |    261 |             [_netService setDelegate:self];
 | 
| jens@22 |    262 |             if( _bonjourTXTRecord )
 | 
| jens@22 |    263 |                 [self _updateTXTRecord];
 | 
| jens@22 |    264 |             [_netService publish];
 | 
| jens@22 |    265 |         } else {
 | 
| jens@22 |    266 |             self.bonjourError = -1;
 | 
| jens@22 |    267 |             Warn(@"%@: Failed to create NSNetService",self);
 | 
| jens@22 |    268 |         }
 | 
| jens@22 |    269 |     }
 | 
| jens@22 |    270 | }
 | 
| jens@22 |    271 | 
 | 
| jens@22 |    272 | - (void) _closeBonjour
 | 
| jens@22 |    273 | {
 | 
| jens@22 |    274 |     if( _netService ) {
 | 
| jens@22 |    275 |         [_netService stop];
 | 
| jens@22 |    276 |         [_netService release];
 | 
| jens@22 |    277 |         _netService = nil;
 | 
| jens@22 |    278 |         self.bonjourPublished = NO;
 | 
| jens@22 |    279 |     }
 | 
| jens@22 |    280 |     if( self.bonjourError )
 | 
| jens@22 |    281 |         self.bonjourError = 0;
 | 
| jens@22 |    282 | }
 | 
| jens@22 |    283 | 
 | 
| jens@22 |    284 | 
 | 
| jens@22 |    285 | - (NSString*) bonjourServiceName {return _bonjourServiceName;}
 | 
| jens@22 |    286 | 
 | 
| jens@22 |    287 | - (void) setBonjourServiceName: (NSString*)name
 | 
| jens@22 |    288 | {
 | 
| jens@22 |    289 |     if( ! $equal(name,_bonjourServiceName) ) {
 | 
| jens@22 |    290 |         [self _closeBonjour];
 | 
| jens@22 |    291 |         setObj(&_bonjourServiceName,name);
 | 
| jens@22 |    292 |         [self _openBonjour];
 | 
| jens@22 |    293 |     }
 | 
| jens@22 |    294 | }
 | 
| jens@22 |    295 | 
 | 
| jens@22 |    296 | 
 | 
| jens@0 |    297 | - (NSDictionary*) bonjourTXTRecord
 | 
| jens@0 |    298 | {
 | 
| jens@0 |    299 |     return _bonjourTXTRecord;
 | 
| jens@0 |    300 | }
 | 
| jens@0 |    301 | 
 | 
| jens@0 |    302 | - (void) setBonjourTXTRecord: (NSDictionary*)txt
 | 
| jens@0 |    303 | {
 | 
| jens@0 |    304 |     if( ifSetObj(&_bonjourTXTRecord,txt) )
 | 
| jens@0 |    305 |         [self _updateTXTRecord];
 | 
| jens@0 |    306 | }
 | 
| jens@0 |    307 | 
 | 
| jens@0 |    308 | - (void) _updateTXTRecord
 | 
| jens@0 |    309 | {
 | 
| jens@0 |    310 |     if( _netService ) {
 | 
| jens@0 |    311 |         NSData *data;
 | 
| jens@0 |    312 |         if( _bonjourTXTRecord ) {
 | 
| jens@0 |    313 |             data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
 | 
| jens@0 |    314 |             if( data )
 | 
| jens@0 |    315 |                 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
 | 
| jens@0 |    316 |             else
 | 
| jens@0 |    317 |                 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
 | 
| jens@0 |    318 |         } else
 | 
| jens@0 |    319 |             data = nil;
 | 
| jens@0 |    320 |         [_netService setTXTRecordData: data];
 | 
| jens@0 |    321 |     }
 | 
| jens@0 |    322 | }
 | 
| jens@0 |    323 | 
 | 
| jens@0 |    324 | 
 | 
| jens@0 |    325 | - (void)netServiceWillPublish:(NSNetService *)sender
 | 
| jens@0 |    326 | {
 | 
| jens@0 |    327 |     LogTo(BLIP,@"%@: Advertising %@",self,sender);
 | 
| jens@0 |    328 |     self.bonjourPublished = YES;
 | 
| jens@0 |    329 | }
 | 
| jens@0 |    330 | 
 | 
| jens@0 |    331 | - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
 | 
| jens@0 |    332 | {
 | 
| jens@0 |    333 |     self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
 | 
| jens@0 |    334 |     LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
 | 
| jens@0 |    335 |     [_netService release];
 | 
| jens@0 |    336 |     _netService = nil;
 | 
| jens@0 |    337 | }
 | 
| jens@0 |    338 | 
 | 
| jens@0 |    339 | - (void)netServiceDidStop:(NSNetService *)sender
 | 
| jens@0 |    340 | {
 | 
| jens@0 |    341 |     LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
 | 
| jens@0 |    342 |     self.bonjourPublished = NO;
 | 
| jens@0 |    343 |     [_netService release];
 | 
| jens@0 |    344 |     _netService = nil;
 | 
| jens@0 |    345 | }
 | 
| jens@0 |    346 | 
 | 
| jens@0 |    347 | 
 | 
| jens@0 |    348 | @end
 | 
| jens@0 |    349 | 
 | 
| jens@0 |    350 | 
 | 
| jens@0 |    351 | 
 | 
| jens@0 |    352 | /*
 | 
| jens@0 |    353 |  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
 | 
| jens@0 |    354 |  
 | 
| jens@0 |    355 |  Redistribution and use in source and binary forms, with or without modification, are permitted
 | 
| jens@0 |    356 |  provided that the following conditions are met:
 | 
| jens@0 |    357 |  
 | 
| jens@0 |    358 |  * Redistributions of source code must retain the above copyright notice, this list of conditions
 | 
| jens@0 |    359 |  and the following disclaimer.
 | 
| jens@0 |    360 |  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 | 
| jens@0 |    361 |  and the following disclaimer in the documentation and/or other materials provided with the
 | 
| jens@0 |    362 |  distribution.
 | 
| jens@0 |    363 |  
 | 
| jens@0 |    364 |  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 | 
| jens@0 |    365 |  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 | 
| jens@0 |    366 |  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
 | 
| jens@0 |    367 |  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | 
| jens@0 |    368 |  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 | 
| jens@0 |    369 |   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 | 
| jens@0 |    370 |  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
 | 
| jens@0 |    371 |  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
| jens@0 |    372 |  */
 |