NAME

XKPasswd - A secure memorable password generator

VERSION

This documentation refers to XKPasswd version 2.1.1.

SYNOPSIS

    use XKPasswd;

    #
    # Functional Interface - for single passwords generated from simple configs
    #
    
    # generate a single password using words from the file
    # sample_dict.txt using the default configuration
    my $password = xkpasswd('sample_dict.txt');
    
    # generate a single password using one of the module's
    # predefined presets exactly
    my $password = xkpasswd('sample_dict.txt', 'XKCD');
    
    # generate a single password using one of the module's
    # predefined presets as a starting point, but with a
    # small customisation
    my $password = xkpasswd('sample_dict.txt', 'XKCD', {separator_character => q{ }});
    
    #
    # Object Oriented Interface
    #
    
    # create a new instance with the default config
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt');
    
    # create an instance from the preset 'XKCD'
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', 'XKCD');
    
    # create an instance based on the preset 'XKCD' with one customisation
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', 'XKCD', {separator_character => q{ }});
    
    # create an instance from a config based on a preset
    # but with many alterations
    my $config = XKPasswd->preset_config('XKCD');
    $config->{separator_character} = q{ };
    $config->{case_transform} = 'INVERT';
    $config->{padding_type} = "FIXED";
    $config->{padding_characters_before} = 1;
    $config->{padding_characters_after} = 1;
    $config->{padding_character} = '*';
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', $config);
    
    # create an instance from an entirely custom configuration
    my $config = {
        padding_alphabet => [qw{! @ $ % ^ & * + = : ~ ?}],
        separator_alphabet => [qw{- + = . _ | ~}],
        word_length_min => 6,
        word_length_max => 6,
        num_words => 3,
        separator_character => 'RANDOM',
        padding_digits_before => 2,
        padding_digits_after => 2,
        padding_type => 'FIXED',
        padding_character => 'RANDOM',
        padding_characters_before => 2,
        padding_characters_after => 2,
        case_transform => 'CAPITALISE',
        random_function => \&XKPasswd::basic_random_generator,
        random_increment => 'AUTO',
    }
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', $config);
    
    # generate a single password
    my $password = $xkpasswd_instance->password();
    
    # generate multiple passwords
    my @passwords = $xkpasswd_instance->passwords(10);

DESCRIPTION

A secure memorable password generator inspired by the wonderful XKCD webcomic at http://www.xkcd.com/ and Steve Gibson's Password Haystacks page at https://www.grc.com/haystack.htm. This is the Perl library that powers https://www.xkpasswd.net.

PHILOSOPHY

More and more of the things we do on our computer require passwords, and at the same time it seems we hear about organisations or sites losing user database on every day that ends in a y. If we re-use our passwords we expose ourself to an ever greater risk, but we need more passwords than we can possibly remember or invent. Coming up with one good password is easy, but coming up with one good password a week is a lot harder, let alone one a day!

Obviously we need some technological help. We need our computers to help us generate robust password and store them securely. There are many great password managers out there to help us securely store and sync our passwords, including commercial offerings and open-source projects. Many of these managers also offer to generate random passwords for us, usually in the form of a random string of meaningless letters numbers and symbols. These kinds of nonsense passwords are certainly secure, but they are often impractical.

Regardless of how good your chosen password manager is, there will always be times when you need to type in your passwords, and that's when random gibberish passwords become a real pain point. As annoying as it is to have to glance over and back at a small cellphone screen to manually type a gibberish password into a computer, that's nothing compared to the annoyance of trying to communicate such a password to a family member, friend, colleague or customer over the phone.

Surely it would be better to have passwords that are still truly random in the way humans can't be, but are also human-friendly in the way random gibberish never will be? This is the problem this module aims to solve.

Rather than randomly choosing many letters, digits, and symbols from a fairly small alphabet of possible characters, this library chooses a small number of words from a large alphabet of possible words as the basis for passwords. Words are easy to remember, easy to read from a screen, easy to type, and easy to communicate over the telephone.

This module uses words to make up the bulk of the passwords it generates, but it also adds carefully placed random symbols and digits to add more security without the passwords difficult to remember, read, type, or speak.

In shot, this module is for people who prefer passwords that look like this:

    !15.play.MAJOR.fresh.FLAT.23!

to passwords that look like this:

    eB8.GJXa@TuM

THE MATHS

Before examining the password strength of passwords generated with this module we need to lay out the relatively simple maths underlying it all.

Maths Primer

A coin could be used as a very simple password generator. Each character in the password would be the result of a single coin toss. If the coin lands heads up, we add a H to our password, if it lands tails up, we add a T.

If you made a one-letter password in this way there would only be two possibilities, H, or T, or two permutations. If you made a two-letter password in this way there would be four possible combinations, or permutations, HH, HT, TH, and TT. If you made a three-character password in this way there would be 16 permutations, a five character one would have 32 permutations, and so forth.

So, for a coin toss, which has two possible values for each character, the formula for the number of permutations P for a given length of password L is:

    P = 2^L

Or, two to the power of the length of the password.

If we now swapped our coin for a dice, we would go from two possible values per letter, to six possible values per letter. For one dice roll there would be six permutations, for two there would be 36, for three there would be 108 and so on.

This means that for a dice, the number of permutations can be calculated with the formula:

    P = 6^L

When talking about passwords, the set of possible symbols used for each character in the password is referred to as the password's alphabet. So, for the coin toss the alphabet was just H and T, and for the dice it was 1, 2, 3, 4, 5, and 6. The actual characters used in the alphabet make no difference to the strength of the password, all that matters is the size of the alphabet, which we'll call A.

As you can probably infer from the two examples above, the formula for the number of possible permutations P for a password of length L created from an alphabet of size A is:

    P = A^L

In the real world our passwords are generally made up of a mix of letters, digits, and symbols. If we use mixed case that gives us 52 letters alone, then add in the ten digits from 0 to 9 and we're already up to 62 possible characters before we even start on the array of symbols and punctuation characters on our keyboards. It's generally accepted that if you include symbols and punctuation, there are 95 characters available for use in randomly generated passwords. Hence, in the real-world, the value for A is assumed to be 95. When you start raising a number as big as 95 to even low powers the number of permutations quickly rises.

A two character password with alphabet of 95 has 9025 permutations, increasing the length to three characters brings that up to 857,375, and so on. These numbers very quickly become too big to handle. For just an 8 character password we are talking about 6,634,204,312,890,625 permutations, which is a number so big most people couldn't say it (what do you call something a thousand times bigger than a trillion?).

Because the numbers get so astronomically big so quickly, computer scientists use bits of entropy to measure password strength rather than the number of permutations. The formula to turn permutations into bits of entropy E is very simple:

    E = Log(2)P

In other words, the entropy is the log to base two of the permutations. For our eight character example that equates to about 52 bits.

There are two approaches to increasing the number of permutations, and hence the entropy, you can choose more characters, or, you can make the alphabet you are choosing from bigger.

The Entropy of XKPasswd Passwords

Exactly how much entropy does a password need? That's the subject of much debate, and the answer ultimately depends on the value of the assets being protected by the password.

Two common recommendations you hear are 8 characters containing a mix of upper and lower case letters, digits, and symbols, or 12 characters with the same composition. These evaluation to approximately 52 bits of entropy and 78 bits of entropy respectively.

When evaluating the entropy of passwords generated by this module, it has to be done from two points of view for the answer to be meaningful. Firstly, a best-case scenario - the attacker has absolutely no knowledge of how the password was generated, and hence must mount a brute-force attack. Then, secondly from the point of view of an attacker with full knowledge of how the password was generated. Not just the knowledge that this module was used, but a copy of the dictionary file used, and, a copy of the configuration settings used.

For the purpose of this documentation, the entropy in the first scenario, the brute force attack, will be referred to as the blind entropy, and the entropy in the second scenario the seen entropy.

The blind entropy is solely determined by the configuration settings, the seen entropy depends on both the settings and the dictionary file used.

Calculating the bind entropy Eb is quite straightforward, we just need to know the size of the alphabet resulting from the configuration A, and the minimum length of passwords generated with the configuration L, and plug those values into this formula:

    Eb = Log(2)(A^L)

Calculating A simply involves determining whether or not the configuration results in a mix of letter cases (26 or 52 characters), the inclusion of at least one symbol (if any one is present, assume the industry standard of a 33 character search space), and the inclusion of at least one digit (10 character). This will result in a value between 26 and 95.

Calculating L is also straightforward. The one minor complication is that some configurations result in a variable length password. In this case, assume the shortest possible length the configuration could produce.

The example password from the "PHILOSOPHY" section (!15.play.MAJOR.fresh.FLAT.23!) was generated using the preset WEB32. This preset uses four words of between four and five letters long, with the case of each word randomly set to all lower or all upper as the basis for the password, it then chooses two pairs of random digits as extra words to go front and back, before separating each word with a copy of a randomly chosen symbol, and padding the front and back of the password with a copy of a different randomly chosen symbol. This results in passwords that contain a mix of cases, digits, and symbols, and are between 27 and 31 characters long. If we add these values into the formula we find that the blind entropy for passwords created with this preset is:

    Eb = Log(2)(95^27) = 163 bits

This is spectacularly secure! And, this is the most likely kind of attack for a password to face. However, to have confidence in the password we must also now calculate the entropy when the attacker knows everything about how the password was generated.

We will calculate the entropy resulting from the same WEB32 config being used to generate a password using the sample library file that ships with the module.

The number of permutations the attacker needs to check is purely the product of possibly results for each random choice made during the assembly of the password.

Lets start with the words that will form the core of the password. The configuration chooses four words of between four and five letters long from the dictionary, and then randomises their case, effectively making it a choice from twice as many words (each word in each case).

The sample dictionary file contains 698 words of the configured length, which doubles to 1396. Choosing four words from that very large alphabet gives a starting point of 1396^4, or 3,797,883,801,856 permutations.

Next we need to calculate the permutations for the separator character. The configuration specifies just nine permitted characters, and we choose just one, so that equates to 9 permutations.

Similarly, the padding character on the end is chosen from 13 permitted symbols giving 13 more permutations.

Finally, there are four randomly chosen digits, giving 10^4, or 10,000 permutations.

The total number of permutations is the product of all these permutations:

    Pseen = 3,797,883,801,856 * 9 * 13 * 10,000 = 2.77x10^17
    

Finally, we convery this to entropy by taking the base 2 log:

    Eseen = Log(2)2.77x10^17 = ~57bits
    

What this means is that most probably, passwords generated with this preset using the sample dictionary file are spectacularly more secure than even 12 randomly chosen characters, and, that in the very unlikely event that an attackers knows absolutely everything about how the password was generated, it is still significantly more secure than 8 randomly chosen characters.

Because the exact strength of the passwords produced by this module depend on the configuration and dictionary file used, the constructor does the above math when creating an XKPasswd object, and throws a warning if either the blind entropy falls below 78bits, or the seen entropy falls below 52 bits.

SUBROUTINES/METHODS

DICTIONARY FILES

XKPasswd instances load their word lists from text files. The constructor loads the words contained in a single file into memory when assembling an XKPasswd object. Once constructed, the object never reads from the file again. Throughout this documentation, the text file containing the words to be used is referred to as the Dictionary File, and specified via the dictionary_file_path config variable.

The rules for the formatting of dictionary files are simple. Dictionary files must contain one word per line. Words shorter than four letters will be ignored, as will all lines starting with the # symbol.

This format is the same as that of the standard Unix Words file, usually found at /usr/share/dict/words on Unix and Linux operating systems (including OS X).

In order to produce secure passwords it's important to use a dictionary file that contains a large selection of words with a good mix of different word lengths.

A sample dictionary file (sample_dict.txt) is distributed with this module.

CONFIGURATION HASHREFS

A number of subroutines require a configuration hashref as an argument. The following are the valid keys for that hashref, what they mean, and what values are valid for each.

PRESETS

For ease of use, this module comes with a set of pre-defined presets. Preset names can be used in place of config hashrefs when instantiating an XKPasswd object, or, when using the functional interface to XKPasswd.

Presets can be used as-is, or, they can be used as a starting point for creating your own config hashref, as demonstrated by the following example:

    my $config = XKPasswd->preset_config('XKCD');
    $config->{separator_character} = q{ }; # change the separator to a space
    my $xkpasswd = XKPasswd->new('sample_dict.txt', $config);
    

If you only wish to alter a small number of config settings, the following two shortcuts might be of interest (both produce the same result as the example above):

    my $config = XKPasswd->preset_config('XKCD', {separator_character => q{ }});
    my $xkpasswd = XKPasswd->new('sample_dict.txt', $config);
    

or

    my $xkpasswd = XKPasswd->new('sample_dict.txt', 'XKCD', {separator_character => q{ }});

For more see the definitions for the class functions defined_presets(), presets_to_string(), preset_description(), and preset_config().

The following presets are defined:

ENTROPY CHECKING

For security reasons, this module's default behaviour is to warn (using carp()) when ever the loaded combination dictionary file and configuration would result in low-entropy passwords. When the constructor is invoked, or when a new dictionary file or new config hashref are loaded into an object (using dictionary() or config()) the entropy of the resulting new state of the object is calculated and checked against the defined minima.

Entropy is calculated and checked for two scenarios. Firstly, for the best-case scenario, when an attacker has no prior knowledge about the password, and must resort to brute-force attacks. And secondly, for the worst-case scenario, when the attacker is assumed to know that this module was used to generate the password, and, that the attacker has a copy of the dictionary file and config settings used to generate the password.

Entropy checking is controlled via three package variables:

CAVEATS

The entropy calculations make some assumptions which may in some cases lead to the results being inaccurate. In general, an attempt has been made to always round down, meaning that in reality the entropy of the produced passwords may be higher than the values calculated by the package.

When calculating the entropy for brute force attacks on configurations that can result in variable length passwords, the shortest possible password is assumed.

When calculating the entropy for brute force attacks on configurations that contain at least one symbol, it is assumed that an attacker would have to brute-force-check 33 symbols. This is the same value used by Steve Gibson's Password Haystacks calculator (https://www.grc.com/haystack.htm).

When calculating the entropy for worst-case attacks on configurations that contain symbol substitutions where the replacement is more than 1 character long the possible extra length is ignored.

RANDOM FUNCTIONS

In order to avoid this module relying on any non-standard modules, the default source of randomness is Perl's built-in rand() function. This provides a reasonable level of randomness, and should suffice for most users, however, some users will prefer to make use of one of the many advanced randomisation modules in CPAN, or, reach out to a web service like http://random.org for their randomness. To facilitate both of these options, this module uses a cache of randomness, and allows a custom randomness function to be specified by setting the config variable random_function to a coderef to the function.

Functions specified in this way must take exactly one argument, an integer number greater than zero, and then return that many random decimal numbers between zero and one.

The random function is not called each time a random number is needed, instead a number of random numbers are generated at once, and cached until they are needed. The amount of random numbers generated at once is controlled by the random_increment config variable. The reason the module works in this way is to facilitate web-based services which prefer you to generate many numbers at once rather than invoking them repeatedly. For example, Random.org ask developers to query them for more random numbers less frequently.

FUNCTIONAL INTERFACE

Although the package was primarily designed to be used in an object-oriented way, there is a functional interface too. The functional interface initialises an object internally and then uses that object to generate a single password. If you only need one password, this is no less efficient than the object-oriented interface, however, if you are generating multiple passwords it is much less efficient.

There is only a single function exported by the module:

xkpasswd()

    my $password = xkpasswd('sample_dict.txt');
    

This function call is equivalent to the following Object-Oriented code:

    my $xkpasswd = XKPasswd->new('sample_dict.txt');
    my $password = $xkpasswd->password();
    

This function passes its arguments through to the constructor, so all arguments that are valid in new() are valid here.

This function Croaks if there is a problem generating the password.

Note that it is inefficient to use this function to generate multiple passwords because the dictionary file will be re-loaded, and the entropy calculations for ensuring security repeated, each time a password is generated.

CONSTRUCTOR

    # create a new instance with the default config
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt');
    
    # create an instance from the preset 'XKCD'
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', 'XKCD');
    
    # create an instance based on the preset 'XKCD' with one customisation
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', 'XKCD', {separator_character => q{ }});
    
    # create an instance from a config hashref
    my $xkpasswd_instance = XKPasswd->new('sample_dict.txt', $config_hashref);

The constructor must be called via the package name, and at least one argument must be passed, the path to the dictionary file to be used when generating the words.

If only one argument is passed the default values are used for all config keys. To use a different configuration, a second argument can be passed. If this argument is a scalar it will be assumed to be the name of a preset, and if it is a hashref it is assumed to be the config to load.

If a preset name is passed as a second argument, a hashref with config key overrides can be passed as a third argument. If the second argument is a hashref the third argument is ignored.

CLASS METHODS

NOTE - All class methods must be invoked via the package name, or they will croak.

clone_config()

    my $clone = XKPasswd->clone_config($config);
    

This function must be passed a valid config hashref as the first argument or it will croak. The function returns a hashref.

config_stats()

    my %stats = XKPasswd->config_stats($config);
    

This function requires one argument, a valid config hashref. It returns a hash of statistics about a given configuration. The hash is indexed by the following:

There is one scenario in which the calculated maximum length will not be reliably accurate, and that's when a character substitution with a length greater than 1 is specified, and padding_type is not set to ADAPTIVE. If the config passed contains such a character substitution, the length will be calculated ignoring the possibility that one or more extra characters could be introduced depending on how many, if any, of the long substitutions get triggered by the randomly chosen words. If this happens the function will also carp with a warning.

config_to_string()

    my $config_string = XKPasswd->config_to_string($config);
    

This function returns the content of the passed config hashref as a scalar string. The function must be passed a valid config hashref or it will croak.

default_config()

    my $config = XKPasswd->default_config();

This function returns a hashref containing a config with default values.

This function can optionally be called with a single argument, a hashref containing keys with values to override the defaults with.

    my $config = XKPasswd->default_config({num_words => 3});
    

When overrides are present, the function will carp if an invalid key or value is passed, and croak if the resulting merged config is invalid.

This function is a shortcut for preset_config(), and the two examples above are equivalent to the following:

    my $config = XKPasswd->preset_config('DEFAULT');
    my $config = XKPasswd->preset_config('DEFAULT', {num_words => 3});

defined_presets()

    my @preset_names = XKPasswd->defined_presets();
    

This function returns the list of defined preset names as an array of scalars.

is_valid_config()

    my $is_ok = XKPasswd->is_valid_config($config);
    

This function must be passed a hashref to test as the first argument or it will croak. The function returns 1 if the passed config is valid, and 0 otherwise.

Optionally, any truthy value can be passed as a second argument to indicate that the function should croak on invalid configs rather than returning 0;

    use English qw( -no_match_vars );
    eval{
        XKPasswd->is_valid_config($config, 'do_croak');
    }or do{
        print "ERROR - config is invalid because: $EVAL_ERROR\n";
    }

preset_config()

    my $config = XKPasswd->preset_config('XKCD');
    

This function returns the config hashref for a given preset. See above for the list of available presets.

The first argument this function accpets is the name of the desired preset as a scalar. If an invalid name is passed, the function will carp. If no preset is passed the preset DEFAULT is assumed.

This function can optionally accept a second argument, a hashref containing keys with values to override the defaults with.

    my $config = XKPasswd->preset_config('XKCD', {case_transform => 'INVERT'});
    

When overrides are present, the function will carp if an invalid key or value is passed, and croak if the resulting merged config is invalid.

preset_description()

    my $description = XKPasswd->preset_description('XKCD');
    

This function returns the description for a given preset. See above for the list of available presets.

The first argument this function accpets is the name of the desired preset as a scalar. If an invalid name is passed, the function will carp. If no preset is passed the preset DEFAULT is assumed.

presets_to_string()

    print XKPasswd->presets_to_string();
    

This function returns a string containing a description of each defined preset and the configs associated with the presets.

METHODS

NOTE - all methods must be invoked on an XKPasswd object or they will croak.

config()

    my $config = $xkpasswd_instance->config(); # getter
    $xkpasswd_instance->config($config); # setter

When called with no arguments the function returns a clone of the instance's config hashref.

When called with a single argument the function sets the config of the instance to a clone of the passed hashref. If present, the argument must be a hashref, and must contain valid config keys and values. The function will croak if an invalid config is passed.

config_string()

    my $config_string = $xkpasswd_instance->config_string();
    

This function returns the content of the passed config hashref as a scalar string. The function must be passed a valid config hashref or it will croak.

dictionary()

    print $xkpasswd_instance->dictionary();
    $xkpasswd_instance->dictionary('sample_dict.txt');
    

When called with no arguments this function returns the path to the currently loaded dictionary file. To load a dictionary file into an instance call this function with the path to the dictionary file.

password()

    my $password = $xkpasswd_instance->password();
    

This function generates a random password based on the instance's loaded config and returns it as a scalar. The function takes no arguments.

The function croaks if there is an error generating the password. The most likely cause of and error is the random number generation, particularly if the loaded random generation function relies on a cloud service or a non-standard library.

passwords()

    my @passwords = $xkpasswd_instance->passwords(10);
    

This function generates a number of passwords and returns them all as an array.

The function uses password() to genereate the passwords, and hence will croak if there is an error generating any of the requested passwords.

stats()

    my %stats = $xkpasswd_instance->stats();
    

This function generates a hash containing stats about the instance indexed by the following keys:

status()

    print $xkpasswd_instance->status();
    

Generates a string detailing the internal status of the instance. Below is a sample status string:

    *DICTIONARY*
    File path: /usr/share/dict/words
    # words: 234252
    # words of valid length: 87066

    *CONFIG*
    case_transform: 'CAPITALISE'
    character_substitutions: {}
    num_words: '4'
    padding_digits_after: '0'
    padding_digits_before: '0'
    padding_type: 'NONE'
    random_function: XKPasswd::basic_random_generator
    random_increment: '4'
    separator_character: '-'
    word_length_max: '8'
    word_length_min: '4'

    *RANDOM NUMBER CACHE*
    # in cache: 0

    *PASSWORD STATISTICS*
    Password length: between 19 & 35
    Brute-Force permutations: between 2.29x10^33 & 2.85x10^61 (average 2.56x10^47)
    Permutations (given dictionary & config): 5.74x10^19
    Brute-Force Entropy (in bits): between 110 and 204 (average 157)
    Entropy (given dictionary & config): 65bits
    Passwords Generated: 0

update_config()

    $xkpasswd_instance->update_config({separator_character => '+'});
    

The function updates the config within an XKPasswd instance. A hashref with the config options to be changed must be passed. The function returns a reference to the instance to enable function chaining. The function will croak if the updated config would be invalid in some way. Note that if this happens the running config will not have been altered in any way.

DIAGNOSTICS

By default this module does all of it's error notification via the functions carp(), croak(), and confess() from the Carp module. Optionally, all error messages can also be printed. To enable the printing of messages, set $XKPasswd::LOG_ERRORS to a truthy value. All error messages will then be printed to the stream at $XKPasswd::LOG_STREAM, which is set to STDERR by default.

Ordinarily this module produces very little output, to enable more verbose output $XKPasswd::DEBUG can be set to a truthy value. If this is set, all debug messages will be printed to the stream $XKPasswd::LOG_STREAM.

This module produces output at three severity levels:

CONFIGURATION AND ENVIRONMENT

This module does not currently support configuration files, nor does it currently interact with the environment. It may do so in future versions.

DEPENDENCIES

This module uses the following standard Perl modules:

The module can also optionally use the following non-standard Perl modules:

INCOMPATIBILITIES

This module has no known incompatibilities.

BUGS AND LIMITATIONS

There are no known bugs in this module.

Please report problems to Bart Busschots (mailto:bart@bartificer.net) Patches are welcome.

LICENCE AND COPYRIGHT

Copyright (c) 2014, Bart Busschots T/A Bartificer Web Solutions All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

AUTHOR

Bart Busschots (mailto:bart@bartificer.net)