MYKeychain.m
author snej@snej.local
Sun Apr 12 22:16:14 2009 -0700 (2009-04-12)
changeset 9 aa5eb3fd6ebf
parent 3 1dfe820d7ebe
child 16 c409dbc4f068
permissions -rw-r--r--
Doc touch-up
snej@0
     1
//
snej@0
     2
//  MYKeychain.m
snej@0
     3
//  MYCrypto
snej@0
     4
//
snej@0
     5
//  Created by Jens Alfke on 3/23/09.
snej@0
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@0
     7
//
snej@0
     8
snej@0
     9
#import "MYKeychain.h"
snej@0
    10
#import "MYCrypto_Private.h"
snej@0
    11
#import "MYDigest.h"
snej@4
    12
#import "MYIdentity.h"
snej@4
    13
snej@0
    14
snej@2
    15
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    16
snej@0
    17
snej@0
    18
@interface MYKeyEnumerator : NSEnumerator
snej@0
    19
{
snej@4
    20
    @private
snej@0
    21
    MYKeychain *_keychain;
snej@0
    22
    SecKeychainSearchRef _search;
snej@0
    23
    SecItemClass _itemClass;
snej@0
    24
}
snej@0
    25
snej@0
    26
- (id) initWithKeychain: (MYKeychain*)keychain
snej@0
    27
              itemClass: (SecItemClass)itemClass
snej@0
    28
             attributes: (SecKeychainAttribute[])attributes 
snej@0
    29
                  count: (unsigned)count;
snej@0
    30
@end
snej@0
    31
snej@0
    32
snej@4
    33
@interface MYIdentityEnumerator : NSEnumerator
snej@4
    34
{
snej@4
    35
    @private
snej@4
    36
    SecIdentitySearchRef _searchRef;
snej@4
    37
}
snej@4
    38
snej@4
    39
- (id) initWithKeychain: (MYKeychain*)keychain;
snej@4
    40
@end
snej@4
    41
snej@4
    42
snej@4
    43
snej@0
    44
snej@0
    45
@implementation MYKeychain
snej@0
    46
snej@0
    47
snej@0
    48
- (id) initWithKeychainRef: (SecKeychainRef)keychainRef
snej@0
    49
{
snej@0
    50
    self = [super init];
snej@0
    51
    if (self != nil) {
snej@0
    52
        if (keychainRef) {
snej@0
    53
            CFRetain(keychainRef);
snej@0
    54
            _keychain = keychainRef;
snej@0
    55
        }
snej@0
    56
    }
snej@0
    57
    return self;
snej@0
    58
}
snej@0
    59
snej@0
    60
+ (MYKeychain*) _readableKeychainWithRef: (SecKeychainRef)keychainRef fromPath: (NSString*)path {
snej@0
    61
    if (!keychainRef)
snej@0
    62
        return nil;
snej@0
    63
    SecKeychainStatus status;
snej@0
    64
    BOOL ok = check(SecKeychainGetStatus(keychainRef, &status), @"SecKeychainGetStatus");
snej@0
    65
    if (ok && !(status & kSecReadPermStatus)) {
snej@0
    66
        Warn(@"Can't open keychain at %@ : not readable (status=%i)", path,status);
snej@0
    67
        ok = NO;
snej@0
    68
    }
snej@0
    69
    MYKeychain *keychain = nil;
snej@0
    70
    if (ok)
snej@0
    71
        keychain = [[[self alloc] initWithKeychainRef: keychainRef] autorelease];
snej@0
    72
    CFRelease(keychainRef);
snej@0
    73
    return keychain;
snej@0
    74
}
snej@0
    75
snej@0
    76
+ (MYKeychain*) openKeychainAtPath: (NSString*)path
snej@0
    77
{
snej@0
    78
    Assert(path);
snej@0
    79
    SecKeychainRef keychainRef = NULL;
snej@0
    80
    if (!check(SecKeychainOpen(path.fileSystemRepresentation, &keychainRef), @"SecKeychainOpen"))
snej@0
    81
        return nil;
snej@0
    82
    return [self _readableKeychainWithRef: keychainRef fromPath: path];
snej@0
    83
}
snej@0
    84
snej@0
    85
+ (MYKeychain*) createKeychainAtPath: (NSString*)path
snej@0
    86
                        withPassword: (NSString*)password
snej@0
    87
{
snej@0
    88
    Assert(path);
snej@0
    89
    const char *passwordStr = [password UTF8String];
snej@0
    90
    SecKeychainRef keychainRef = NULL;
snej@0
    91
    if (!check(SecKeychainCreate(path.fileSystemRepresentation,
snej@0
    92
                                 passwordStr ?strlen(passwordStr) :0,
snej@0
    93
                                 passwordStr, 
snej@0
    94
                                 (password==nil), 
snej@0
    95
                                 NULL, 
snej@0
    96
                                 &keychainRef),
snej@0
    97
               @"SecKeychainCreate"))
snej@0
    98
        return nil;
snej@0
    99
    return [self _readableKeychainWithRef: keychainRef fromPath: path];
snej@0
   100
}
snej@0
   101
snej@0
   102
- (BOOL) deleteKeychainFile {
snej@0
   103
    Assert(_keychain);
snej@0
   104
    return check(SecKeychainDelete(_keychain), @"SecKeychainDelete");
snej@0
   105
}
snej@0
   106
snej@0
   107
snej@0
   108
- (void) dealloc
snej@0
   109
{
snej@0
   110
    if (_keychain) CFRelease(_keychain);
snej@0
   111
    [super dealloc];
snej@0
   112
}
snej@0
   113
snej@2
   114
- (void) finalize
snej@2
   115
{
snej@2
   116
    if (_keychain) CFRelease(_keychain);
snej@2
   117
    [super finalize];
snej@2
   118
}
snej@2
   119
snej@0
   120
snej@0
   121
+ (MYKeychain*) allKeychains
snej@0
   122
{
snej@0
   123
    static MYKeychain *sAllKeychains;
snej@0
   124
    @synchronized(self) {
snej@0
   125
        if (!sAllKeychains)
snej@0
   126
            sAllKeychains = [[self alloc] initWithKeychainRef: nil];
snej@0
   127
    }
snej@0
   128
    return sAllKeychains;
snej@0
   129
}
snej@0
   130
snej@0
   131
snej@0
   132
+ (MYKeychain*) defaultKeychain
snej@0
   133
{
snej@0
   134
    static MYKeychain *sDefaultKeychain;
snej@0
   135
    @synchronized(self) {
snej@0
   136
        if (!sDefaultKeychain) {
snej@0
   137
            SecKeychainRef kc = NULL;
snej@0
   138
            OSStatus err = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser,&kc);
snej@0
   139
#if TARGET_OS_IPHONE
snej@0
   140
            // In the simulator, an app is run in a sandbox that has no keychain by default.
snej@0
   141
            // As a convenience, create one if necessary:
snej@0
   142
            if (err == errSecNoDefaultKeychain) {
snej@0
   143
                Log(@"No default keychain in simulator; creating one...");
snej@0
   144
                NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
snej@0
   145
                                                                      NSUserDomainMask, YES) objectAtIndex: 0];
snej@0
   146
                path = [path stringByAppendingPathComponent: @"MYCrypto.keychain"];
snej@0
   147
                sDefaultKeychain = [[self createKeychainAtPath: path withPassword: nil] retain];
snej@0
   148
                Assert(sDefaultKeychain, @"Couldn't create default keychain");
snej@0
   149
                SecKeychainSetDomainDefault(kSecPreferencesDomainUser, sDefaultKeychain.keychainRef);
snej@0
   150
                Log(@"...created %@", sDefaultKeychain);
snej@0
   151
                return sDefaultKeychain;
snej@0
   152
            }
snej@0
   153
#endif
snej@0
   154
            if (!check(err, @"SecKeychainCopyDefault"))
snej@0
   155
                kc = NULL;
snej@0
   156
snej@0
   157
            Assert(kc, @"No default keychain");
snej@0
   158
            sDefaultKeychain = [[self alloc] initWithKeychainRef: kc];
snej@0
   159
            CFRelease(kc);
snej@0
   160
        }
snej@0
   161
    }
snej@0
   162
    return sDefaultKeychain;
snej@0
   163
}
snej@0
   164
snej@0
   165
snej@0
   166
- (id) copyWithZone: (NSZone*)zone {
snej@0
   167
    // It's not necessary to make copies of Keychain objects. This makes it more efficient
snej@0
   168
    // to use instances as NSDictionary keys or store them in NSSets.
snej@0
   169
    return [self retain];
snej@0
   170
}
snej@0
   171
snej@0
   172
- (BOOL) isEqual: (id)obj {
snej@0
   173
    return (obj == self) || 
snej@0
   174
            ([obj isKindOfClass: [MYKeychain class]] && CFEqual(_keychain, [obj keychainRef]));
snej@0
   175
}
snej@0
   176
snej@0
   177
snej@0
   178
- (SecKeychainRef) keychainRef {
snej@0
   179
    return _keychain;
snej@0
   180
}
snej@0
   181
snej@0
   182
snej@0
   183
- (SecKeychainRef) keychainRefOrDefault {
snej@0
   184
    if (_keychain)
snej@0
   185
        return _keychain;
snej@0
   186
    else
snej@0
   187
        return [[[self class] defaultKeychain] keychainRef];
snej@0
   188
}
snej@0
   189
    
snej@0
   190
    
snej@0
   191
- (NSString*) path {
snej@0
   192
    if (!_keychain)
snej@0
   193
        return nil;
snej@0
   194
    char pathBuf[PATH_MAX];
snej@0
   195
    UInt32 pathLen = sizeof(pathBuf);
snej@0
   196
    if (!check(SecKeychainGetPath(_keychain, &pathLen, pathBuf), @"SecKeychainGetPath"))
snej@0
   197
        return nil;
snej@0
   198
    return [[NSFileManager defaultManager] stringWithFileSystemRepresentation: pathBuf length: pathLen];
snej@0
   199
}
snej@0
   200
snej@0
   201
- (NSString*) description {
snej@0
   202
    if (_keychain)
snej@0
   203
        return $sprintf(@"%@[%p, %@]", [self class], _keychain, self.path);
snej@0
   204
    else
snej@0
   205
        return $sprintf(@"%@[all]", [self class]);
snej@0
   206
}
snej@0
   207
snej@0
   208
snej@0
   209
#pragma mark -
snej@0
   210
#pragma mark SEARCHING:
snej@0
   211
snej@0
   212
snej@0
   213
- (MYKeychainItem*) itemOfClass: (SecItemClass)itemClass 
snej@0
   214
                     withDigest: (MYSHA1Digest*)pubKeyDigest 
snej@0
   215
{
snej@0
   216
    SecKeychainAttribute attr = {.tag= (itemClass==kSecCertificateItemClass ?kSecPublicKeyHashItemAttr :kSecKeyLabel), 
snej@0
   217
                                 .length= pubKeyDigest.length, 
snej@0
   218
                                 .data= (void*) pubKeyDigest.bytes};
snej@0
   219
    MYKeyEnumerator *e = [[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   220
                                                         itemClass: itemClass
snej@0
   221
                                                        attributes: &attr count: 1];
snej@0
   222
    MYKeychainItem *item = e.nextObject;
snej@0
   223
    [e release];
snej@0
   224
    return item;
snej@0
   225
}
snej@0
   226
snej@0
   227
- (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@0
   228
    return (MYPublicKey*) [self itemOfClass: kSecPublicKeyItemClass withDigest: pubKeyDigest];
snej@0
   229
}   
snej@0
   230
snej@0
   231
- (NSEnumerator*) enumeratePublicKeys {
snej@0
   232
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   233
                                            itemClass: kSecPublicKeyItemClass
snej@0
   234
                                           attributes: NULL count: 0] autorelease];
snej@0
   235
}
snej@0
   236
snej@0
   237
- (NSEnumerator*) publicKeysWithAlias: (NSString*)alias {
snej@0
   238
    NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
snej@0
   239
    SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
snej@0
   240
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   241
                                            itemClass: kSecPublicKeyItemClass
snej@0
   242
                                           attributes: &attr count: 1] autorelease];
snej@0
   243
}
snej@0
   244
snej@0
   245
snej@3
   246
- (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@3
   247
    return (MYPrivateKey*) [self itemOfClass: kSecPrivateKeyItemClass withDigest: pubKeyDigest];
snej@0
   248
}
snej@0
   249
snej@3
   250
- (NSEnumerator*) enumeratePrivateKeys {
snej@0
   251
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   252
                                            itemClass: kSecPrivateKeyItemClass
snej@0
   253
                                           attributes: NULL count: 0] autorelease];
snej@0
   254
}
snej@0
   255
snej@0
   256
- (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@0
   257
    return (MYCertificate*) [self itemOfClass: kSecCertificateItemClass withDigest: pubKeyDigest];
snej@0
   258
}
snej@0
   259
snej@0
   260
- (NSEnumerator*) enumerateCertificates {
snej@0
   261
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   262
                                            itemClass: kSecCertificateItemClass
snej@0
   263
                                           attributes: NULL count: 0] autorelease];
snej@0
   264
}
snej@0
   265
snej@4
   266
- (NSEnumerator*) enumerateIdentities {
snej@4
   267
    return [[[MYIdentityEnumerator alloc] initWithKeychain: self] autorelease];
snej@4
   268
}
snej@4
   269
snej@0
   270
- (NSEnumerator*) enumerateSymmetricKeys {
snej@0
   271
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   272
                                            itemClass: kSecSymmetricKeyItemClass
snej@0
   273
                                           attributes: NULL count: 0] autorelease];
snej@0
   274
}
snej@0
   275
snej@0
   276
- (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias {
snej@0
   277
    NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
snej@0
   278
    SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
snej@0
   279
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   280
                                            itemClass: kSecSymmetricKeyItemClass
snej@0
   281
                                           attributes: &attr count: 1] autorelease];
snej@0
   282
}
snej@0
   283
snej@0
   284
snej@0
   285
snej@0
   286
#pragma mark -
snej@0
   287
#pragma mark IMPORT:
snej@0
   288
snej@0
   289
snej@0
   290
- (MYPublicKey*) importPublicKey: (NSData*)keyData {
snej@0
   291
    return [[[MYPublicKey alloc] _initWithKeyData: keyData 
snej@0
   292
                                      forKeychain: self.keychainRefOrDefault]
snej@0
   293
            autorelease];
snej@0
   294
}
snej@0
   295
snej@3
   296
- (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
snej@0
   297
                    privateKey: (NSData*)privKeyData 
snej@0
   298
                    alertTitle: (NSString*)title
snej@0
   299
                   alertPrompt: (NSString*)prompt {
snej@3
   300
    return [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
snej@3
   301
                                     publicKeyData: pubKeyData
snej@3
   302
                                       forKeychain: self.keychainRefOrDefault
snej@3
   303
                                        alertTitle: (NSString*)title
snej@3
   304
                                       alertPrompt: (NSString*)prompt]
snej@0
   305
            autorelease];
snej@0
   306
}
snej@0
   307
snej@3
   308
- (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
snej@0
   309
                    privateKey: (NSData*)privKeyData 
snej@0
   310
{
snej@0
   311
    return [self importPublicKey: pubKeyData privateKey: privKeyData
snej@0
   312
                      alertTitle: @"Import Private Key"
snej@0
   313
                     alertPrompt: @"To import your saved private key, please re-enter the "
snej@0
   314
                                   "passphrase you used when you exported it."];
snej@0
   315
}
snej@0
   316
snej@0
   317
- (MYCertificate*) importCertificate: (NSData*)data
snej@0
   318
                                type: (CSSM_CERT_TYPE) type
snej@0
   319
                            encoding: (CSSM_CERT_ENCODING) encoding;
snej@0
   320
{
snej@0
   321
    MYCertificate *cert = [[[MYCertificate alloc] initWithCertificateData: data 
snej@0
   322
                                                                    type: type
snej@0
   323
                                                                encoding: encoding]
snej@0
   324
                           autorelease];
snej@0
   325
    if (cert) {
snej@0
   326
        if (!check(SecCertificateAddToKeychain(cert.certificateRef, self.keychainRefOrDefault),
snej@0
   327
                   @"SecCertificateAddToKeychain"))
snej@0
   328
            cert = nil;
snej@0
   329
    }
snej@0
   330
    return cert;
snej@0
   331
}
snej@0
   332
snej@0
   333
- (MYCertificate*) importCertificate: (NSData*)data {
snej@0
   334
    return [self importCertificate: data 
snej@0
   335
                              type: CSSM_CERT_X_509v3 
snej@0
   336
                          encoding: CSSM_CERT_ENCODING_BER];
snej@0
   337
}
snej@0
   338
snej@4
   339
- (BOOL) addCertificate: (MYCertificate*)certificate {
snej@4
   340
    Assert(certificate);
snej@4
   341
    return check(SecCertificateAddToKeychain(certificate.certificateRef, self.keychainRefOrDefault),
snej@4
   342
                 @"SecCertificateAddToKeychain");
snej@4
   343
}
snej@4
   344
snej@0
   345
snej@0
   346
- (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
snej@0
   347
                                     algorithm: (CCAlgorithm)algorithm
snej@0
   348
{
snej@0
   349
    return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
snej@0
   350
                                             algorithm: algorithm inKeychain: self];
snej@0
   351
}
snej@0
   352
snej@3
   353
- (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
snej@3
   354
    return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
snej@0
   355
}
snej@0
   356
snej@0
   357
snej@0
   358
- (CSSM_CSP_HANDLE) CSPHandle {
snej@0
   359
    CSSM_CSP_HANDLE cspHandle = 0;
snej@0
   360
    Assert(check(SecKeychainGetCSPHandle(self.keychainRefOrDefault, &cspHandle), @"SecKeychainGetCSPHandle"));
snej@0
   361
    return cspHandle;
snej@0
   362
}
snej@0
   363
snej@0
   364
snej@0
   365
@end
snej@0
   366
snej@0
   367
snej@0
   368
snej@0
   369
#pragma mark -
snej@0
   370
@implementation MYKeyEnumerator
snej@0
   371
snej@0
   372
- (id) initWithKeychain: (MYKeychain*)keychain
snej@0
   373
              itemClass: (SecItemClass)itemClass
snej@0
   374
             attributes: (SecKeychainAttribute[])attributes 
snej@0
   375
                  count: (unsigned)count {
snej@0
   376
    self = [super init];
snej@0
   377
    if (self) {
snej@0
   378
        _keychain = [keychain retain];
snej@0
   379
        _itemClass = itemClass;
snej@0
   380
        SecKeychainAttributeList list = {.count=count, .attr=attributes};
snej@0
   381
        if (!check(SecKeychainSearchCreateFromAttributes(keychain.keychainRef,
snej@0
   382
                                                         itemClass,
snej@0
   383
                                                         &list,
snej@0
   384
                                                         &_search),
snej@0
   385
                   @"SecKeychainSearchCreateFromAttributes")) {
snej@0
   386
            [self release];
snej@0
   387
            return nil;
snej@0
   388
        }
snej@0
   389
    }
snej@0
   390
    return self;
snej@0
   391
}
snej@0
   392
snej@0
   393
- (void) dealloc
snej@0
   394
{
snej@0
   395
    [_keychain release];
snej@0
   396
    if (_search) CFRelease(_search);
snej@0
   397
    [super dealloc];
snej@0
   398
}
snej@0
   399
snej@2
   400
- (void) finalize
snej@2
   401
{
snej@2
   402
    [_keychain release];
snej@2
   403
    if (_search) CFRelease(_search);
snej@2
   404
    [super finalize];
snej@2
   405
}
snej@2
   406
snej@0
   407
snej@0
   408
- (id) nextObject {
snej@0
   409
    if (!_search)
snej@0
   410
        return nil;
snej@0
   411
    MYPublicKey *key = nil;
snej@0
   412
    do{
snej@0
   413
        SecKeychainItemRef found = NULL;
snej@0
   414
        OSStatus err = SecKeychainSearchCopyNext(_search, &found);
snej@0
   415
        if (err || !found) {
snej@0
   416
            if (err != errSecItemNotFound)
snej@0
   417
                check(err,@"SecKeychainSearchCopyNext");
snej@0
   418
            CFRelease(_search);
snej@0
   419
            _search = NULL;
snej@0
   420
            return nil;
snej@0
   421
        }
snej@0
   422
        
snej@0
   423
        switch (_itemClass) {
snej@0
   424
            case kSecPrivateKeyItemClass: {
snej@3
   425
                key = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found];
snej@0
   426
                break;
snej@0
   427
            }
snej@0
   428
            case kSecCertificateItemClass:
snej@0
   429
                key = [[[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found] autorelease];
snej@0
   430
                break;
snej@0
   431
            case kSecPublicKeyItemClass:
snej@0
   432
                key = [[[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found] autorelease];
snej@0
   433
                break;
snej@0
   434
        }
snej@0
   435
        CFRelease(found);
snej@0
   436
    } while (key==nil);
snej@0
   437
    return key;
snej@0
   438
}
snej@0
   439
snej@4
   440
@end
snej@4
   441
snej@4
   442
snej@4
   443
snej@4
   444
@implementation MYIdentityEnumerator
snej@4
   445
snej@4
   446
- (id) initWithKeychain: (MYKeychain*)keychain {
snej@4
   447
    self = [super init];
snej@4
   448
    if (self) {
snej@4
   449
        if (!check(SecIdentitySearchCreate(keychain.keychainRef, 0, &_searchRef),
snej@4
   450
                   @"SecIdentitySearchCreate")) {
snej@4
   451
            [self release];
snej@4
   452
            return nil;
snej@4
   453
        }
snej@4
   454
    }
snej@4
   455
    return self;
snej@4
   456
}
snej@4
   457
snej@4
   458
- (id) nextObject {
snej@4
   459
    SecIdentityRef identityRef = NULL;
snej@4
   460
    OSStatus err = SecIdentitySearchCopyNext(_searchRef, &identityRef);
snej@4
   461
    if (err==errKCItemNotFound || !check(err, @"SecIdentitySearchCopyNext"))
snej@4
   462
        return nil;
snej@4
   463
    return [[[MYIdentity alloc] initWithIdentityRef: identityRef] autorelease];
snej@4
   464
}
snej@0
   465
snej@0
   466
@end
snej@0
   467
snej@0
   468
snej@2
   469
#endif !MYCRYPTO_USE_IPHONE_API
snej@0
   470
snej@0
   471
snej@0
   472
snej@0
   473
/*
snej@0
   474
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@0
   475
 
snej@0
   476
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@0
   477
 provided that the following conditions are met:
snej@0
   478
 
snej@0
   479
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@0
   480
 and the following disclaimer.
snej@0
   481
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@0
   482
 and the following disclaimer in the documentation and/or other materials provided with the
snej@0
   483
 distribution.
snej@0
   484
 
snej@0
   485
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@0
   486
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@0
   487
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@0
   488
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@0
   489
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@0
   490
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@0
   491
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@0
   492
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@0
   493
 */