From 16e50619ea671e855544f9a2082292b14ff8f3d8 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 17 Jul 2023 14:31:51 +0000 Subject: [PATCH] Chore: import running Perl-based code from July 2023 --- English.dict.txt | 1450 ++++++ XKPasswd.pm | 4104 +++++++++++++++++ XKPasswd/Util.pm | 305 ++ contrib/ajax-loader.gif | Bin 0 -> 404 bytes .../icons/accept.png | Bin 0 -> 781 bytes .../famfamfam_silk_icons_v013/icons/add.png | Bin 0 -> 733 bytes .../icons/delete.png | Bin 0 -> 715 bytes .../famfamfam_silk_icons_v013/icons/error.png | Bin 0 -> 666 bytes .../icons/exclamation.png | Bin 0 -> 701 bytes .../icons/information.png | Bin 0 -> 778 bytes contrib/famfamfam_silk_icons_v013/readme.txt | 22 + .../icons-shadowless/cross-white.png | Bin 0 -> 674 bytes .../icons-shadowless/minus-white.png | Bin 0 -> 625 bytes .../icons-shadowless/plus-white.png | Bin 0 -> 648 bytes contrib/jquery-2.1.1.min.js | 4 + contrib/password_strength.png | Bin 0 -> 93029 bytes error-browser-maybe-unsupported.tt | 6 + error-browser-unsupported.tt | 6 + favicon.png | Bin 0 -> 525 bytes index.cgi | 204 + index.js | 1636 +++++++ index.tt | 303 ++ li.png | Bin 0 -> 300 bytes sideBanner.png | Bin 0 -> 49155 bytes xkpasswd.css | 291 ++ 25 files changed, 8331 insertions(+) create mode 100644 English.dict.txt create mode 100644 XKPasswd.pm create mode 100644 XKPasswd/Util.pm create mode 100644 contrib/ajax-loader.gif create mode 100644 contrib/famfamfam_silk_icons_v013/icons/accept.png create mode 100644 contrib/famfamfam_silk_icons_v013/icons/add.png create mode 100644 contrib/famfamfam_silk_icons_v013/icons/delete.png create mode 100644 contrib/famfamfam_silk_icons_v013/icons/error.png create mode 100644 contrib/famfamfam_silk_icons_v013/icons/exclamation.png create mode 100644 contrib/famfamfam_silk_icons_v013/icons/information.png create mode 100644 contrib/famfamfam_silk_icons_v013/readme.txt create mode 100644 contrib/fugue-icons-3.5.6/icons-shadowless/cross-white.png create mode 100644 contrib/fugue-icons-3.5.6/icons-shadowless/minus-white.png create mode 100644 contrib/fugue-icons-3.5.6/icons-shadowless/plus-white.png create mode 100644 contrib/jquery-2.1.1.min.js create mode 100644 contrib/password_strength.png create mode 100644 error-browser-maybe-unsupported.tt create mode 100644 error-browser-unsupported.tt create mode 100644 favicon.png create mode 100755 index.cgi create mode 100644 index.js create mode 100644 index.tt create mode 100644 li.png create mode 100644 sideBanner.png create mode 100644 xkpasswd.css diff --git a/English.dict.txt b/English.dict.txt new file mode 100644 index 0000000..b4e0b61 --- /dev/null +++ b/English.dict.txt @@ -0,0 +1,1450 @@ +# +# Sample dictionary for use with the xkpasswd.pm Perl Module compiled by Bart Busschots +# +# Copyright 2011 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 BART BUSSCHOTS T/A BARTIFICER WEB SOLUTIONS ''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 +# BART BUSSCHOTS T/A BARTIFICER WEB SOLUTIONS 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. +# +Africa +Alabama +Alaska +America +Amsterdam +April +Arizona +Asia +Athens +August +Australia +Austria +Barbados +Belfast +Belgium +Berlin +Botswana +Brazil +Britain +British +Bulgaria +California +Canada +Chile +China +Colombia +Congo +Copenhagen +Cuba +Damascus +December +Delaware +Denmark +Dublin +Earth +Egypt +England +English +Europe +February +Fiji +Finland +Florida +France +French +Friday +Germany +Gibraltar +God +Greece +Greek +Havana +Hawaii +Holland +I +Iceland +India +Indian +Ireland +Italy +Jamaica +Japan +Japanese +Jerusalem +Jordan +July +June +Jupiter +Kentucky +Kenya +Korea +Lisbon +London +Madrid +Malta +March +Mark +Mars +Maryland +Mercury +Mexico +Monday +Montana +Moon +Moscow +Nepal +Neptune +Netherlands +Nevada +Norway +November +October +Ohio +Oslo +Panama +Paris +Peru +Pluto +Poland +Portugal +Rome +Russia +Saturday +Saturn +Scotland +September +Singapore +Spain +Sun +Sunday +Sweden +Texas +Tokyo +Tuesday +Uranus +Venus +Vermont +Virginia +Wales +Warsaw +Washington +Wednesday +a +able +about +above +across +act +action +actually +add +addition +adjective +advance +afraid +after +again +against +age +ago +agree +agreed +ahead +air +airplane +all +allow +almost +alone +along +already +also +although +always +am +among +amount +an +and +anger +angle +angry +animal +another +answer +any +anything +appear +apple +are +area +arm +arms +army +around +arrive +arrived +art +article +as +ask +at +attempt +aunt +away +baby +back +bad +bag +ball +bank +banker +base +basket +battle +bay +be +bean +bear +beat +beautiful +beauty +became +because +become +bed +been +before +began +begin +behind +being +believe +bell +belong +below +beside +best +better +between +beyond +bicycle +big +bill +bird +birds +bit +black +block +blood +blow +blue +board +boat +body +bone +bones +book +born +borrow +both +bottle +bottom +box +boy +branch +branches +bread +break +bridge +bright +bring +broad +broke +broken +brother +brought +brown +bug +build +building +built +burn +burning +bus +business +busy +but +butter +buy +by +cake +call +came +can +cannot +capital +captain +car +care +carefully +carry +case +cat +catch +cattle +caught +cause +cells +cent +center +cents +century +certain +chair +chance +change +character +charge +chart +check +chief +child +childhood +children +choose +church +cigarette +circle +city +class +clean +clear +climbed +clock +close +cloth +clothes +cloud +coast +coat +cold +college +color +colour +column +come +common +company +compare +complete +compound +condition +conditions +consider +considerable +consonant +contain +continue +continued +control +cook +cool +copy +corn +corner +correct +cost +cotton +could +count +country +course +cover +covered +cows +create +cried +crops +cross +crowd +cry +cup +current +cut +daily +dance +dare +dark +date +daughter +day +dead +deal +dear +death +decide +decided +decimal +deep +degree +delight +demand +describe +desert +design +desire +destroy +details +determine +developed +device +dictionary +did +die +died +difference +different +difficult +dig +dinner +direct +direction +discover +discovered +dish +distance +distant +divide +divided +division +do +doctor +does +dog +dollar +dollars +done +door +double +doubt +down +draw +drawing +dream +dress +dried +drink +drive +drop +dry +duck +during +dusk +duty +each +ear +early +ears +earth +east +easy +eat +edge +effect +effort +egg +eggs +eight +either +electric +electricity +elements +else +end +enemy +energy +engine +enjoy +enough +enter +entered +entire +equal +equation +escape +especially +etching +even +evening +ever +every +everyone +everything +exactly +example +except +exciting +exercise +expect +experience +experiment +explain +express +eye +face +fact +factories +factors +fail +fair +fall +family +famous +fancy +far +farm +farmers +fast +fat +father +favor +fear +feed +feel +feeling +feet +fell +fellow +felt +fence +few +field +fifteen +fifth +fifty +fig +fight +figure +fill +filled +finally +find +fine +finger +fingers +finish +finished +fire +firm +first +fish +fit +five +fix +flat +flier +floor +flow +flower +flowers +fly +follow +food +fool +foot +for +force +foreign +forest +forever +forget +form +fortieth +forty +forward +found +four +fraction +free +fresh +friend +friends +from +front +fruit +full +fun +further +future +gain +galaxy +game +garden +gas +gate +gather +gave +general +gentle +gentleman +get +gift +girl +give +gives +glad +glass +glossary +go +goes +gold +gone +good +goodbye +got +govern +government +grain +grass +grave +gray +great +green +grew +ground +group +grow +grown +guard +guess +guide +gun +had +hair +half +hall +halt +hand +hang +happen +happened +happy +hard +has +hat +have +he +head +health +hear +heard +heart +heat +heaven +heavy +height +held +hello +help +her +here +hers +high +hill +him +himself +his +history +hit +hold +hole +home +honor +hope +horse +hot +hour +hours +house +how +however +huge +human +hundred +hunger +hunt +hunting +hurry +hurt +husband +ice +idea +if +ill +important +in +inch +inches +include +increase +indeed +indicate +industry +information +insects +inside +instead +instruments +interest +into +iron +is +island +it +its +itself +job +join +joined +journey +joy +judge +jump +jumped +just +keep +kept +key +kill +killed +kind +king +kiss +kitchen +knew +know +known +labor +ladder +lady +lake +land +language +large +last +late +later +laugh +laughed +laughter +law +lay +lead +leader +learn +least +leave +led +left +leg +legs +lend +length +less +let +letter +level +liar +lie +life +lift +lifted +light +like +likely +line +list +listen +little +live +located +lone +long +look +lord +lose +loss +lost +lot +loud +love +low +lower +machine +mad +made +mail +main +major +make +man +manner +many +map +march +mark +market +marry +master +match +material +matter +may +maybe +mayor +me +mean +measure +meat +meet +meeting +melody +member +members +men +met +metal +method +middle +might +mile +milk +million +mind +mine +minute +minutes +miss +mister +modern +molecules +moment +money +month +months +moon +more +morning +most +mother +mountain +mouth +move +movement +much +mud +music +must +my +nail +name +nation +natural +nature +near +nearly +necessary +neck +need +needle +neighbor +neither +nerve +never +new +news +next +nice +niece +night +nine +no +noise +none +noon +nor +north +northern +nose +not +note +nothing +notice +noun +now +number +numeral +object +observe +ocean +of +off +offer +office +often +oh +oil +old +on +once +one +only +open +opinion +opposite +or +order +orderly +other +ought +our +out +outer +outside +over +own +oxygen +page +paid +pain +paint +pair +paper +paragraph +park +part +partial +particular +party +pass +passed +past +pattern +pay +peace +people +per +perfect +perhaps +period +person +phrase +pick +picked +picture +piece +place +plain +plains +plan +plane +planet +plant +plants +play +pleasant +please +pleasure +plural +poem +point +pole +poor +position +possible +pot +pounds +power +practice +prepare +prepared +present +president +presidents +press +pretty +price +printed +probable +probably +problem +process +produce +products +promise +property +proud +prove +provide +public +pull +pulled +pure +push +pushed +put +quarter +queen +question +questions +quick +quickly +quiet +quite +race +radio +rain +raise +raised +ran +rather +reach +reached +read +ready +real +realize +really +reason +receive +received +record +red +region +remain +remember +repeated +reply +report +represent +require +resent +rest +result +return +rhythm +rich +ridden +ride +right +ring +rise +river +road +rock +roll +rolled +room +root +rope +rose +round +row +rule +run +rush +sad +safe +safety +said +sail +salt +same +sand +sat +save +saw +say +says +scale +scene +school +science +scientists +score +sea +season +seat +second +section +see +seed +seeds +seem +seen +self +sell +send +sense +sent +sentence +separate +serve +service +set +settle +settled +seven +several +shade +shake +shall +shape +share +sharp +she +shine +ship +shirt +shoe +shoes +shop +shore +short +shot +should +shoulder +shout +shouted +show +shown +sick +side +sight +sign +signal +silent +silver +similar +simple +since +sing +single +sir +sister +sit +six +size +skin +sky +sleep +slept +slow +slowly +small +smell +smiled +smoke +snow +so +soft +soil +sold +soldier +soldiers +solution +some +someone +something +sometimes +son +song +soon +sorry +sort +sound +south +southern +space +speak +special +speed +spell +spend +spent +spoke +spot +spread +spring +square +stand +star +stars +start +state +statement +station +stay +steel +step +stick +still +stock +stone +stood +stop +store +storm +story +straight +strange +stranger +stream +street +strength +stretched +strike +string +strong +student +students +study +subject +substances +succeed +success +such +sudden +suddenly +suffer +suffix +sugar +suggested +suit +sum +summer +sun +supply +suppose +sure +surface +surprise +sweet +swim +syllables +symbols +system +table +tail +take +taken +talk +tall +taste +teach +teacher +team +tear +tell +temperature +ten +terms +test +than +thank +that +the +their +them +themselves +then +there +therefore +these +they +thick +thin +thing +think +third +thirteen +this +those +though +thought +thousand +thousands +three +threw +through +throw +thrown +thus +tie +tied +till +time +tiny +to +today +together +told +tomorrow +tone +too +took +tools +top +tore +total +touch +toward +town +track +trade +train +training +travel +tree +triangle +tried +tries +trip +trouble +truck +true +trust +try +tube +turn +twelve +twenty +two +type +uncle +under +underline +understand +understood +unit +until +up +upon +us +use +usual +usually +valley +value +various +verb +very +view +village +visit +voice +vowel +wagon +wait +walk +wall +want +wants +war +warm +was +wash +watch +water +wave +waves +way +we +weak +wear +weather +wedge +week +weight +welcome +well +went +were +west +western +wet +what +wheat +wheel +wheels +when +where +whether +which +while +white +who +whole +whom +whose +why +wide +wife +wild +will +win +wind +window +wing +wings +winter +wire +wise +wish +with +within +without +woman +women +won +wonder +wood +word +wore +work +workers +world +worn +worth +would +write +written +wrong +wrote +yard +year +yellow +yes +yesterday +yet +you +young +your +yourself diff --git a/XKPasswd.pm b/XKPasswd.pm new file mode 100644 index 0000000..9109981 --- /dev/null +++ b/XKPasswd.pm @@ -0,0 +1,4104 @@ +package XKPasswd; + +# import required modules +use strict; +use warnings; +use Carp; # for nicer 'exception' handling for users of the module +use English qw( -no_match_vars ); # for more readable code +use B qw(svref_2object); # for code ref->name conversion +use Math::Round; # for round() +use Math::BigInt; # for the massive numbers needed to store the permutations + +# import (or not) optional modules +my $_CAN_STACK_TRACE = eval{ + require Devel::StackTrace; # for better error reporting when debugging +}; + +## no critic (ProhibitAutomaticExportation); +use base qw( Exporter ); +our @EXPORT = qw( xkpasswd ); +## use critic + +# Copyright (c) 2014, Bart Busschots T/A Bartificer Web Solutions All rights +# reserved. +# +# Code released under the FreeBSD license (included in the POD at the bottom of +# this file) + +#============================================================================== +# Code +#============================================================================== + +# +# 'Constants'------------------------------------------------------------------ +# + +# version info +use version; our $VERSION = qv('2.1_01'); + +# acceptable entropy levels +our $ENTROPY_MIN_BLIND = 78; # 78 bits - equivalent to 12 alpha numeric characters with mixed case and symbols +our $ENTROPY_MIN_SEEN = 52; # 52 bits - equivalent to 8 alpha numeric characters with mixed case and symbols +our $SUPRESS_ENTROPY_WARNINGS = 'NONE'; # valid values are 'NONE', 'ALL', 'SEEN', or 'BLIND' (invalid values treated like 'NONE') + +# Logging configuration +our $LOG_STREAM = *STDERR; # default to logging to STDERR +our $LOG_ERRORS = 0; # default to not logging errors +our $DEBUG = 0; # default to not having debugging enabled + +# utility variables +my $_CLASS = 'XKPasswd'; + +# config key definitions +my $_KEYS = { + symbol_alphabet => { + req => 0, + ref => 'ARRAY', # ARRAY REF + validate => sub { # at least 2 scalar elements + my $key = shift; + unless(scalar @{$key} >= 2){ return 0; } + foreach my $symbol (@{$key}){ + unless(ref $symbol eq q{} && length $symbol == 1){ return 0; } + } + return 1; + }, + desc => 'An array ref containing at least 2 single-character scalars', + }, + separator_alphabet => { + req => 0, + ref => 'ARRAY', # ARRAY REF + validate => sub { # at least 2 scalar elements + my $key = shift; + unless(scalar @{$key} >= 2){ return 0; } + foreach my $symbol (@{$key}){ + unless(ref $symbol eq q{} && length $symbol == 1){ return 0; } + } + return 1; + }, + desc => 'An array ref containing at least 2 single-character scalars', + }, + padding_alphabet => { + req => 0, + ref => 'ARRAY', # ARRAY REF + validate => sub { # at least 2 scalar elements + my $key = shift; + unless(scalar @{$key} >= 2){ return 0; } + foreach my $symbol (@{$key}){ + unless(ref $symbol eq q{} && length $symbol == 1){ return 0; } + } + return 1; + }, + desc => 'An array ref containing at least 2 single-character scalars', + }, + word_length_min => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # int > 3 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key > 3){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer greater than three', + }, + word_length_max => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # int > 3 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key > 3){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer greater than three', + }, + num_words => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # an int >= 2 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key >= 2){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to two', + }, + separator_character => { + req => 1, + ref => q{}, # SCALAR + validate => sub { + my $key = shift; + unless(length $key == 1 || $key =~ m/^(NONE)|(RANDOM)$/sx){ return 0; } + return 1; + }, + desc => q{A scalar containing a single character, or the special value 'NONE' or 'RANDOM'}, + }, + padding_digits_before => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # an int >= 0 + my $key = shift; + unless($key =~ m/^\d+$/sx){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to zero', + }, + padding_digits_after => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # an int >= 0 + my $key = shift; + unless($key =~ m/^\d+$/sx){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to zero', + }, + padding_type => { + req => 1, + ref => q{}, # SCALAR + validate => sub { + my $key = shift; + unless($key =~ m/^(NONE)|(FIXED)|(ADAPTIVE)$/sx){ return 0; } + return 1; + }, + desc => q{A scalar containg one of the values 'NONE', 'FIXED', or 'ADAPTIVE'}, + }, + padding_characters_before => { + req => 0, + ref => q{}, # SCALAR + validate => sub { # positive integer or 0 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key >= 0){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to one', + }, + padding_characters_after => { + req => 0, + ref => q{}, # SCALAR + validate => sub { # positive integer or 0 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key >= 0){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to one', + }, + pad_to_length => { + req => 0, + ref => q{}, # SCALAR + validate => sub { # positive integer >= 12 + my $key = shift; + unless($key =~ m/^\d+$/sx && $key >= 12){ return 0; } + return 1; + }, + desc => 'A scalar containing an integer value greater than or equal to twelve', + }, + padding_character => { + req => 0, + ref => q{}, # SCALAR + validate => sub { + my $key = shift; + unless(length $key == 1 || $key =~ m/^(NONE)|(RANDOM)|(SEPARATOR)$/sx){return 0; } + return 1; + }, + desc => q{A scalar containing a single character or one of the special values 'NONE', 'RANDOM', or 'SEPARATOR'}, + }, + case_transform => { + req => 0, + ref => q{}, # SCALAR + validate => sub { + my $key = shift; + ## no critic (ProhibitComplexRegexes); + unless($key =~ m/^(NONE)|(UPPER)|(LOWER)|(CAPITALISE)|(INVERT)|(ALTERNATE)|(RANDOM)$/sx){ return 0; } + ## use critic + return 1; + }, + desc => q{A scalar containing one of the values 'NONE' , 'UPPER', 'LOWER', 'CAPITALISE', 'INVERT', 'ALTERNATE', or 'RANDOM'}, + }, + random_function => { + req => 1, + ref => q{CODE}, # Code ref + validate => sub { + return 1; # no validation to do other than making sure it's a code ref + }, + desc => q{A code ref to a function for generating n random numbers between 0 and 1}, + }, + random_increment => { + req => 1, + ref => q{}, # SCALAR + validate => sub { # positive integer >= 1, or 'AUTO' + my $key = shift; + unless(($key =~ m/^\d+$/sx && $key >= 1) || $key eq 'AUTO'){ return 0; } + return 1; + }, + desc => q{A scalar containing an integer value greater than or equal to one, or 'AUTO'}, + }, + character_substitutions => { + req => 0, + ref => 'HASH', # Hashref REF + validate => sub { + my $key = shift; + foreach my $char (keys %{$key}){ + unless(ref $char eq q{} && $char =~ m/^\w$/sx){ return 0; } # single char key + unless(ref $key->{$char} eq q{} && $key->{$char} =~ m/^\S+$/sx){ return 0; } + } + return 1; + }, + desc => 'An hash ref mapping characters to replace with their replacements - can be empty', + }, +}; + +# preset definitions +my $_PRESETS = { + DEFAULT => { + description => 'The default preset resulting in a password consisting of 3 random words of between 4 and 8 letters with alternating case separated by a random character, with two random digits before and after, and padded with two random characters front and back', + config => { + symbol_alphabet => [qw{! @ $ % ^ & * - _ + = : | ~ ? / . ;}], + word_length_min => 4, + word_length_max => 8, + 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 => 'ALTERNATE', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 'AUTO', + }, + }, + WEB32 => { + description => q{A preset for websites that allow passwords up to 32 characteres long.}, + config => { + padding_alphabet => [qw{! @ $ % ^ & * + = : | ~ ?}], + separator_alphabet => [qw{- + = . * _ | ~}, q{,}], + word_length_min => 4, + word_length_max => 5, + num_words => 4, + separator_character => 'RANDOM', + padding_digits_before => 2, + padding_digits_after => 2, + padding_type => 'FIXED', + padding_character => 'RANDOM', + padding_characters_before => 1, + padding_characters_after => 1, + case_transform => 'ALTERNATE', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 10, + }, + }, + WEB16 => { + description => 'A preset for websites that insist passwords not be longer than 16 characters.', + config => { + padding_alphabet => [qw{! @ $ % ^ & * + = : | ~ ?}], + separator_alphabet => [qw{- + = . * _ | ~}, q{,}], + word_length_min => 4, + word_length_max => 4, + num_words => 3, + separator_character => 'RANDOM', + padding_digits_before => 0, + padding_digits_after => 0, + padding_type => 'FIXED', + padding_character => 'RANDOM', + padding_characters_before => 1, + padding_characters_after => 1, + case_transform => 'RANDOM', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 8, + }, + }, + WIFI => { + description => 'A preset for generating 63 character long WPA2 keys (most routers allow 64 characters, but some only 63, hence the odd length).', + config => { + padding_alphabet => [qw{! @ $ % ^ & * + = : | ~ ?}], + separator_alphabet => [qw{- + = . * _ | ~}, q{,}], + word_length_min => 4, + word_length_max => 8, + num_words => 6, + separator_character => 'RANDOM', + padding_digits_before => 4, + padding_digits_after => 4, + padding_type => 'ADAPTIVE', + padding_character => 'RANDOM', + pad_to_length => 63, + case_transform => 'RANDOM', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 22, + }, + }, + APPLEID => { + description => 'A preset respecting the many prerequisites Apple places on Apple ID passwords. The preset also limits itself to symbols found on the iOS letter and number keyboards (i.e. not the awkward to reach symbol keyboard)', + config => { + padding_alphabet => [qw{! ? @ &}], + separator_alphabet => [qw{- : .}, q{,}], + word_length_min => 5, + word_length_max => 7, + num_words => 3, + separator_character => 'RANDOM', + padding_digits_before => 2, + padding_digits_after => 2, + padding_type => 'FIXED', + padding_character => 'RANDOM', + padding_characters_before => 1, + padding_characters_after => 1, + case_transform => 'RANDOM', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 12, + }, + }, + NTLM => { + description => 'A preset for 14 character Windows NTLMv1 password. WARNING - only use this preset if you have to, it is too short to be acceptably secure and will always generate entropy warnings for the case where the config and dictionary are known.', + config => { + padding_alphabet => [qw{! @ $ % ^ & * + = : | ~ ?}], + separator_alphabet => [qw{- + = . * _ | ~}, q{,}], + word_length_min => 5, + word_length_max => 5, + num_words => 2, + separator_character => 'RANDOM', + padding_digits_before => 1, + padding_digits_after => 0, + padding_type => 'FIXED', + padding_character => 'RANDOM', + padding_characters_before => 0, + padding_characters_after => 1, + case_transform => 'INVERT', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 5, + }, + }, + SECURITYQ => { + description => 'A preset for creating fake answers to security questions.', + config => { + word_length_min => 4, + word_length_max => 8, + num_words => 6, + separator_character => q{ }, + padding_digits_before => 0, + padding_digits_after => 0, + padding_type => 'FIXED', + padding_character => 'RANDOM', + padding_alphabet => [qw{. ! ?}], + padding_characters_before => 0, + padding_characters_after => 1, + case_transform => 'NONE', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 7, + }, + }, + XKCD => { + description => 'A preset for generating passwords similar to the example in the original XKCD cartoon, but with a dash to separate the four random words, and the capitalisation randomised to add sufficient entropy to avoid warnings.', + config => { + word_length_min => 4, + word_length_max => 8, + num_words => 4, + separator_character => q{-}, + padding_digits_before => 0, + padding_digits_after => 0, + padding_type => 'NONE', + case_transform => 'RANDOM', + random_function => \&XKPasswd::basic_random_generator, + random_increment => 8, + }, + }, +}; + +# +# Constructor ----------------------------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : CONSTRUCTOR (CLASS) +# Purpose : Instantiate an object of type XKPasswd +# Returns : An object of type XKPasswd +# Arguments : 1. The path to a dictionary file +# 2. OPTIONAL - the name of a preset as a scalar (an empty string, +# undef, or 'DEFAULT' to get the default config) +# -OR- +# A hashref containing a full valid config +# 3. OPTIONAL - a hashref continaing any keys from the preset to be +# overridden (ignored if a hashref is passed as the second arg) +# Throws : Croaks if the function is called in an invalid way, or with an +# invalid config +# Notes : +# See Also : For valid configuarion options see POD documentation below +sub new{ + my $class = shift; + my $dictionary_path = shift; + my $preset = shift; + my $preset_override = shift; + + # validate args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of constructor'); + } + unless(defined $dictionary_path && -f $dictionary_path){ + $_CLASS->_error('a valid dictionary path must be passed as the first argument'); + } + + # before going any further, check the presets if debugging (doing later may cause an error before we test) + if($DEBUG){ + $_CLASS->_check_presets(); + } + + # assemble the config hashref + my $config = {}; + if(defined $preset){ + if(ref $preset eq q{}){ + #we were passed a preset name + + # expand blank preset name to 'DEFAULT' + $preset = 'DEFAULT' if $preset eq q{}; + + # convert name to caps + $preset = uc $preset; + + # make sure the preset exists + unless(defined $_PRESETS->{$preset}){ + $_CLASS->_error("invalid arguments - preset '$preset' does not exist"); + } + + # if overrides are defined, make sure they are hashrefs + if(defined $preset_override){ + unless(ref $preset_override eq 'HASH'){ + $_CLASS->_error('invalid arguments - if present, the third argument must be a hashref'); + } + } + + # load the preset + $config = $_CLASS->preset_config($preset, $preset_override); + }elsif(ref $preset eq 'HASH'){ + # we were passed a hashref, so use it as the config + $config = $preset; + }else{ + $_CLASS->_error('invalid argument - if present, the second argument must be a scalar or a hashref'); + } + }else{ + $config = $_CLASS->default_config(); + } + + # initialise the object + my $instance = { + # 'public' instance variables (none so far) + # 'PRIVATE' internal variables + _CONFIG => {}, + _DICTIONARY_PATH => q{}, # the path to the dictionary hashref + _CACHE_DICTIONARY_FULL => [], # a cache of all words found in the dictionary file + _CACHE_DICTIONARY_LIMITED => [], # a cache of all the words found in the dictionary file that meet the length criteria + _CACHE_ENTROPYSTATS => {}, # a cache of the entropy stats for the current combination of dictionary and config + _CACHE_RANDOM => [], # a cache of random numbers (as floating points between 0 and 1) + _PASSWORD_COUNTER => 0, # the number of passwords this instance has generated + }; + bless $instance, $class; + + # load the config + $instance->config($config); + + # load the dictionary (can't be done until the config is loaded) + $instance->dictionary($dictionary_path); + + # if debugging, print status + $_CLASS->_debug("instantiated $_CLASS object with the following details:\n".$instance->status()); + + # return the initialised object + return $instance; +} + +# +# Public Class (Static) functions --------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : generate a config hashref populated with the default values +# Returns : a hashref +# Arguments : 1. OPTIONAL - a hashref with config keys to over-ride when +# assembling the config +# Throws : Croaks if invoked in an invalid way. If passed overrides also +# Croaks if the resulting config is invalid, and Carps if passed +# on each invalid key passed in the overrides hashref. +# Notes : +# See Also : +sub default_config{ + my $class = shift; + my $overrides = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + # build and return a default config + return $_CLASS->preset_config('DEFAULT', $overrides); +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : generate a config hashref populated using a preset +# Returns : a hashref +# Arguments : 1. OPTIONAL - The name of the preset to assemble the config for +# as a scalar. If no name is passed, the preset 'DEFAULT' is +# used +# 2. OPTIONAL - a hashref with config keys to over-ride when +# assembling the config +# Throws : Croaks if invoked in an invalid way. If passed overrides also +# Croaks if the resulting config is invalid, and Carps if passed +# on each invalid key passed in the overrides hashref. +# Notes : +# See Also : +sub preset_config{ + my $class = shift; + my $preset = shift; + my $overrides = shift; + + # default blank presets to 'DEFAULT' + $preset = 'DEFAULT' unless defined $preset; + + # convert preset names to upper case + $preset = uc $preset; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(ref $preset eq q{}){ + $_CLASS->_error('invalid args - if present, the first argument must be a scalar'); + } + unless(defined $_PRESETS->{$preset}){ + $_CLASS->_error("preset '$preset' does not exist"); + } + if(defined $overrides){ + unless(ref $overrides eq 'HASH'){ + $_CLASS->_error('invalid args, overrides must be passed as a hashref'); + } + } + + # start by loading the preset + my $config = $_CLASS->clone_config($_PRESETS->{$preset}->{config}); + + # if overrides were passed, apply them and validate + if(defined $overrides){ + foreach my $key (keys %{$overrides}){ + # ensure the key is valid - skip it if not + unless(defined $_KEYS->{$key}){ + $_CLASS->_warn("Skippining invalid key=$key"); + next; + } + + # ensure the value is valid + eval{ + $_CLASS->_validate_key($key, $overrides->{$key}, 1); # returns 1 if valid + }or do{ + $_CLASS->_warn("Skipping key=$key because of invalid value. Expected: $_KEYS->{$key}->{desc}"); + next; + }; + + # save the key into the config + $config->{$key} = $overrides->{$key}; + } + unless($_CLASS->is_valid_config($config)){ + $_CLASS->_error('The default config combined with the specified overrides has resulted in an inalid config'); + } + } + + # return the config + return $config; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Clone a config hashref +# Returns : a hashref +# Arguments : 1. the config hashref to clone +# Throws : Croaks if called in an invalid way, or with an invalid config. +# Notes : This function needs to be updated each time a new non-scalar +# valid config key is added to the library. +# See Also : +sub clone_config{ + my $class = shift; + my $config = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $config && $_CLASS->is_valid_config($config)){ + $_CLASS->_error('invalid args - a valid config hashref must be passed'); + } + + # start with a blank hashref + my $clone = {}; + + # copy over all the scalar keys + KEY_TO_CLONE: + foreach my $key (keys %{$_KEYS}){ + # skip non-scalar keys + next KEY_TO_CLONE unless $_KEYS->{$key}->{ref} eq q{}; + + #if the key exists in the config to clone, copy it to the clone + if(defined $config->{$key}){ + $clone->{$key} = $config->{$key}; + } + } + + # deal with the non-scarlar keys + if(defined $config->{symbol_alphabet} && ref $config->{symbol_alphabet} eq 'ARRAY'){ + $clone->{symbol_alphabet} = []; + foreach my $symbol (@{$config->{symbol_alphabet}}){ + push @{$clone->{symbol_alphabet}}, $symbol; + } + } + if(defined $config->{separator_alphabet} && ref $config->{separator_alphabet} eq 'ARRAY'){ + $clone->{separator_alphabet} = []; + foreach my $symbol (@{$config->{separator_alphabet}}){ + push @{$clone->{separator_alphabet}}, $symbol; + } + } + if(defined $config->{padding_alphabet} && ref $config->{padding_alphabet} eq 'ARRAY'){ + $clone->{padding_alphabet} = []; + foreach my $symbol (@{$config->{padding_alphabet}}){ + push @{$clone->{padding_alphabet}}, $symbol; + } + } + $clone->{random_function} = $config->{random_function}; + if(defined $config->{character_substitutions}){ + $clone->{character_substitutions} = {}; + foreach my $key (keys %{$config->{character_substitutions}}){ + $clone->{character_substitutions}->{$key} = $config->{character_substitutions}->{$key}; + } + } + + # return the clone + return $clone; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : validate a config hashref +# Returns : 1 if the config is valid, 0 otherwise +# Arguments : 1. a hashref to validate +# 2. OPTIONAL - a true value to throw exception on error +# Throws : Croaks on invalid args, or on error if second arg is truthy +# Notes : This function needs to be updated each time a new valid config +# key is added to the library. +# See Also : +## no critic (ProhibitExcessComplexity); +sub is_valid_config{ + my $class = shift; + my $config = shift; + my $croak = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless($config && ref $config eq 'HASH'){ + $_CLASS->_error('invalid arguments'); + } + + # + # check the keys + # + + my @keys = sort keys %{$_KEYS}; + + # first ensure all required keys are present + foreach my $key (@keys){ + # skip non-required keys + next unless $_KEYS->{$key}->{req}; + + # make sure the passed config contains the key + unless(defined $config->{$key}){ + croak("Required key=$key not defined") if $croak; + return 0; + } + } + + # next ensure all passed keys have valid values + foreach my $key (@keys){ + # skip keys not present in the config under test + next unless defined $config->{$key}; + + # validate the key + eval{ + $_CLASS->_validate_key($key, $config->{$key}, 1); # returns 1 on success + }or do{ + croak("Invalid value for key=$key. Expected: ".$_KEYS->{$key}->{desc}) if $croak; + return 0; + }; + } + + # finally, make sure all other requirements are met + + # if there is a need for a symbol alphabet, make sure one is defined + if($config->{separator_character} eq 'RANDOM'){ + unless(defined $config->{symbol_alphabet} || defined $config->{separator_alphabet}){ + croak(qq{separator_character='$config->{separator_character}' requires either a symbol_alphabet or separator_alphabet be specified}) if $croak; + return 0; + } + } + + # if there is any kind of character padding, make sure a padding character is specified + if($config->{padding_type} ne 'NONE'){ + unless(defined $config->{padding_character}){ + croak(qq{padding_type='$config->{padding_type}' requires padding_character be set}) if $croak; + return 0; + } + if($config->{padding_character} eq 'RANDOM'){ + unless(defined $config->{symbol_alphabet} || defined $config->{padding_alphabet}){ + croak(qq{padding_character='$config->{padding_character}' requires either a symbol_alphabet or padding_alphabet be specified}) if $croak; + return 0; + } + } + } + + # if there is fixed character padding, make sure before and after are specified, and at least one has a value greater than 1 + if($config->{padding_type} eq 'FIXED'){ + unless(defined $config->{padding_characters_before} && defined $config->{padding_characters_after}){ + croak(q{padding_type='FIXED' requires padding_characters_before & padding_characters_after be set}) if $croak; + return 0; + } + unless($config->{padding_characters_before} + $config->{padding_characters_after} > 0){ + croak(q{padding_type='FIXED' requires at least one of padding_characters_before & padding_characters_after be greater than one (to use no padding use padding_type='NONE')}) if $croak; + return 0; + } + } + + # if there is adaptive padding, make sure a length is specified + if($config->{padding_type} eq 'ADAPTIVE'){ + unless(defined $config->{pad_to_length}){ + croak(q{padding_type='ADAPTIVE' requires pad_to_length be set}) if $croak; + return 0; + } + } + + # if we got this far, all is well, so return true + return 1; +} +## use critic + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Convert a config hashref to a String +# Returns : A scalar +# Arguments : 1. A config hashref +# Throws : Croaks on invalid invocation or with invalid args. Carps if there +# are problems with the config hashref. +# Notes : +# See Also : +sub config_to_string{ + my $class = shift; + my $config = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $config && ref $config eq 'HASH'){ + $_CLASS->_error('invalid arguments'); + } + + # assemble the string to return + my $ans = q{}; + foreach my $key (sort keys %{$_KEYS}){ + # skip undefined keys + next unless defined $config->{$key}; + + # make sure the key has the expected type + unless(ref $config->{$key} eq $_KEYS->{$key}->{ref}){ + $_CLASS->_warn("unexpected key type for key=$key (expected ref='$_KEYS->{$key}->{ref}', got ref='".ref $config->{$key}.q{')}); + next; + } + + # process the key + ## no critic (ProhibitCascadingIfElse); + if($_KEYS->{$key}->{ref} eq q{}){ + # the key is a scalar + $ans .= $key.q{: '}.$config->{$key}.qq{'\n}; + }elsif($_KEYS->{$key}->{ref} eq 'ARRAY'){ + # the key is an array ref + $ans .= "$key: ["; + my @parts = (); + foreach my $subval (sort @{$config->{$key}}){ + push @parts, "'$subval'"; + } + $ans .= join q{, }, @parts; + $ans .= "]\n"; + }elsif($_KEYS->{$key}->{ref} eq 'HASH'){ + $ans .= "$key: {"; + my @parts = (); + foreach my $subkey (sort keys %{$config->{$key}}){ + push @parts, "$subkey: '$config->{$key}->{$subkey}'"; + } + $ans .= join q{, }, @parts; + $ans .= "}\n"; + }elsif($_KEYS->{$key}->{ref} eq 'CODE'){ + $ans .= $key.q{: }.$_CLASS->_coderef_to_subname($config->{$key}).qq{\n}; + }else{ + # this should never happen, but just in case, throw a warning + $_CLASS->_warn("encounterd an un-handled key type ($_KEYS->{$key}->{ref}) for key=$key - skipping key"); + } + ## use critic + } + + # return the string + return $ans; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Return the description for a given preset +# Returns : A scalar string +# Arguments : 1. OPTIONAL - the name of the preset to get the description for, +# if no name is passed 'DEFAULT' is assumed +# Throws : Croaks on invalid invocation or invalid args +# Notes : +# See Also : +sub preset_description{ + my $class = shift; + my $preset = shift; + + # default blank presets to 'DEFAULT' + $preset = 'DEFAULT' unless defined $preset; + + # convert preset names to upper case + $preset = uc $preset; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(ref $preset eq q{}){ + $_CLASS->_error('invalid args - if present, the first argument must be a scalar'); + } + unless(defined $_PRESETS->{$preset}){ + $_CLASS->_error("preset '$preset' does not exist"); + } + + # return the description by loading the preset + return $_PRESETS->{$preset}->{description}; +} + + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Return a list of all valid preset names +# Returns : An array of preset names as scalars +# Arguments : NONE +# Throws : Croaks on invalid invocation. +# Notes : +# See Also : +sub defined_presets{ + my $class = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + # return the preset names + my @preset_names = sort keys %{$_PRESETS}; + return @preset_names; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Render the defined presets as a string +# Returns : A scalar +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : +# See Also : +sub presets_to_string{ + my $class = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + # loop through each preset and assemble the result + my $ans = q{}; + my @preset_names = $_CLASS->defined_presets(); + foreach my $preset (@preset_names){ + $ans .= $preset."\n===\n"; + $ans .= $_PRESETS->{$preset}->{description}."\n"; + $ans .= "\nConfig:\n---\n"; + $ans .= $_CLASS->config_to_string($_PRESETS->{$preset}->{config}); + $ans .= "\nStatistics:\n---\n"; + my %stats = $_CLASS->config_stats($_PRESETS->{$preset}->{config}); + if($stats{length_min} == $stats{length_max}){ + $ans .= "Length (fixed): $stats{length_min} characters\n"; + }else{ + $ans .= "Length (variable): between $stats{length_min} & $stats{length_max} characters\n"; + } + $ans .= "Random Numbers Needed Per-Password: $stats{random_numbers_required}\n"; + $ans .= "\n"; + } + + # return the string + return $ans; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Calculate statistics for a given configutration hashref. +# Returns : A hash of statistics indexed by the following keys: +# * 'length_min' - the minimum possible length of a password +# generated by the given config +# * 'length_max' - the maximum possible length of a password +# generated by the given config +# * 'random_numbers_required' - the number of random numbers needed +# to generate a single password using the given config +# Arguments : 1. A valid config hashref +# 2. OPTONAL - a truthy value to suppress warnings if the config +# is such that there are uncertainties in the calculations. +# E.g. the max length is uncertain when the config contains +# a character substitution with a replacement of length greater +# than 1 +# Throws : Croaks on invalid invocation or args, carps if multi-character +# substitutions are in use when not using adapive padding +# Notes : This function ignores character replacements, if one or more +# multi-character replacements are used when padding is not set +# to adaptive, this function will return an invalid max length. +# See Also : +sub config_stats{ + my $class = shift; + my $config = shift; + my $suppres_warnings = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $config && $_CLASS->is_valid_config($config)){ + $_CLASS->_error('invalid args - a valid config hashref must be passed'); + } + + # calculate the lengths + my $len_min = 0; + my $len_max = 0; + if($config->{padding_type} eq 'ADAPTIVE'){ + $len_min = $len_max = $config->{pad_to_length}; + }else{ + # calcualte the length of everything but the words themselves + my $len_base = 0; + if($config->{padding_type} eq 'FIXED'){ + $len_base += $config->{padding_characters_before}; + $len_base += $config->{padding_characters_after}; + } + if($config->{padding_digits_before} > 0){ + $len_base += $config->{padding_digits_before}; + if($config->{separator_character} ne 'NONE'){ + $len_base++; + } + } + if($config->{padding_digits_after} > 0){ + $len_base += $config->{padding_digits_after}; + if($config->{separator_character} ne 'NONE'){ + $len_base++; + } + } + if($config->{separator_character} ne 'NONE'){ + $len_base += $config->{num_words} - 1; + } + + # maximise and minimise the word lengths to calculate the final answers + $len_min = $len_base + ($config->{num_words} * $config->{word_length_min}); + $len_max = $len_base + ($config->{num_words} * $config->{word_length_max}); + } + + # calculate the number of random numbers needed to generate the password + my $num_rand = 0; + $num_rand += $config->{num_words}; + if($config->{case_transform} eq 'RANDOM'){ + $num_rand += $config->{num_words}; + } + if($config->{separator_character} eq 'RANDOM'){ + $num_rand++; + } + if(defined $config->{padding_character} && $config->{padding_character} eq 'RANDOM'){ + $num_rand++; + } + $num_rand += $config->{padding_digits_before}; + $num_rand += $config->{padding_digits_after}; + + # detect whether or not we need to carp about multi-character replacements + if($config->{padding_type} ne 'ADAPTIVE' && !$suppres_warnings){ + if(defined $config->{character_substitutions}){ + CHAR_SUB: + foreach my $char (keys %{$config->{character_substitutions}}){ + if(length $config->{character_substitutions}->{$char} > 1){ + $_CLASS->_warn('maximum length calculation is unreliable because the config contains a character substituion with length greater than 1 character'); + last CHAR_SUB; + } + } + } + } + + # assemble the result and return + my $stats = { + length_min => $len_min, + length_max => $len_max, + random_numbers_required => $num_rand, + }; + return %{$stats}; +} + +# +# Public Instance functions --------------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Get the path to the currently loaded dictionary file, or, load a +# new dictionary file +# Returns : A scalar with the path to the loaded dictionary file if called +# with no argument, or, a reference to the instance (to enable +# function chaining) if called with a file path +# Arguments : 1. OPTIONAL - the path to the config file to load as a scalar +# Throws : Croaks on invalid invocation, or, if there is a problem loading +# a dictionary file +# Notes : +# See Also : For description of dictionary file format, see POD documentation +# below +sub dictionary{ + my $self = shift; + my $path = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # decide if we're a 'getter' or a 'setter' + if(!(defined $path)){ + # we are a getter, so just return + return $self->{_DICTIONARY_PATH}; + }else{ + # we are a setter, so try load the dictionary + + # croak if we are called before the config has been loaded into the instance + unless(defined $self->{_CONFIG}->{word_length_min} && $self->{_CONFIG}->{word_length_max}){ + $_CLASS->_error('failed to load dictionary file - config has not been loaded yet'); + } + + # parse the file + my @cache_full = $_CLASS->_parse_words_file($path); + + # generate the valid word cache - croaks if too few words left after filtering + my @cache_limited = $_CLASS->_filter_word_list(\@cache_full, $self->{_CONFIG}->{word_length_min}, $self->{_CONFIG}->{word_length_max}); + + # if we got here all is well, so save the new path and caches into the object + $self->{_DICTIONARY_PATH} = $path; + $self->{_CACHE_DICTIONARY_FULL} = [@cache_full]; + $self->{_CACHE_DICTIONARY_LIMITED} = [@cache_limited]; + + # update the instance's entropy cache + $self->_update_entropystats_cache(); + } + + # return a reference to self + return 1; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Get a clone of the current config from an instance, or load a +# new config into the instance. +# Returns : A config hashref if called with no arguments, or, the instance +# if called with a hashref (to facilitate function chaining) +# Arguments : 1. OPTIONAL - a configuartion hashref +# Throws : Croaks if the function is called in an invalid way, with invalid +# arguments, or with an invalid config +# Notes : +# See Also : For valid configuarion options see POD documentation below +sub config{ + my $self = shift; + my $config = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # decide if we're a 'getter' or a 'setter' + if(!(defined $config)){ + # we are a getter - simply return a clone of our config + return $self._clone_config(); + }else{ + # we are a setter + + # ensure the config passed is a hashref + unless($config && ref $config eq 'HASH'){ + $_CLASS->_error('invalid arguments - the config passed must be a hashref'); + } + + # validate the passed config hashref + eval{ + $_CLASS->is_valid_config($config, 1); # returns 1 if valid + }or do{ + my $msg = 'invoked with invalid config'; + if($self->{debug}){ + $msg .= " ($EVAL_ERROR)"; + } + $_CLASS->_error($msg); + }; + + # save a clone of the passed config into the instance + $self->{_CONFIG} = $_CLASS->clone_config($config); + + # update the instance's entropy cache + $self->_update_entropystats_cache(); + } + + # return a reference to self to facilitate function chaining + return $self; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Return the config of the currently running instance as a string. +# Returns : A scalar. +# Arguments : NONE +# Throws : Croaks if invoked in an invalid way. Carps if it meets a key of a +# type not accounted for in the code. +# Notes : +# See Also : +sub config_string{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # assemble the string to return + my $ans = $_CLASS->config_to_string($self->{_CONFIG}); + + # return the string + return $ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Alter the running config with new values. +# Returns : A reference to the instalce itself to enable function chaining. +# Arguments : 1. a hashref containing config keys and values. +# Throws : Croaks on invalid invocaiton, invalid args, and, if the resulting +# new config is in some way invalid. +# Notes : Invalid keys in the new keys hashref will be silently ignored. +# See Also : +sub update_config{ + my $self = shift; + my $new_keys = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $new_keys && ref $new_keys eq 'HASH'){ + $_CLASS->_error('invalid arguments - the new config keys must be passed as a hashref'); + } + + # clone the current config as a starting point for the new config + my $new_config = $self->_clone_config(); + + # merge the new values into the config + my $num_keys_updated = 0; + foreach my $key (sort keys %{$_KEYS}){ + # skip the key if it's not present in the list of new keys + next unless defined $new_keys->{$key}; + + #validate the new key value + unless($_CLASS->_validate_key($key, $new_keys->{$key})){ + $_CLASS->_error("invalid new value for key=$key"); + } + + # update the key in the new config + $new_config->{$key} = $new_keys->{$key}; + $num_keys_updated++; + $_CLASS->_debug("updated $key to new value"); + } + $_CLASS->_debug("updated $num_keys_updated keys"); + + # validate the merged config + unless($_CLASS->is_valid_config($new_config)){ + $_CLASS->_error('updated config is invalid'); + } + + # re-calculate the dictionary cache if needed + my @cache_all = @{$self->{_CACHE_DICTIONARY_FULL}}; + my @cache_limited = @{$self->{_CACHE_DICTIONARY_LIMITED}}; + if($new_config->{word_length_min} ne $self->{_CONFIG}->{word_length_min} || $new_config->{word_length_max} ne $self->{_CONFIG}->{word_length_max}){ + # re-build the cache of valid words - throws an error if too few words are returned + @cache_limited = $_CLASS->_filter_word_list(\@cache_all, $new_config->{word_length_min}, $new_config->{word_length_max}); + } + + # if we got here, all is well with the new config, so add it and the caches to the instance + $self->{_CONFIG} = $new_config; + $self->{_CACHE_DICTIONARY_LIMITED} = [@cache_limited]; + + # update the instance's entropy cache + $self->_update_entropystats_cache(); + + # return a reference to self + return $self; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Return the status of the internal caches within the instnace. +# Returns : A string +# Arguments : NONE +# Throws : Croaks in invalid invocation +# Notes : +# See Also : +sub caches_state{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # generate the string + my $ans = q{}; + $ans .= 'Loaded Words: '.(scalar @{$self->{_CACHE_DICTIONARY_LIMITED}}).' (out of '.(scalar @{$self->{_CACHE_DICTIONARY_FULL}}).' loaded from the file)'.qq{\n}; + $ans .= 'Cached Random Numbers: '.(scalar @{$self->{_CACHE_RANDOM}}).qq{\n}; + + # return it + return $ans; +} + +#####-SUB-###################################################################### +# Purpose : Generaete a random password based on the object's loaded config +# Returns : a passowrd as a scalar +# Arguments : NONE +# Throws : Croaks on invalid invocation or on error generating the password +# Notes : +# See Also : +sub password{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # + # Generate the password + # + my $password = q{}; + eval{ + # + # start by generating the needed parts of the password + # + $_CLASS->_debug('starting to generate random words'); + my @words = $self->_random_words(); + $_CLASS->_debug('got random words='.(join q{, }, @words)); + $self->_transform_case(\@words); + $self->_substitute_characters(\@words); # TO DO + my $separator = $self->_separator(); + $_CLASS->_debug("got separator=$separator"); + my $pad_char = $self->_padding_char($separator); + $_CLASS->_debug("got pad_char=$pad_char"); + + # + # Then assemble the finished password + # + + # start with the words and the separator + $password = join $separator, @words; + $_CLASS->_debug("assembled base password: $password"); + + # next add the numbers front and back + if($self->{_CONFIG}->{padding_digits_before} > 0){ + $password = $self->_random_digits($self->{_CONFIG}->{padding_digits_before}).$separator.$password; + } + if($self->{_CONFIG}->{padding_digits_after} > 0){ + $password = $password.$separator.$self->_random_digits($self->{_CONFIG}->{padding_digits_after}); + } + $_CLASS->_debug("added random digits (as configured): $password"); + + + # then finally add the padding characters + if($self->{_CONFIG}->{padding_type} eq 'FIXED'){ + # simple fixed padding + if($self->{_CONFIG}->{padding_characters_before} > 0){ + foreach my $c (1..$self->{_CONFIG}->{padding_characters_before}){ + $password = $pad_char.$password; + } + } + if($self->{_CONFIG}->{padding_characters_after} > 0){ + foreach my $c (1..$self->{_CONFIG}->{padding_characters_after}){ + $password .= $pad_char; + } + } + }elsif($self->{_CONFIG}->{padding_type} eq 'ADAPTIVE'){ + # adaptive padding + my $pwlen = length $password; + if($pwlen < $self->{_CONFIG}->{pad_to_length}){ + # if the password is shorter than the target length, padd it out + while((length $password) < $self->{_CONFIG}->{pad_to_length}){ + $password .= $pad_char; + } + }elsif($pwlen > $self->{_CONFIG}->{pad_to_length}){ + # if the password is too long, trim it + $password = substr $password, 0, $self->{_CONFIG}->{pad_to_length}; + } + } + $_CLASS->_debug("added padding (as configured): $password"); + 1; # ensure true evaluation on successful execution + }or do{ + $_CLASS->_error("Failed to generate password with the following error: $EVAL_ERROR"); + }; + + # increment the passwords generated counter + $self->{_PASSWORD_COUNTER}++; + + # return the finished password + return $password; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Generate multiple passwords +# Returns : An array of passwords as scalars +# Arguments : 1. the number of passwords to generate as a scalar +# Throws : Croaks on invalid invocation or invalid args +# Notes : +# See Also : +sub passwords{ + my $self = shift; + my $num_pws = shift; + + # validate args + unless(defined $self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $num_pws && ref $num_pws eq q{} && $num_pws =~ m/^\d+$/sx && $num_pws > 0){ + $_CLASS->_error('invalid args - must specify the number of passwords to generate as a positive integer'); + } + + # generate the needed passwords + my @passwords = (); + my $num_to_do = $num_pws; + while($num_to_do > 0){ + push @passwords, $self->password(); # could croak + $num_to_do--; + } + + # return the passwords + return @passwords; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Return statistics about the instance +# Returns : A hash of statistics indexed by the following keys: +# * 'dictionary_path' - the path to the dictionary file the +# instance is using +# * 'dictionary_words_total' - the total number of words loaded +# from the dictionary file +# * 'dictionary_words_filtered' - the number of words loaded from +# the dictionary file that meet the lenght criteria set in the +# loaded config +# * 'dictionary_words_percent_avaialable' - the percentage of the +# total dictionary that is avialable for use with the loaded +# config +# * 'dictionary_filter_length_min' - the minimum length world +# permitted by the filter +# * 'dictionary_filter_length_max' - the maximum length world +# permitted by the filter +# * 'password_entropy_blind_min' - the entropy of the shortest +# password this config can generate from the point of view of a +# brute-force attacker in bits +# * 'password_entropy_blind_max' - the entropy of the longest +# password this config can generate from the point of view of a +# brute-force attacker in bits +# * 'password_entropy_blind' - the entropy of the average length +# of password generated by this configuration from the point of +# view of a brute-force attacker in bits +# * 'password_entropy_seen' - the true entropy of passwords +# generated by this instance assuming the dictionary and config +# are known to the attacker in bits +# * 'password_length_min' - the minimum length of passwords +# generated with this instance's config +# * 'password_length_max' - the maximum length of passwords +# generated with this instance's config +# * 'password_permutations_blind_min' - the number of permutations +# a brute-froce attacker would have to try to be sure of success +# on the shortest possible passwords geneated by this instance +# as a Math::BigInt object +# * 'password_permutations_blind_max' - the number of permutations +# a brute-froce attacker would have to try to be sure of success +# on the longest possible passwords geneated by this instance as +# a Math::BigInt object +# * 'password_permutations_blind' - the number of permutations +# a brute-froce attacker would have to try to be sure of success +# on the average length password geneated by this instance as a +# Math::BigInt object +# * 'password_permutations_seen' - the number of permutations an +# attacker with a copy of the dictionary and config would need to +# try to be sure of cracking a password generated by this +# instance as a Math::BigInt object +# * 'password_random_numbers_required' - the number of random +# numbers needed to generate a single password using the loaded +# config +# * 'passwords_generated' - the number of passwords this instance +# has generated +# * 'randomnumbers_cached' - the number of random numbers +# currently cached within the instance +# * 'randomnumbers_cache_increment' - the number of random numbers +# generated at once to re-plenish the cache when it's empty +# * 'randomnumbers_generator_function' - the name of the function used to +# generate random numbers (resoved with _coderef_to_subname()) +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : +# See Also : _coderef_to_subname() +sub stats{ + my $self = shift; + + # validate args + unless(defined $self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # create a hash to assemble all the stats into + my %stats = (); + + # deal with the config-specific stats + my %config_stats = $_CLASS->config_stats($self->{_CONFIG}); + $stats{password_length_min} = $config_stats{length_min}; + $stats{password_length_max} = $config_stats{length_max}; + $stats{password_random_numbers_required} = $config_stats{random_numbers_required}; + + # deal with the dictionary file + my %dict_stats = $self->_calcualte_dictionary_stats(); + $stats{dictionary_path} = $self->{_DICTIONARY_PATH}; + $stats{dictionary_words_total} = $dict_stats{num_words_total}; + $stats{dictionary_words_filtered} = $dict_stats{num_words_filtered}; + $stats{dictionary_words_percent_avaialable} = $dict_stats{percent_words_available}; + $stats{dictionary_filter_length_min} = $dict_stats{filter_length_min}; + $stats{dictionary_filter_length_max} = $dict_stats{filter_length_max}; + + # deal with the entropy stats + $stats{password_entropy_blind_min} = $self->{_CACHE_ENTROPYSTATS}->{entropy_blind_min}; + $stats{password_entropy_blind_max} = $self->{_CACHE_ENTROPYSTATS}->{entropy_blind_max}; + $stats{password_entropy_blind} = $self->{_CACHE_ENTROPYSTATS}->{entropy_blind}; + $stats{password_entropy_seen} = $self->{_CACHE_ENTROPYSTATS}->{entropy_seen}; + $stats{password_permutations_blind_min} = $self->{_CACHE_ENTROPYSTATS}->{permutations_blind_min}; + $stats{password_permutations_blind_max} = $self->{_CACHE_ENTROPYSTATS}->{permutations_blind_max}; + $stats{password_permutations_blind} = $self->{_CACHE_ENTROPYSTATS}->{permutations_blind}; + $stats{password_permutations_seen} = $self->{_CACHE_ENTROPYSTATS}->{permutations_seen}; + + # deal with password counter + $stats{passwords_generated} = $self->{_PASSWORD_COUNTER}; + + # deal with the random number generator + $stats{randomnumbers_cached} = scalar @{$self->{_CACHE_RANDOM}}; + $stats{randomnumbers_cache_increment} = $self->{_CONFIG}->{random_increment}; + $stats{randomnumbers_generator_function} = $_CLASS->_coderef_to_subname($self->{_CONFIG}->{random_function}); + + # return the stats + return %stats; +} + +#####-SUB-###################################################################### +# Type : INSTANCE +# Purpose : Represent the current state of the instance as a string. +# Returns : Returns a multi-line string as as scalar containing details of the +# loaded dictionary file, config, and caches +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : +# See Also : +sub status{ + my $self = shift; + + # validate args + unless(defined $self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # assemble the response + my %stats = $self->stats(); + my $status = q{}; + + # the dictionary + $status .= "*DICTIONARY*\n"; + $status .= "File path: $stats{dictionary_path}\n"; + $status .= "# words: $stats{dictionary_words_total}\n"; + $status .= "# words of valid length: $stats{dictionary_words_filtered} ($stats{dictionary_words_percent_avaialable}%)\n"; + + # the config + $status .= "\n*CONFIG*\n"; + $status .= $self->config_string(); + + # the random number cache + $status .= "\n*RANDOM NUMBER CACHE*\n"; + $status .= "# in cache: $stats{randomnumbers_cached}\n"; + + # password statistics + $status .= "\n*PASSWORD STATISTICS*\n"; + if($stats{password_length_min} == $stats{password_length_max}){ + $status .= "Password length: $stats{password_length_max}\n"; + $status .= 'Permutations (brute-force): '.$_CLASS->_render_bigint($stats{password_permutations_blind_max})."\n"; + }else{ + $status .= "Password length: between $stats{password_length_min} & $stats{password_length_max}\n"; + $status .= 'Permutations (brute-force): between '.$_CLASS->_render_bigint($stats{password_permutations_blind_min}).q{ & }.$_CLASS->_render_bigint($stats{password_permutations_blind_max}).q{ (average }.$_CLASS->_render_bigint($stats{password_permutations_blind}).")\n"; + } + $status .= 'Permutations (given dictionary & config): '.$_CLASS->_render_bigint($stats{password_permutations_seen})."\n"; + if($stats{password_length_min} == $stats{password_length_max}){ + $status .= "Entropy (brute-force): $stats{password_entropy_blind_max}bits\n"; + }else{ + $status .= "Entropy (Brute-Force): between $stats{password_entropy_blind_min}bits and $stats{password_entropy_blind_max}bits (average $stats{password_entropy_blind}bits)\n"; + } + $status .= "Entropy (given dictionary & config): $stats{password_entropy_seen}bits\n"; + $status .= "Passwords Generated: $stats{passwords_generated}\n"; + + # debug-only info + if($DEBUG){ + $status .= "\n*DEBUG INFO*\n"; + if($_CAN_STACK_TRACE){ + $status .= "Devel::StackTrace IS installed\n"; + }else{ + $status .= "Devel::StackTrace is NOT installed\n"; + } + } + + # return the status + return $status; +} + +# +# Regular Subs----------------------------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : SUBROUTINE +# Purpose : A functional interface to this library (exported) +# Returns : A random password as a scalar +# Arguments : 1. The path to a dictionary file +# 2. OPTIONAL - the name of a preset as a scalar (an empty string, +# undef, or 'DEFAULT' to get the default config) +# -OR- +# A hashref containing a full valid config +# 3. OPTIONAL - a hashref continaing any keys from the preset to be +# overridden (ignored if a hashref is passed as the second arg) +# Throws : Croaks on error +# Notes : +# See Also : +sub xkpasswd{ + my $dictionary_path = shift; + my $preset = shift; + my $preset_override = shift; + + # try initialise an xkpasswd object + my $xkpasswd; + eval{ + $xkpasswd = $_CLASS->new($dictionary_path, $preset, $preset_override); + 1; # ensure truthy evaliation on successful execution + } or do { + $_CLASS->_error("Failed to generate password with the following error: $EVAL_ERROR"); + }; + + # genereate and return a password - could croak + return $xkpasswd->password(); +} + +#####-SUB-###################################################################### +# Type : SUBROUTINE +# Purpose : The default random generator function. +# Returns : An array of random decimal numbers between 0 and 1 as scalars. +# Arguments : 1. the number of random numbers to generate (must be at least 1) +# 2. OPTIONAL - a truthy value to enable debugging +# Throws : Croaks on invalid args +# Notes : +# See Also : +sub basic_random_generator{ + my $num = shift; + my $debug = shift; + + # validate args + unless(defined $num && $num =~ m/^\d+$/sx && $num >= 1){ + $_CLASS->_error('invalid args - must request at least 1 password'); + } + + # generate the random numbers + my @ans = (); + my $num_to_generate = $num; + while($num_to_generate > 0){ + push @ans, rand; + $num_to_generate--; + } + + # return the random numbers + return @ans; +} + +# +# 'Private' functions --------------------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Function to log output from the module - SHOULD NEVER BE CALLED +# DIRECTLY +# Returns : Always returns 1 (to keep perlcritic happy) +# Arguments : 1. the severity of the message (one of 'DEBUG', 'WARNING', or +# 'ERROR') +# 2. the message to log +# Throws : Croaks on invalid invocation +# Notes : THIS FUNCTION SHOULD NEVER BE CALLED DIRECTLY, but always called +# via _debug(), _warn(), or _error(). +# This function does not croak on invalid args, it confess with as +# useful an output as it can. +# If the function prints output, it will do so to $LOG_STREAM. The +# severity determines the functions exact behaviour: +# * 'DEBUG' - message is always printed without a stack trace +# * 'WARNING' - output is carped, and, if $LOG_ERRORS is true the +# message is also printed +# * 'ERROR' - output is confessed if $DEBUG and croaked otherwise. +# If $LOG_ERRORS is true the message is also printed with a +# stack trace (the stack trace is omited if Devel::StackTrace) is +# not installed. +# See Also : _debug(), _warn() & _error() +## no critic (ProhibitExcessComplexity); +sub _log{ + my $class = shift; + my $severity = uc shift; + my $message = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + croak((caller 0)[3].'(): invalid invocation of class method'); + } + unless(defined $severity && ref $severity eq q{} && length $severity > 1){ + $severity = 'UNKNOWN_SEVERITY'; + } + unless(defined $message && ref $message eq q{}){ + my $output = 'ERROR - '.(caller 0)[3]."(): invoked with severity '$severity' without message at ".(caller 1)[1].q{:}.(caller 1)[2]; + if($LOG_ERRORS){ + my $log_output = $output; + if($_CAN_STACK_TRACE){ + $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string(); + } + print {$LOG_STREAM} $log_output."\n"; + } + confess($output); + } + + # figure out the correct index for the function that is really responsible + my $caller_index = 2; + my $calling_func = (caller 1)[3]; + unless($calling_func =~ m/^$_CLASS[:]{2}((_debug)|(_warn)|(_error))$/sx){ + print {$LOG_STREAM} 'WARNING - '.(caller 0)[3].q{(): invoked directly rather than via _debug(), _warn() or _error() - DO NOT DO THIS!}; + $caller_index++; + } + + # deal with evals + my $true_caller = q{}; + my @caller = caller $caller_index; + if(@caller){ + $true_caller = $caller[3]; + } + my $eval_depth = 0; + while($true_caller eq '(eval)'){ + $eval_depth++; + $caller_index++; + my @next_caller = caller $caller_index; + if(@next_caller){ + $true_caller = $next_caller[3]; + }else{ + $true_caller = q{}; + } + } + if($true_caller eq q{}){ + $true_caller = 'UNKNOWN_FUNCTION'; + } + + # deal with the message as appropriate + my $output = "$severity - "; + if($eval_depth > 0){ + if($eval_depth == 1){ + $output .= "eval() within $true_caller"; + }else{ + $output .= "$eval_depth deep eval()s within $true_caller"; + } + }else{ + $output .= $true_caller; + } + $output .= "(): $message"; + if($severity eq 'DEBUG'){ + # debugging, so always print and do nothing more + print {$LOG_STREAM} "$output\n" if $DEBUG; + }elsif($severity eq 'WARNING'){ + # warning - always carp, but first print if needed + if($LOG_ERRORS){ + print {$LOG_STREAM} "$output\n"; + } + carp($output); + }elsif($severity eq 'ERROR'){ + # error - print if needed, then confess or croak depending on whether or not debugging + if($LOG_ERRORS){ + my $log_output = $output; + if($DEBUG && $_CAN_STACK_TRACE){ + $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string(); + print {$LOG_STREAM} "$output\n"; + } + print {$LOG_STREAM} "$log_output\n"; + } + if($DEBUG){ + confess($output); + }else{ + croak($output); + } + }else{ + # we have an unknown severity, so assume the worst and confess (also log if needed) + if($LOG_ERRORS){ + my $log_output = $output; + if($_CAN_STACK_TRACE){ + $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string(); + } + print {$LOG_STREAM} "$log_output\n"; + } + confess($output); + } + + # to keep perlcritic happy + return 1; +} +## use critic + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Function for printing a debug message +# Returns : Always return 1 (to keep perlcritic happpy) +# Arguments : 1. the debug message to log +# Throws : Croaks on invalid invocation +# Notes : a wrapper for _log() which invokes that function with a severity +# of 'DEBUG' +# See Also : _log() +sub _debug{ + my $class = shift; + my $message = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + #pass the call on to _log + return $_CLASS->_log('DEBUG', $message); +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Function for issuing a warning +# Returns : Always returns 1 to keep perlcritic happy +# Arguments : 1. the warning message to log +# Throws : Croaks on invalid invocation +# Notes : a wrapper for _log() which invokes that function with a severity +# of 'WARNING' +# See Also : _log() +sub _warn{ + my $class = shift; + my $message = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + #pass the call on to _log + return $_CLASS->_log('WARNING', $message); +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Function for throwing an error +# Returns : Always returns 1 to keep perlcritic happy +# Arguments : 1. the error message to log +# Throws : Croaks on invalid invocation +# Notes : a wrapper for _log() which invokes that function with a severity +# of 'ERROR' +# See Also : _log() +sub _error{ + my $class = shift; + my $message = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + #pass the call on to _log + return $_CLASS->_log('ERROR', $message); +} + +#####-SUB-###################################################################### +# Type : INSTANCE ('PRIVATE') +# Purpose : Clone the instance's config hashref +# Returns : a hashref +# Arguments : NONE +# Throws : Croaks if called in an invalid way +# Notes : +# See Also : +sub _clone_config{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # build the clone + my $clone = $_CLASS->clone_config($self->{_CONFIG}); + + # if, and only if, debugging, validate the cloned config so errors in the + # cloning code will trigger an exception + if($self->{debug}){ + eval{ + $_CLASS->is_valid_config($clone, 1); # returns 1 if valid + }or do{ + $_CLASS->_error('cloning error ('.$EVAL_ERROR.')'); + }; + } + + # return the clone + return $clone; +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : validate the value for a single key +# Returns : 1 if the key is valid, 0 otherwise +# Arguments : 1. the key to validate the value for +# 2. the value to validate +# 3. OPTIONAL - a true value to croak on invalid value +# Throws : Croaks if invoked invalidly, or on error if arg 3 is truthy. +# Also Carps if called with invalid key with a truthy arg 3. +# Notes : +# See Also : +sub _validate_key{ + my $class = shift; + my $key = shift; + my $val = shift; + my $croak = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $key && ref $key eq q{} && defined $val){ + $_CLASS->_error('invoked with invalid args'); + } + + # make sure the key exists + unless(defined $_KEYS->{$key}){ + carp("invalid key=$key") if $croak; + return 0; + } + + # make sure the value is of the correct type + unless(ref $val eq $_KEYS->{$key}->{ref}){ + croak("invalid type for key=$key. Expected: ".$_KEYS->{$key}->{desc}) if $croak; + return 0; + } + + # make sure the value passes the validation function for the key + unless($_KEYS->{$key}->{validate}->($val)){ + croak("invalid value for key=$key. Expected: ".$_KEYS->{$key}->{desc}) if $croak; + return 0; + } + + # if we got here, all is well, so return 1 + return 1; +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Parse a dictionary file into an array of words. +# Returns : An array of words as scalars. +# Arguments : 1. a scalar containing the path to the file to parse +# Throws : Croaks on invalid invocation, invalid args, and if there is an +# error reading the file. +# Notes : +# See Also : +sub _parse_words_file{ + my $class = shift; + my $path = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $path && ref $path eq q{} && -f $path){ + $_CLASS->_error('invoked with invalid file path'); + } + + # slurp the words file + open my $WORDSFILE, '<', $path or $_CLASS->_error("failed to open words file at $path"); + my $words_raw = do{local $/ = undef; <$WORDSFILE>}; + close $WORDSFILE; + + #loop throuh the lines and build up the word list + my @ans = (); + LINE: + foreach my $line (split /\n/sx, $words_raw){ + # skip empty lines + next LINE if $line =~ m/^\s*$/sx; + + # skip comment lines + next LINE if $line =~ m/^[#]/sx; + + # skip anything that's not at least three letters + next LINE unless $line =~ m/^[[:alpha:]]{4,}$/sx; + + # store the word + push @ans, $line; + } + + # return the answer + return @ans; +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : Filter a word list based on word length +# Returns : An array of words as scalars. +# Arguments : 1. a reference to the array of words to filter. +# Throws : Croaks on invalid invocation, or if too few matching words found. +# Notes : +# See Also : +sub _filter_word_list{ + my $class = shift; + my $word_list_ref = shift; + my $min_len = shift; + my $max_len = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $word_list_ref && ref $word_list_ref eq q{ARRAY}){ + $_CLASS->_error('invoked with invalid word list'); + } + unless(defined $min_len && ref $min_len eq q{} && $min_len =~ m/^\d+$/sx && $min_len > 3){ + $_CLASS->_error('invoked with invalid minimum word length'); + } + unless(defined $max_len && ref $max_len eq q{} && $max_len =~ m/^\d+$/sx && $max_len >= $min_len){ + $_CLASS->_error('invoked with invalid maximum word length'); + } + + #build the array of words of appropriate length + my @ans = (); + WORD: + foreach my $word (@{$word_list_ref}){ + # skip words shorter than the minimum + next WORD if length $word < $min_len; + + # skip words longer than the maximum + next WORD if length $word > $max_len; + + # store the word in the filtered list + push @ans, $word; + } + + # return the list + return @ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Generate a random integer greater than 0 and less than a given +# maximum value. +# Returns : A random integer as a scalar. +# Arguments : 1. the min value for the random number (as a positive integer) +# Throws : Croaks if invoked in an invalid way, with invalid args, of if +# there is a problem generating random numbers (should the cache) +# be empty. +# Notes : The random cache is used as the source for the randomness. If the +# random pool is empty, this function will replenish it. +# See Also : +sub _random_int{ + my $self = shift; + my $max = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $max && $max =~ m/^\d+$/sx && $max > 0){ + $_CLASS->_error('invoked with invalid random limit'); + } + + # calculate the random number + my $ans = ($self->_rand() * 1_000_000) % $max; + + # return it + $_CLASS->_debug("returning $ans (max=$max)"); + return $ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Generate a number of random integers. +# Returns : A scalar containing a number of random integers. +# Arguments : 1. The number of random integers to generate +# Throws : Croaks on invalid invocation, or if there is a problem generating +# the needed randomness. +# Notes : +# See Also : +sub _random_digits{ + my $self = shift; + my $num = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $num && $num =~ m/^\d+$/sx && $num > 0){ + $_CLASS->_error('invoked with invalid number of digits'); + } + + # assemble the response + my $ans = q{}; + foreach my $n (1..$num){ + $ans .= $self->_random_int(10); + } + + # return the response + return $ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Return the next random number in the cache, and if needed, +# replenish it. +# Returns : A decimal number between 0 and 1 +# Arguments : NONE +# Throws : Croaks if invoked in an invalid way, or if there is problem +# replenishing the random cache. +# Notes : +# See Also : +sub _rand{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # get the next random number from the cache + my $num = shift @{$self->{_CACHE_RANDOM}}; + if(!defined $num){ + # the cache was empty - so try top up the random cache - could croak + $_CLASS->_debug('random cache empty - attempting to replenish'); + $self->_increment_random_cache(); + + # try shift again + $num = shift @{$self->{_CACHE_RANDOM}}; + } + + # make sure we got a valid random number + unless(defined $num && $num =~ m/^\d+([.]\d+)?$/sx && $num >= 0 && $num <= 1){ + $_CLASS->_error('found invalid entry in random cache'); + } + + # return the random number + $_CLASS->_debug("returning $num (".(scalar @{$self->{_CACHE_RANDOM}}).' remaining in cache)'); + return $num; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Append random numbers to the cache. +# Returns : Always returns 1. +# Arguments : NONE +# Throws : Croaks if incorrectly invoked or if the random generating +# function fails to produce random numbers. +# Notes : +# See Also : +sub _increment_random_cache{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # figure out how many numbers to generate + my $num_rand = $self->{_CONFIG}->{random_increment}; + if($num_rand eq 'AUTO'){ + $_CLASS->_debug(q{random_increment='AUTO' - generating stats to determine increment to use}); + my %conf_stats = $_CLASS->config_stats($self->{_CONFIG}); + $num_rand = $conf_stats{random_numbers_required}; + $_CLASS->_debug("using increment of $num_rand"); + }else{ + $_CLASS->_debug("using hard-coded increment of $num_rand"); + } + + # genereate the random numbers + my @random_numbers = &{$self->{_CONFIG}->{random_function}}($num_rand); + $_CLASS->_debug('generated '.(scalar @random_numbers).' random numbers ('.(join q{, }, @random_numbers).')'); + + # validate them + my $num_generated = scalar @random_numbers; + unless($num_generated == $num_rand){ + $_CLASS->_error("random function did not return the correct number of random numbers (expected $num_rand, got $num_generated)"); + } + foreach my $num (@random_numbers){ + unless($num =~ m/^1|(0([.]\d+)?)$/sx){ + $_CLASS->_error("random function returned and invalid value ($num)"); + } + } + + # add them to the cache + foreach my $num (@random_numbers){ + push @{$self->{_CACHE_RANDOM}}, $num; + } + + # always return 1 (to keep PerlCritic happy) + return 1; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Get the required number of random words from the loaded words +# file +# Returns : An array of words +# Arguments : NONE +# Throws : Croaks on invalid invocation or error generating random numbers +# Notes : The number of words generated is determined by the num_words +# config key. +# See Also : +sub _random_words{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # get the random words + my @ans = (); + $_CLASS->_debug('about to generate '.$self->{_CONFIG}->{num_words}.' words'); + while ((scalar @ans) < $self->{_CONFIG}->{num_words}){ + my $word = $self->{_CACHE_DICTIONARY_LIMITED}->[$self->_random_int(scalar @{$self->{_CACHE_DICTIONARY_LIMITED}})]; + $_CLASS->_debug("generate word=$word"); + push @ans, $word; + } + + # return the list of random words + $_CLASS->_debug('returning '.(scalar @ans).' words'); + return @ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Get the separator character to use based on the loaded config. +# Returns : A scalar containing the separator, which could be an empty string. +# Arguments : NONE +# Throws : Croaks on invalid invocation, or if there is a problem generating +# any needed random numbers. +# Notes : The character returned is controlled by the config variable +# separator_character +# See Also : +sub _separator{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # figure out the separator character + my $sep = $self->{_CONFIG}->{separator_character}; + if ($sep eq 'NONE'){ + $sep = q{}; + }elsif($sep eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{separator_alphabet}){ + $sep = $self->{_CONFIG}->{separator_alphabet}->[$self->_random_int(scalar @{$self->{_CONFIG}->{separator_alphabet}})]; + }else{ + $sep = $self->{_CONFIG}->{symbol_alphabet}->[$self->_random_int(scalar @{$self->{_CONFIG}->{symbol_alphabet}})]; + } + } + + # return the separator character + return $sep +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Return the padding character based on the loaded config. +# Returns : A scalar containing the padding character, which could be an +# empty string. +# Arguments : 1. the separator character being used to generate the password +# Throws : Croaks on invalid invocation, or if there is a problem geneating +# any needed random numbers. +# Notes : The character returned is determined by a combination of the +# padding_type & padding_character config variables. +# See Also : +sub _padding_char{ + my $self = shift; + my $sep = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $sep){ + $_CLASS->_error('no separator character passed'); + } + + # if there is no padding character needed, return an empty string + if($self->{_CONFIG}->{padding_type} eq 'NONE'){ + return q{}; + } + + # if we got here we do need a character, so generate one as appropriate + my $padc = $self->{_CONFIG}->{padding_character}; + if($padc eq 'SEPARATOR'){ + $padc = $sep; + }elsif($padc eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{padding_alphabet}){ + $padc = $self->{_CONFIG}->{padding_alphabet}->[$self->_random_int(scalar @{$self->{_CONFIG}->{padding_alphabet}})]; + }else{ + $padc = $self->{_CONFIG}->{symbol_alphabet}->[$self->_random_int(scalar @{$self->{_CONFIG}->{symbol_alphabet}})]; + } + } + + # return the padding character + return $padc; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Apply the case transform (if any) specified in the loaded config. +# Returns : Always returns 1 (to keep PerlCritic happy) +# Arguments : 1. A reference to the array contianing the words to be +# transformed. +# Throws : Croaks on invalid invocation or if there is a problem generating +# any needed random numbers. +# Notes : The transformations applied are controlled by the case_transform +# config variable. +# See Also : +## no critic (ProhibitExcessComplexity); +sub _transform_case{ + my $self = shift; + my $words_ref = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $words_ref && ref $words_ref eq 'ARRAY'){ + $_CLASS->_error('no words array reference passed'); + } + + # if the transform is set to nothing, then just return + if($self->{_CONFIG}->{case_transform} eq 'NONE'){ + return 1; + } + + # apply the appropriate transform + ## no critic (ProhibitCascadingIfElse); + if($self->{_CONFIG}->{case_transform} eq 'UPPER'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + $words_ref->[$i] = uc $words_ref->[$i]; + } + }elsif($self->{_CONFIG}->{case_transform} eq 'LOWER'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + $words_ref->[$i] = lc $words_ref->[$i]; + } + }elsif($self->{_CONFIG}->{case_transform} eq 'CAPITALISE'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + $words_ref->[$i] = ucfirst lc $words_ref->[$i]; + } + }elsif($self->{_CONFIG}->{case_transform} eq 'INVERT'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + $words_ref->[$i] = lcfirst uc $words_ref->[$i]; + } + }elsif($self->{_CONFIG}->{case_transform} eq 'ALTERNATE'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + my $word = $words_ref->[$i]; + if($i % 2 == 0){ + $word = lc $word; + }else{ + $word = uc $word; + } + $words_ref->[$i] = $word; + } + }elsif($self->{_CONFIG}->{case_transform} eq 'RANDOM'){ + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + my $word = $words_ref->[$i]; + if($self->_random_int(2) % 2 == 0){ + $word = uc $word; + }else{ + $word = lc $word; + } + $words_ref->[$i] = $word; + } + } + ## use critic + + return 1; # just to to keep PerlCritic happy +} +## use critic + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Apply any case transforms specified in the loaded config. +# Returns : Always returns 1 (to keep PerlCritic happy) +# Arguments : 1. a reference to an array containing the words that will make up +# the password. +# Throws : Croaks on invalid invocation or invalid args. +# Notes : The substitutions that will be applied are specified in the +# character_substitutions config variable. +# See Also : +sub _substitute_characters{ + my $self = shift; + my $words_ref = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + unless(defined $words_ref && ref $words_ref eq 'ARRAY'){ + $_CLASS->_error('no words array reference passed'); + } + + # if no substitutions are defined, do nothing + unless(defined $self->{_CONFIG}->{character_substitutions} && (scalar keys %{$self->{_CONFIG}->{character_substitutions}})){ + return 1; + } + + # If we got here, go ahead and apply the substitutions + foreach my $i (0..((scalar @{$words_ref}) - 1)){ + my $word = $words_ref->[$i]; + foreach my $char (keys %{$self->{_CONFIG}->{character_substitutions}}){ + my $sub = $self->{_CONFIG}->{character_substitutions}->{$char}; + $word =~ s/$char/$sub/sxg; + } + $words_ref->[$i] = $word; + } + + # always return 1 to keep PerlCritic happy + return 1; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Perform sanity checks on all defined presets +# Returns : Always returns 1 (to keep perlcritic happy) +# Arguments : NONE +# Throws : Croaks on invalid input +# Notes : The function is designed to be called from the constructor when +# in debug mode. It prints information on what it's doing and any +# errors it finds to STDERR +# See Also : +sub _check_presets{ + my $class = shift; + + # validate the args + unless($class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + + # loop through all presets and perform sanity checks + my @preset_names = $_CLASS->defined_presets(); + my $num_problems = 0; + foreach my $preset (@preset_names){ + # make sure the preset is valid + eval{ + $_CLASS->is_valid_config($_PRESETS->{$preset}->{config}, 1); + 1; # ensure truthy evaluation on success + }or do{ + $_CLASS->_warn("preset $preset has invalid config ($EVAL_ERROR)"); + $num_problems++; + }; + + # make sure the random_increment is optimal + if($_PRESETS->{$preset}->{config}->{random_increment} ne 'AUTO'){ + my %stats = $_CLASS->config_stats($_PRESETS->{$preset}->{config}); + unless($_PRESETS->{$preset}->{config}->{random_increment} == $stats{random_numbers_required}){ + $_CLASS->_warn("preset $preset has sub-optimal random_increment (value=$_PRESETS->{$preset}->{config}->{random_increment}, required per password=$stats{random_numbers_required})"); + $num_problems++; + } + } + } + if($num_problems == 0){ + $_CLASS->_debug('all presets OK'); + } + + # to keep perlcritic happy + return 1; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Gather entropy stats for the combination of the loaded config +# and dictionary. +# Returns : A hash of stats indexed by: +# * 'permutations_blind_min' - the number of permutations to be +# tested by an attacker with no knowledge of the dictionary file +# used, or the config used, assuming the minimum possible +# password length from the given config (as BigInt) +# * 'permutations_blind_max' - the number of permutations to be +# tested by an attacker with no knowledge of the dictionary file +# used, or the cofig file used, assuming the maximum possible +# password length fom the given config (as BigInt) +# * 'permutations_blind' - the number of permutations for the +# average password length for the given config (as BigInt) +# * 'permutations_seen' - the number of permutations to be tested +# by an attacker with full knowledge of the dictionary file and +# configuration used (as BigInt) +# * 'entropy_blind_min' - permutations_blind_min converted to bits +# * 'entropy_blind_max' - permutations_blind_max converted to bits +# * 'entropy_blind' - permutations_blind converted to bits +# * 'entropy_seen' - permutations_seen converted to bits +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : This function uses config_stats() to determined the longest and +# shortest password lengths, so the caveat that function has +# when it comes to multi-character substitutions applies here too. +# This function assumes no accented characters (at least for now). +# For the blind calculations, if any single symbol is present, a +# search-space of 33 symbols is assumed (same as password +# haystacks page) +# See Also : config_stats() +sub _calculate_entropy_stats{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + my %ans = (); + + # get the password length details for the config + my %config_stats = $_CLASS->config_stats($self->{_CONFIG}, 'supress errors'); + my $b_length_min = Math::BigInt->new($config_stats{length_min}); + my $b_length_max = Math::BigInt->new($config_stats{length_max}); + + # calculate the blind permutations - (based purely on length and alphabet) + my $alphabet_count = 26; # all passwords have at least one case of letters + if($self->{_CONFIG}->{case_transform} =~ m/^(ALTERNATE)|(CAPITALISE)|(INVERT)|(RANDOM)$/sx){ + $alphabet_count += 26; # these configs guarantee a mix of cases + } + if($self->{_CONFIG}->{padding_digits_before} > 0 || $self->{_CONFIG}->{padding_digits_after} > 0){ + $alphabet_count += 10; # these configs guarantee digits in the mix + } + if($self->_passwords_will_contain_symbol()){ + $alphabet_count += 33; # the config almost certainly includes a symbol, so add 33 to the alphabet (like password haystacks does) + } + my $b_alphabet_count = Math::BigInt->new($alphabet_count); + my $length_avg = round(($config_stats{length_min} + $config_stats{length_max})/2); + $ans{permutations_blind_min} = $b_alphabet_count->copy()->bpow($b_length_min); #$alphabet_count ** $length_min; + $_CLASS->_debug('got permutations_blind_min='.$ans{permutations_blind_min}); + $ans{permutations_blind_max} = $b_alphabet_count->copy()->bpow($b_length_max); #$alphabet_count ** $length_max; + $_CLASS->_debug('got permutations_blind_max='.$ans{permutations_blind_max}); + $ans{permutations_blind} = $b_alphabet_count->copy()->bpow(Math::BigInt->new($length_avg)); #$alphabet_count ** $length_avg; + $_CLASS->_debug('got permutations_blind='.$ans{permutations_blind}); + + # calculate the seen permutations + my $num_words = scalar @{$self->{_CACHE_DICTIONARY_LIMITED}}; + my $b_num_words = Math::BigInt->new($num_words); + my $b_seen_perms = Math::BigInt->new('0'); + # start with the permutations from the chosen words + if($self->{_CONFIG}->{case_transform} eq 'RANDOM'){ + # effectively doubles the numberof words in the dictionary + $b_seen_perms->badd($b_num_words->copy()->bpow(Math::BigInt->new($self->{_CONFIG}->{num_words} * 2))); + }else{ + $b_seen_perms->badd($b_num_words->copy()->bpow(Math::BigInt->new($self->{_CONFIG}->{num_words}))); # += $num_words ** $self->{_CONFIG}->{num_words}; + } + # multiply in the permutations from the separator (if any - i.e. if it's randomly chosen) + if($self->{_CONFIG}->{separator_character} eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{separator_alphabet}){ + $b_seen_perms->bmul(Math::BigInt->new(scalar @{$self->{_CONFIG}->{separator_alphabet}})); + }else{ + $b_seen_perms->bmul(Math::BigInt->new(scalar @{$self->{_CONFIG}->{symbol_alphabet}})); + } + } + # multiply in the permutations from the padding character (if any - i.e. if it's randomly chosen) + if($self->{_CONFIG}->{padding_type} ne 'NONE' && $self->{_CONFIG}->{padding_character} eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{padding_alphabet}){ + $b_seen_perms->bmul(Math::BigInt->new(scalar @{$self->{_CONFIG}->{padding_alphabet}})); + }else{ + $b_seen_perms->bmul(Math::BigInt->new(scalar @{$self->{_CONFIG}->{symbol_alphabet}})); + } + } + # multiply in the permutations from the padding digits (if any) + my $num_padding_digits = $self->{_CONFIG}->{padding_digits_before} + $self->{_CONFIG}->{padding_digits_after}; + while($num_padding_digits > 0){ + $b_seen_perms->bmul(Math::BigInt->new('10')); + $num_padding_digits--; + } + $ans{permutations_seen} = $b_seen_perms; + $_CLASS->_debug('got permutations_seen='.$ans{permutations_seen}); + + # calculate the entropy values based on the permutations + $ans{entropy_blind_min} = $ans{permutations_blind_min}->copy()->blog(2)->numify(); + $_CLASS->_debug('got entropy_blind_min='.$ans{entropy_blind_min}); + $ans{entropy_blind_max} = $ans{permutations_blind_max}->copy()->blog(2)->numify(); + $_CLASS->_debug('got entropy_blind_max='.$ans{entropy_blind_max}); + $ans{entropy_blind} = $ans{permutations_blind}->copy()->blog(2)->numify(); + $_CLASS->_debug('got entropy_blind='.$ans{entropy_blind}); + $ans{entropy_seen} = $ans{permutations_seen}->copy()->blog(2)->numify(); + $_CLASS->_debug('got entropy_seen='.$ans{entropy_seen}); + + # return the stats + return %ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Calculate statistics on the loaded dictionary file +# Returns : A hash of statistics indexed by: +# * 'filter_length_min' - the minimum allowed word length +# * 'filter_length_max' - the maximum allowed word length +# * 'num_words_total' - the number of words in the un-filtered +# dictionary file +# * 'num_words_filtered' - the number of words after filtering on +# size limitations +# * 'percent_words_available' - the percentage of the un-filtered +# words remaining in the filtered words list +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : +# See Also : +sub _calcualte_dictionary_stats{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # create a hash to aggregate the stats into + my %ans = (); + + # deal with agregate numbers first + $ans{num_words_total} = scalar @{$self->{_CACHE_DICTIONARY_FULL}}; + $ans{num_words_filtered} = scalar @{$self->{_CACHE_DICTIONARY_LIMITED}}; + $ans{percent_words_available} = round(($ans{num_words_filtered}/$ans{num_words_total}) * 100); + $ans{filter_length_min} = $self->{_CONFIG}->{word_length_min}; + $ans{filter_length_max} = $self->{_CONFIG}->{word_length_max}; + + # return the stats + return %ans; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : A function to check if passwords genereated with the loaded +# config would contian a symbol +# Returns : 1 if the config will produce passwords with a symbol, or 0 +# otherwise +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : This function is used by _calculate_entropy_stats() to figure out +# whether or not there are symbols in the alphabet when calculating +# the brute-force entropy. +# See Also : _calculate_entropy_stats() +sub _passwords_will_contain_symbol{ + my $self = shift; + + # validate args + unless(defined $self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # assume no symbol, if we find one, set to 1 + my $symbol_used = 0; + + ## no critic (ProhibitEnumeratedClasses); + # first check the padding + if($self->{_CONFIG}->{padding_type} ne 'NONE'){ + if($self->{_CONFIG}->{padding_character} eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{padding_alphabet}){ + my $all_pad_chars = join q{}, @{$self->{_CONFIG}->{padding_alphabet}}; + if($all_pad_chars =~ m/[^0-9a-zA-Z]/sx){ # if we have just one non-word character + $symbol_used = 1; + } + }else{ + my $all_pad_chars = join q{}, @{$self->{_CONFIG}->{symbol_alphabet}}; + if($all_pad_chars =~ m/[^0-9a-zA-Z]/sx){ # if we have just one non-word character + $symbol_used = 1; + } + } + }else{ + if($self->{_CONFIG}->{padding_character} =~ m/[^0-9a-zA-Z]/sx){ # the padding character is not a word character + $symbol_used = 1; + } + } + } + + # then check the separator + if($self->{_CONFIG}->{separator_character} ne 'NONE'){ + if($self->{_CONFIG}->{separator_character} eq 'RANDOM'){ + if(defined $self->{_CONFIG}->{separator_alphabet}){ + my $all_sep_chars = join q{}, @{$self->{_CONFIG}->{separator_alphabet}}; + if($all_sep_chars =~ m/[^0-9a-zA-Z]/sx){ # if we have just one non-word character + $symbol_used = 1; + } + }else{ + my $all_sep_chars = join q{}, @{$self->{_CONFIG}->{symbol_alphabet}}; + if($all_sep_chars =~ m/[^0-9a-zA-Z]/sx){ # if we have just one non-word character + $symbol_used = 1; + } + } + }else{ + if($self->{_CONFIG}->{separator_character} =~ m/[^0-9a-zA-Z]/sx){ # the separator is not a word character + $symbol_used = 1; + } + } + } + ## use critic + + # return + return $symbol_used; +} + +#####-SUB-###################################################################### +# Type : INSTANCE (PRIVATE) +# Purpose : Update the entropy stats cache (and warn of low entropy if +# appropriate) +# Returns : always returns 1 (to keep perlcritic happy) +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : This function should only be called from config() or dictionary(). +# The entropy is calculated with _calculate_entropy_stats(), and a +# reference to the hash returned from that function is stored in +# $self->{_CACHE_ENTROPYSTATS}. +# See Also : _calculate_entropy_stats(), config() & dictionary() +sub _update_entropystats_cache{ + my $self = shift; + + # validate args + unless($self && $self->isa($_CLASS)){ + $_CLASS->_error('invalid invocation of instance method'); + } + + # do nothing if the dictionary has not been loaded yet (should only happen while the constructor is building an instance) + return 1 unless($self->{_DICTIONARY_PATH}); + + # calculate and store the entropy stats + my %stats = $self->_calculate_entropy_stats(); + $self->{_CACHE_ENTROPYSTATS} = \%stats; + + # warn if we need to + unless(uc $SUPRESS_ENTROPY_WARNINGS eq 'ALL'){ + # blind warning if needed + unless(uc $SUPRESS_ENTROPY_WARNINGS eq 'BLIND'){ + if($self->{_CACHE_ENTROPYSTATS}->{entropy_blind_min} < $ENTROPY_MIN_BLIND){ + $_CLASS->_warn('loaded dictionary and config combination results in low minimum entropy for blind attacks ('.$self->{_CACHE_ENTROPYSTATS}->{entropy_blind_min}.", warning threshold is $ENTROPY_MIN_BLIND)"); + } + } + + # seen warnings if needed + unless(uc $SUPRESS_ENTROPY_WARNINGS eq 'SEEN'){ + if($self->{_CACHE_ENTROPYSTATS}->{entropy_seen} < $ENTROPY_MIN_SEEN){ + $_CLASS->_warn('loaded dictionary and config combination results in low minimum entropy for attacks assuming full knowledge ('.$self->{_CACHE_ENTROPYSTATS}->{entropy_seen}.", warning threshold is $ENTROPY_MIN_SEEN)"); + } + } + } + + # to keep perl critic happy + return 1; +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : To nicely print a Math::BigInt object +# Returns : a string representing the object's value in scientific notation +# with 1 digit before the decimal and 2 after +# Arguments : 1. a Math::BigInt object +# Throws : Croaks on invalid invocation or args +# Notes : +# See Also : +sub _render_bigint{ + my $class = shift; + my $bigint = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $bigint && $bigint->isa('Math::BigInt')){ + $_CLASS->_error('invalid args, must pass a Math::BigInt object'); + } + + # convert the bigint to an array of characters + my @chars = split //sx, "$bigint"; + + # render nicely + if(scalar @chars < 3){ + return q{}.join q{}, @chars; + } + # start with the three most signifficant digits (as a decimal) + my $ans = q{}.$chars[0].q{.}.$chars[1].$chars[2]; + # then add the scientific notation bit + $ans .= 'x10^'.(scalar @chars - 1); + + # return the result + return $ans; +} + +#####-SUB-###################################################################### +# Type : CLASS (PRIVATE) +# Purpose : To try resolve a code-ref to a function name +# Returns : A string as a scalar, or 'CODEREF' if unable to resolve +# Arguments : 1. A coderef +# Throws : Croaks on invalid invocation or args +# Notes : Based on code from: http://stackoverflow.com/a/7419346 +# See Also : +sub _coderef_to_subname{ + my $class = shift; + my $coderef = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + $_CLASS->_error('invalid invocation of class method'); + } + unless(defined $coderef && ref $coderef eq 'CODE'){ + $_CLASS->_error('invalid arguments, must pass a code ref'); + } + + # try decode the reference + my $cv = svref_2object($coderef); + unless($cv && $cv->isa('B::CV')){ + return 'CODEREF'; + } + my $gv = $cv->GV; + unless($gv){ + return 'CODEREF'; + } + + # figure out what the ref is referring to + my $name = ''; + if(my $st = $gv->STASH){ + $name = $st->NAME.'::'; + } + my $n = $gv->NAME; + if($n){ + $name .= $n; + if($n eq '__ANON__'){ + $name .= ' defined at '.$gv->FILE.':'.$gv->LINE; + } + } + + # return the name + return $name; +} + +1; # because Perl is just a little bit odd :) +__END__ + +#============================================================================== +# User Documentation +#============================================================================== + +=head1 NAME + +XKPasswd - A secure memorable password generator + +=head1 VERSION + +This documentation refers to XKPasswd version 2.1.1. + +=head1 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); + +=head1 DESCRIPTION + +A secure memorable password generator inspired by the wonderful XKCD webcomic +at L and Steve Gibson's Password Haystacks page at +L. This is the Perl library that powers +L. + +=head2 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 I. 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 I 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 + +=head2 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. + +=head3 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 C to our password, if it lands tails up, we add a C. + +If you made a one-letter password in this way there would only be two +possibilities, C, or C, or two permutations. If you made a two-letter +password in this way there would be four possible combinations, or +permutations, C, C, C, and C. 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 C

for a given length of password C +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 I. So, +for the coin toss the alphabet was just C and C, and for the dice it +was C<1>, C<2>, C<3>, C<4>, C<5>, and C<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 C. + +As you can probably infer from the two examples above, the formula for the +number of possible permutations C

for a password of length C created from +an alphabet of size C 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 C<0> to C<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 C 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 C 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. + +=head3 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 C is quite straightforward, we just need to +know the size of the alphabet resulting from the configuration C, and the +minimum length of passwords generated with the configuration C, and plug +those values into this formula: + + Eb = Log(2)(A^L) + +Calculating C 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 C 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 L section +(C) was generated using the preset C. +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 C 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 C<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 C<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. + +=head1 SUBROUTINES/METHODS + +=head2 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 I, and specified via the +C 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 C 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 (C) is distributed with this +module. + +=head2 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. + +=over 4 + +=item * + +C - the alterations that should be made to the case of the +letters in the randomly chosen words that make up the bulk of the randomly +generated passwords. Must be a scalar, and the only valid values for this key +are: + +=over 4 + +=item - + +C - each alternate word will be converted to all upper case and +all lower case. + +=item - + +C - the first letter in every word will be converted to upper case, +all other letters will be converted to lower case. + +=item - + +C - the first letter in every word will be converted to lower case, +all other letters will be converted to upper case. + +=item - + +C - all letters in all the words will be converted to lower case. B + +=item - + +C - the capitalisation used in the randomly generated password will be +the same as it is in the dictionary file. + +=item - + +C - each word will be randomly converted to all upper case or all lower +case. + +=item - + +C - all letters in all the words will be converted to upper case. B + +=back + +The default value returned by C is C. + +=item * + +C - a hashref containing zero or more character +substitutions to be applied to the words that make up the bulk of the generated +passwords. The keys in the hashref are the characters to be replaced, and must +be single alpha numeric characters, while the values in the hashrefs are the +replacements, and can be longer. + +=item * + +C - the number of randomly chosen words use as the basis for the +generated passwords. The default value is four. For security reasons, at least +two words must be used. + +=item * + +C - the total desired length of the password when using adaptive +padding (C set to C). Must be a scalar with an integer +values greater than or equal to 12 (for security reasons). + +=item * + +C - this key is optional. It can be used to override +the contents of C when C is set to +C. If present this key must contain an arrayref containing at +least two single characters as scalars. + +=item * + +C - the character to use when padding the front and/or back +of the randomly generated password. Must be a scalar containing either a single +character or one of the special values C (indicating that a character +should be chosen at random from the C), or C (use +the same character used to separate the words). This key is only needed if +C is set to C or C. The default value returned +by C is C. + +=item * + +C & C - the number of +symbols to pad the front and end of the randomly generated password with. Must +be a scalar with an integer value greater than or equal to zero. These keys are +only needed if C is set to C. The default value returned +by C for both these keys is 2. + +=item * + +C & C - the number of random +digits to include before and after the randomly chosen words making up the bulk +of the randomly generated password. Must be scalars containing integer values +greater than or equal to zero. The default value returned by +C for both these keys is 2. + +=item * + +C - the type of symbol padding to be added at the start and/or +end of the randomly generated password. Must be a scalar with one of the +following values: + +=over 4 + +=item - + +C - do not pad the generated password with any symbol characters. + +=item - + +C - a specified number of symbols will be added to the front and/or +back of the randomly generated password. If this option is chosen, you must +also specify the keys C, C & +C. + +=item - + +C - no symbols will be added to the start of the randomly generated +password, and the appropriate number of symbols will be added to the end to +make the generated password exactly a certain length. The desired length is +specified with the key C, which is required if this padding type +is specified. + +=back + +The default value returned by C is C. + +=item * + +C - a reference to the function to be used use to generate +random numbers during the password generation process. The function generate +an array of random decimal numbers between 0 and 1, take one argument, the +number of random numbers to generate, and it must return an array. By default +the function C is used. + +=item * + +C - the number of random digits to generate at a time when +ever the instance's cache of randomness runs low. Must be an integer greater +than or equal to 1, or the special value C. When the value is set to +C a call to C is used to determine the amount of random +numbers needed to generate a single password, and this value is used as the +random increment. The default value is c. + +=item * + +C - this key is optional. It can be used to override +the contents of C when C is set to +C. If present this key must contain an arrayref containing at +least two single characters as scalars. + +=item * + +C - the character to use to separate the words in the +generated password. Must be a scalar, and acceptable values are a single +character, or, the special values C (indicating that no separator should +be used), or C, indicating that a character should be chosen at random +from the C. The default value returned by C +is C. + +=item * + +C - an arrayref containing at least two single characters as +scalars. This alphabet will be used when selecting random characters to act as +the separator between words, or the padding at the start and/or end of +generated passwords. Note that this key can be overridden by setting either or +both C and C. The default value returned +by C is C<[qw{! @ $ % ^ & * - _ + = : | ~ ?}]> + +=item * + +C & C - the minimum and maximum length of the +words that will make up the generated password. The two keys must be scalars, +and can hold equal values, but C may not be smaller than +C. For security reasons, both values must be greater than 3. +The default values returned by C is are 4 & 8. + +=back + +=head2 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 C, +C, C, and C. + +The following presets are defined: + +=over 4 + +=item * + +C - a preset respecting the many prerequisites Apple places on Apple +ID passwords. Apple's official password policy is located here: +L. Note that Apple's knowledge base article +omits to mention that passwords can't be longer than 32 characters. This preset +is also configured to use only characters that are easy to type on the standard +iOS keyboard, i.e. those appearing on the letters keyboard (C) or the +numbers keyboard C<.?123>, and not those on the harder to reach symbols +keyboard C<#+=>. Below is a sample password generated with this preset: + + @60:london:TAUGHT:forget:70@ + +=item * + +C - the default configuration. Below is a sample password generated +with this preset: + + ~~12:settle:SUCCEED:summer:48~~ + +=item * + +C - a preset for 14 character NTMLv1 (NTLM Version 1) passwords. ONLY USE +THIS PRESET IF YOU MUST! The 14 character limit does not allow for sufficient +entropy in the case where the attacker knows the dictionary and config used +to generate the password, hence this preset will generate low entropy warnings. +Below is a sample password generated with this preset: + + 0=mAYAN=sCART@ + +=item * + +C - a preset for creating fake answers to security questions. It +generates long nonsense sentences ending in C<.> C or C, for example: + + Wales outside full month minutes gentle? + +=item * + +C - a preset for websites that don't allow more than 16 character long +passwords. Because 16 characters is not very long, a large set of +symbols are chosen from for the padding and separator. Below is a sample +password generated with this preset: + + :baby.ohio.DEAR: + +=item * + +C - a preset for websites that don't allow more than 32 character long +passwords. Below is a sample password generated with this preset: + + +93-took-CASE-money-AHEAD-31+ + +=item * + +C - a preset for generating 63 character long WPA2 keys (most routers +allow 64 characters, but some only 63, hence the odd length). Below is a sample +password generated with this preset: + + 2736_ITSELF_PARTIAL_QUICKLY_SCOTLAND_wild_people_7441!!!!!!!!!! + +=item * + +C - a preset inspired by the original XKCD comic +(L), but with some alterations to provide sufficient +entropy to avoid low entropy warnings. Below is a sample password generated +with this preset: + + KING-madrid-exercise-BELGIUM + +=back + +=head2 ENTROPY CHECKING + +For security reasons, this module's default behaviour is to warn (using +C) 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 +C or C) 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: + +=over 4 + +=item * + +C<$XKPasswd::ENTROPY_MIN_BLIND> - the minimum acceptable entropy (in bits) for +a brute-force attack. The default value for this variable is 78 (equivalent to +a 12 character password consisting of mixed-case letters, digits, and symbols). + +=item * + +C<$XKPasswd::ENTROPY_MIN_SEEN> - the minimum acceptable entropy (in bits) for a +worst-case attack (where the dictionary and configuration are known). The default +value for this variable is 52 (equivalent to an 8 character password consisting +of mixed-case letters, digits, and symbols). + +=item * + +C<$XKPasswd::SUPRESS_ENTROPY_WARNINGS> - this variable can be used to suppress +one or both of the entropy warnings. The following values are valid (invalid +values are treated as being C): + +=over 4 + +=item - + +C - no warnings are suppressed. This is the default value. + +=item - + +C - only warnings for the worst-case scenario are suppressed. + +=item - + +C - only warnings for the best-case scenario are suppressed. + +=item - + +C - all entryopy warnings are suppressed. + +=back + +=back + +=head3 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 +I calculator (L). + +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. + +=head2 RANDOM FUNCTIONS + +In order to avoid this module relying on any non-standard modules, the default +source of randomness is Perl's built-in C 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 L 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 C 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 +C 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. + +=head2 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: + +=head3 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 C 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. + + +=head2 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. + +=head2 CLASS METHODS + +B - All class methods must be invoked via the package name, or they will +croak. + +=head3 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. + +=head3 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: + +=over 4 + +=item * + +C - the minimum length a password generated with the given +config could be. + +=item * + +C - the maximum length a password generated with the given +config could be. (see caveat below) + +=item * + +C - the amount of random numbers needed to generate a +password using the given config. + +=back + +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 C is not set to C. 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. + +=head3 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. + +=head3 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 C, 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}); + +=head3 defined_presets() + + my @preset_names = XKPasswd->defined_presets(); + +This function returns the list of defined preset names as an array of scalars. + +=head3 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"; + } + +=head3 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 C 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. + +=head3 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 C is assumed. + +=head3 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. + +=head2 METHODS + +B - all methods must be invoked on an XKPasswd object or they will croak. + +=head3 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. + +=head3 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. + +=head3 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. + +=head3 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. + +=head3 passwords() + + my @passwords = $xkpasswd_instance->passwords(10); + +This function generates a number of passwords and returns them all as an array. + +The function uses C to genereate the passwords, and hence will +croak if there is an error generating any of the requested passwords. + +=head3 stats() + + my %stats = $xkpasswd_instance->stats(); + +This function generates a hash containing stats about the instance indexed by +the following keys: + +=over 4 + +=item * + +C & C - the minimum +and maximum word lengths allowed by the dictionary filter (defined by config +keys C and C) + +=item * + +C - the path to the dictionary file loaded into the instance. + +=item * + +C - the number of words loaded from the dictionary +file that meet the criteria defined by the loaded config. + +=item * + +C - the percentage of the words in the +dictionary file that are available for use with the loaded config. + +=item * + +C - the total number of words loaded from the +dictionary file. + +=item * + +C - the entropy (in bits) of the shortest password +the loaded config can generate from the point of view of a brute-force +attacker. + +=item * + +C - the entropy (in bits) of the longest password +the loaded config can generate from the point of view of a brute-force +attacker. + +=item * + +C - the entropy (in bits) of the average length +of passwords the loaded config can generate from the point of view of a +brute-force attacker. + +=item * + +C - the entropy (in bits) of passwords generated by the +instance assuming the dictionary and config are known to the attacker. + +=item * + +C - the minimum length of passwords generated by the +loaded config. + +=item * + +C - the maximum length of passwords generated by the +loaded config. + +=item * + +C - the number of permutations a brute-force +attacker would have to try to be sure of cracking the shortest possible +passwords generated by this instance. Because this number can be very big, it's +returned as a C object. + +=item * + +C - the number of permutations a brute-force +attacker would have to try to be sure of cracking the longest possible +passwords generated by this instance. Because this number can be very big, it's +returned as a C object. + +=item * + +C - the number of permutations a brute-force +attacker would have to try to be sure of cracking an average length password +generated by this instance. Because this number can be very big, it's returned +as a C object. + +=item * + +C - the number of permutations an attacker with a +copy of the dictionary and config would need to try to be sure of cracking a +password generated by this instance. Because this number can be very big, it's +returned as a C object. + +=item * + +C - the number of passwords this instance has generated. + +=item * + +C - the number of random numbers needed to +generate a single password using the loaded config. + +=item * + +C - the number of random numbers currently cached within +the instance. + +=item * + +C - the number of random numbers generated at +once to replenish the cache when it's empty. + +=item * + +C - the function used by the instance to +generate random numbers. + +=back + +=head3 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 + +=head3 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. + +=head1 DIAGNOSTICS + +By default this module does all of it's error notification via the functions +C, C, and C from the C module. Optionally, +all error messages can also be printed. To enable the printing of messages, +set C<$XKPasswd::LOG_ERRORS> to a truthy value. All error messages will then be +printed to the stream at C<$XKPasswd::LOG_STREAM>, which is set to C by +default. + +Ordinarily this module produces very little output, to enable more verbose +output C<$XKPasswd::DEBUG> can be set to a truthy value. If this is set, all +debug messages will be printed to the stream C<$XKPasswd::LOG_STREAM>. + +This module produces output at three severity levels: + +=over 4 + +=item * + +C - this output is completely suppressed unless C<$XKPasswd::DEBUG> is +set to a truthy value. If not suppressed debug messages are always printed to +C<$XKPasswd::LOG_STREAM> (regardless of the value of C<$XKPasswd::LOG_ERRORS>). + +=item * + +C - warning messages are always thrown with C, and also +printed to C<$XKPasswd::LOG_STREAM> if C<$XKPasswd::LOG_ERRORS> evaluates to +true. + +=item * + +C - error messages are usually thrown with C, but will be +thrown with C if C<$XKPasswd::DEBUG> evaluates to true. If +C<$XKPasswd::LOG_ERRORS> evaluates to true errors are also printed to +C<$XKPasswd::LOG_STREAM>, including a stack trace if C<$XKPasswd::DEBUG> +evaluates to true and the module C is installed. + + +=back + +=head1 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. + +=head1 DEPENDENCIES + +This module uses the following standard Perl modules: + +=over 4 + +=item * + +C - L + +=item * + +C - L +=item * + +C - L + +=item * + +C - L + +=item * + +C - L + +=item * + +C - L + +=item * + +C - L + +=back + +The module can also optionally use the following non-standard Perl modules: + +=over 4 + +=item * + +C - L + +Used for printing stack traces with error messages if +C<$XKPasswd::DEBUG> and C<$XKPasswd::LOG_ERRORS> both evaluate to true. If the +module is not installed the stack traces will be omitted from the log messages. + +=back + +=head1 INCOMPATIBILITIES + +This module has no known incompatibilities. + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Bart Busschots (L) Patches are welcome. + +=head1 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: + +=over 4 + +=item 1. + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +=item 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. + +=back + +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. + + +=head1 AUTHOR + +Bart Busschots (L) \ No newline at end of file diff --git a/XKPasswd/Util.pm b/XKPasswd/Util.pm new file mode 100644 index 0000000..a1ee477 --- /dev/null +++ b/XKPasswd/Util.pm @@ -0,0 +1,305 @@ +package XKPasswd::Util; + +use strict; +use warnings; +use Carp; # for nicer 'exception' handling for users of the module +use English qw( -no_match_vars ); # for more readable code +use lib '../'; # to keep Komodo edit happy while programming +use XKPasswd; + +# conitionally load optional modules +my $JSON_AVAILABLE = 0; +eval{ + require JSON; + $JSON_AVAILABLE = 1; +}; + +# Copyright (c) 2014, Bart Busschots T/A Bartificer Web Solutions All rights +# reserved. +# +# Code released under the FreeBSD license (included in the POD at the bottom of +# ../XKPasswd.pm) + +# +# === NOTE===================================================================== +# This module is not needed to use XKPasswd.pm, it merely contains utility +# functions that are useful during development. +# + +#============================================================================== +# Code +#============================================================================== + +# +# 'Constants'------------------------------------------------------------------ +# + +# version info +use version; our $VERSION = qv('1.1_01'); + +# utility variables +my $_CLASS = 'XKPasswd::Util'; + +# +# Public Class (Static) functions --------------------------------------------- +# + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Test all presets defined in the XKPasswd module for validity and +# against a given dictionary file for sufficient enthropy +# Returns : Always returns 1 (to keep perlcritic happy) +# Arguments : 1. the path to a dictionary file +# Throws : Croaks on invalid invocation or args, or if there is a problem +# testing the configs +# Notes : +# See Also : +sub test_presets{ + my $class = shift; + my $dict_path = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + XKPasswd->_error('invalid invocation of class method'); + } + unless(defined $dict_path && -f $dict_path){ + XKPasswd->_error('invalid args, must pass a dictionary file path'); + } + + # get the list of config names from the parent + my @preset_names = XKPasswd->defined_presets(); + print 'INFO - found '.(scalar @preset_names).' presets ('.(join q{, }, @preset_names).")\n"; + + # first test the validity of all preset configs + print "\nINFO - testing preset config validity\n"; + XKPasswd->_check_presets(); + print "INFO - Done testing config validity\n"; + + # then test each config for sufficient entropy by instantiating an instance with each one + print "\nINFO - testing preset config + dictionary entropy\n"; + foreach my $preset (@preset_names){ + print "Testing '$preset'\n"; + my $xkpasswd = XKPasswd->new($dict_path, $preset); + my %stats = $xkpasswd->stats(); + print "$preset: TOTAL WORDS=$stats{dictionary_words_total}, AVAILABLE WORDS=$stats{dictionary_words_filtered} ($stats{dictionary_words_percent_avaialable}%)"; + print 'RESTRICTIONS: '; + if($stats{dictionary_filter_length_min} == $stats{dictionary_filter_length_max}){ + print "length=$stats{dictionary_filter_length_min}\n"; + }else{ + print "$stats{dictionary_filter_length_min}>=length<=$stats{dictionary_filter_length_max}\n"; + } + print "$preset: BLIND=$stats{password_entropy_blind_min} (need $XKPasswd::ENTROPY_MIN_BLIND), SEEN=$stats{password_entropy_seen} (need $XKPasswd::ENTROPY_MIN_SEEN)\n"; + } + print "INFO - Done testing entropy\n"; + + # to keep perlcritic happy + return 1; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Generate a sample password with each preset with a given +# dictionary file +# Returns : Always returns 1 to keep perlcritic happy +# Arguments : NONE +# Throws : Croaks on invalid invocation +# Notes : +# See Also : +sub print_preset_samples{ + my $class = shift; + my $dict_path = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + XKPasswd->_error('invalid invocation of class method'); + } + unless(defined $dict_path && -f $dict_path){ + XKPasswd->_error('invalid args, must pass a dictionary file path'); + } + + foreach my $preset (XKPasswd->defined_presets()){ + print "$preset: ".xkpasswd($dict_path, $preset)."\n"; + } + + # to keep perlcritic happy + return 1; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Convert a JSON string into an XKPasswd config hashref +# Returns : an XKPasswd config hashref +# Arguments : 1. the JSON string as a scalar +# Throws : Croaks on invalid invocation, invalid args, invalid config, and +# if the JSON module is not available +# Notes : Since you can't send code refs via JSON, the random function +# in all hashrefs is set to the default value +# See Also : +sub config_from_json{ + my $class = shift; + my $json_string = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + XKPasswd->_error('invalid invocation of class method'); + } + unless(defined $json_string && ref $json_string eq q{} && length $json_string){ + XKPasswd->_error('invalid args, must pass a JSON string'); + } + + # make sure the JSON module is available + unless($JSON_AVAILABLE){ + XKPasswd->_error(q{Perl JSON module not avaialble, and required for this function}); + } + + # try parse the passed string + my $loaded_config = JSON->new->utf8->decode($json_string); + unless($loaded_config){ + XKPasswd->_error('Failed to parse JSON string'); + } + + # set the ranom generator to the default value (can't sned code refs via JSON) + $loaded_config->{random_function} = \&XKPasswd::basic_random_generator; + + # make sure the config is valid + eval{ + XKPasswd->is_valid_config($loaded_config, 'do_croak'); # returns 1 on success + }or do{ + XKPasswd->_error("Config failed to validate with error: $EVAL_ERROR"); + }; + + # return the config + return $loaded_config; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Generate n passwords and return them, and the entropy stats as a +# JSON string. +# Returns : A JSON string as a scalar representing a hashref contianing an +# array of passwords indexed by 'passwords', and a hashref of +# entropy stats indexed by 'stats'. The stats hashref itself is +# indexed by: 'password_entropy_blind', +# 'password_permutations_blind', 'password_entropy_blind_min', +# 'password_entropy_blind_max', 'password_permutations_blind_max', +# 'password_entropy_seen' & 'password_permutations_seen' +# Arguments : 1. an XKPasswd object +# 2. the number of passwords to generate +# Throws : Croaks on invalid invocation, invalid args, if there is a +# problem generating the passwords, statistics, or converting the +# results to a JSON string, or if the JSON module is not +# available. +# Notes : +# See Also : +sub passwords_json{ + my $class = shift; + my $xkpasswd = shift; + my $num = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + XKPasswd->_error('invalid invocation of class method'); + } + unless(defined $xkpasswd && $xkpasswd->isa('XKPasswd')){ + XKPasswd->_error('invalid args, must pass an XKPasswd object as the first arg'); + } + unless(defined $num && ref $num eq q{} && $num =~ m/^\d+$/sx){ + XKPasswd->_error('invalid args, must pass the number of passwords to generate as the second arg'); + } + + # make sure the JSON module is available + unless($JSON_AVAILABLE){ + XKPasswd->_error(q{Perl JSON module not avaialble, and required for this function}); + } + + # try generate the passwords and stats - could croak + my @passwords = $xkpasswd->passwords($num); + my %stats = $xkpasswd->stats(); + + # generate the hashref containing the results + my $responseObj = { + passwords => [@passwords], + stats => { + password_entropy_blind => $stats{password_entropy_blind}, + password_permutations_blind => XKPasswd->_render_bigint($stats{password_permutations_blind}), + password_entropy_blind_min => $stats{password_entropy_blind_min}, + password_permutations_blind_min => XKPasswd->_render_bigint($stats{password_permutations_blind_min}), + password_entropy_blind_max => $stats{password_entropy_blind_max}, + password_permutations_blind_max => XKPasswd->_render_bigint($stats{password_permutations_blind_max}), + password_entropy_seen => $stats{password_entropy_seen}, + password_permutations_seen => XKPasswd->_render_bigint($stats{password_permutations_seen}), + }, + }; + + # try generate the JSON string to return + my $json_string = q{}; + eval{ + $json_string = JSON->new()->encode($responseObj); + 1; # ensure truthy evaluation on succesful execution + }or do{ + XKPasswd->_error('failed to render hashref as JSON string with error: $EVAL_ERROR'); + }; + + # return the JSON string + return $json_string; +} + +#####-SUB-###################################################################### +# Type : CLASS +# Purpose : Resturn the presets defined in the XKPasswd module as a JSON +# string +# Returns : A JSON String as a scalar. The JSON string represets a hashref +# with three keys - 'defined_keys' contains an array of preset +# identifiers, 'presets' contains the preset configs indexed by +# preset identifier, and 'preset_descriptions' contains the a +# hashref of descriptions indexed by preset identifiers +# Arguments : NONE +# Throws : Croaks on invalid invocation, if the JSON module is not +# available, or if there is a problem converting the objects to +# JSON +# Notes : +# See Also : +sub presets_json{ + my $class = shift; + + # validate the args + unless(defined $class && $class eq $_CLASS){ + XKPasswd->_error('invalid invocation of class method'); + } + + # make sure the JSON module is available + unless($JSON_AVAILABLE){ + XKPasswd->_error(q{Perl JSON module not avaialble, and required for this function}); + } + + # assemble an object cotaining the presets with any keys that can't be + # converted to JSON removed + my @defined_presets = XKPasswd->defined_presets(); + my $sanitised_presets = {}; + my $preset_descriptions = {}; + foreach my $preset_name (@defined_presets){ + $sanitised_presets->{$preset_name} = XKPasswd->preset_config($preset_name); + $sanitised_presets->{$preset_name}->{random_function} = undef; + $sanitised_presets->{$preset_name}->{random_increment} = undef; + $preset_descriptions->{$preset_name} = XKPasswd->preset_description($preset_name); + } + my $return_object = { + defined_presets => [@defined_presets], + presets => $sanitised_presets, + preset_descriptions => $preset_descriptions, + }; + + # try convert the object to a JSON string + my $json_string = q{}; + eval{ + $json_string = JSON->new()->encode($return_object); + 1; # ensure truthy evaluation on succesful execution + }or do{ + XKPasswd->_error("failed to render presets as JSON string with error: $EVAL_ERROR"); + }; + + # return the JSON string + return $json_string; +} + +1; # because perl is a bit special \ No newline at end of file diff --git a/contrib/ajax-loader.gif b/contrib/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..2fd8e0737ed41ba0a01b98f40688c325d5574762 GIT binary patch literal 404 zcmZ?wbhEHb)Mnsj_{hNU|Nnmm28O1lCLpQ!pWDwhB-q(8z|~04fSC~_^iRsUC^fMp zHASI3vm`?yF)OhmCqFSoFEcMKpF!~_3nv#)l@1UyfDB|{GHU7LI6dv=jpolsxuoxf zeLmg#z^pAIa$%Z!?Y&w1mh+_RdR}iorT6Q|=AU*u7AP3`hypFaG{*J_&=>{&#N>^$ zT8^)&`8*@>-uIrA88haeTIl8MRiLwjJ^Sp&wJ%lpHZi7(NaaU-s_i@FclpsQour`S z>#>^%a+m?o#FewMqVMEC;^z|HUiq-HXUUZXD|YcM<39A-C)up?`N?xHe+JE(vBva_ zL67^Xm!D<=OWdP)4-QibtN)VXQDpczE`xXAkUjh%RI>;okxb7K@0kpyQ1k_Y(|Oe7$m(^ zNYX>mI||sUbmn+c3<&FnE=4u#()KBS^SH8e)Qs5i!#lY=$-1gbH6VluzU=m=EP78&5vQ z-?+fFP-G2l&l_QzYealK$;1Rl?FkzXR&Jv@fBPNjCr#AYRyJ7UJQ0v#?)7Ott=>3`#-pV!7>9}>Q1jL)H6h&gkP@3nI=+F3nA~M>u#(n* z8T!#8oEw&-mED4!h4s!N@Jo3S7N&Q6%6l3}nlcd~X@>;uelvPsSkXIgg~e+^T1zSf z3SNj(5%jK~i8@b;C9VHk(~TedF+gQSL8D5xnVSSWAVY>J9b+m>@{iq7_KE}go~11+5s4;8hc+i0Xa zI1j@EX5!S+Me6HNqKzU5YQwL;-W5$p%ZMKMeR<%zp69-~?<4?8|C8S?bklXr4v&Ov zb&06v2|-x?qB`90yn>Qi%Sh2^G4n)$ZdyvTPf9}1)_buUT7>`e2G&2VU@~Bb(o+Mz zi4)>IxlSY${Dj4k={-9RzU^W5g9|2V5RZ2ZulL9s2xQbZ@r6eP9Ra5u(s|C0Nj#&4>wTSkb?%#=9?@ z^oxDy-O@tyN{L@by(WWvQ3%CyEu8x{+#Jb4-h&K9Owi)2pgg+heWDyked|3R$$kL@A z#sp1v-r+=G4B8D6DqsDH0@7OztA7aT9qc1Py{()w`m``?Y0&gi2=ROcc-9+nU^I6< zT=e_Y=vSnG@?3Ue{BW5ONFttcE!R-R_W4O01|0-|K-YNXLo2`4Qv z`r1LxR6#yf3FB%T95gJnaKKivA~Z}S9A(ZxEDK}O3T04USJ P00000NkvXXu0mjf^IS-S literal 0 HcmV?d00001 diff --git a/contrib/famfamfam_silk_icons_v013/icons/delete.png b/contrib/famfamfam_silk_icons_v013/icons/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..08f249365afd29594b51210c6e21ba253897505d GIT binary patch literal 715 zcmV;+0yO=JP)C4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(8Ia`-u_IEhxG7U<13kSsMW+$lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc literal 0 HcmV?d00001 diff --git a/contrib/famfamfam_silk_icons_v013/icons/error.png b/contrib/famfamfam_silk_icons_v013/icons/error.png new file mode 100644 index 0000000000000000000000000000000000000000..628cf2dae3d419ae220c8928ac71393b480745a3 GIT binary patch literal 666 zcmV;L0%iS)P)eOSYYtbpBV}~vsBnU!_?2tr-P=|^T zED%wc9ezHgW@NMb!^uT_|SvCpFLJylbx zY%bpaTGI8IYXMN$9w<3j9VkA~NYOKEQXsj?6a9_hcwfU$acAhJhB)zb_w@MVUEy@S zX&I>K-R!bhu3?(6bHWIg$HEl7{9g>>&l_qdd+UYb(1~BCo9LptNq&8>!yoJ3Ui(i5 zRJ|XnYBklL!{@$-7=3mJ>P@1c=7Oc79e-V7yf+%lD2!I;Y&nXBZ>=B!5?CB>LvEx6 znI%n)qqi$#X#wKB(U7XP2P=+4{b@j#r%9-K(8UqtSDk>0UKzf*HM9yqMZ1D!$2MdZ zR=`U>0zhOH1XqN?nY@AQqB7)Fp4{v&dKXvb43hZKvnN8;Po;+jY*}~*Z|W9Q0W%{D z^T}Cc<|r(Su=1K=P5>Z4 zg`et&Va}tdzBS-G-ZcO)zCWpJvGQwrHZ`@wpM420ac@bI5~KkTFfGEM3sPWO8co4^fI6lPnA)Y{ef%@{+SnoUk0+dW+*{8WvF8}}l07*qoM6N<$g7cXs A&j0`b literal 0 HcmV?d00001 diff --git a/contrib/famfamfam_silk_icons_v013/icons/exclamation.png b/contrib/famfamfam_silk_icons_v013/icons/exclamation.png new file mode 100644 index 0000000000000000000000000000000000000000..c37bd062e60c3b38fc82e4d1f236a8ac2fae9d8c GIT binary patch literal 701 zcmV;u0z&N#0$9Ug7g~-`rQ^qx~m@y2OU8A z#zh~=7n#Z$Z*fx-GOtDf07cgx0suCz_W(2~Y(0tf@FX@P6EPuM_dgn$vj9LucO)%W zw%HgMW>=#oL>nZ>M&NEf08>)#)k<{$fCT_r>rPi=BV=hFh6WS^qqze>C6Ek}o{M5% za|@JGowu0t{&hgNzySHZxy@LTNh);YzZ2zSp_ zl$^T&Dnc|NLb&RD_!4>pt@VHdP)ZGER%5ZmWEe$lryR&y;2u^3cOkO4#6c%-(EY6a{600000NkvXXu0mjfxS2AI literal 0 HcmV?d00001 diff --git a/contrib/famfamfam_silk_icons_v013/icons/information.png b/contrib/famfamfam_silk_icons_v013/icons/information.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd1aef900803abba99b26920337ec01ad5c267 GIT binary patch literal 778 zcmV+l1NHogP)BVme|mWaqy4$_pJm?y9KM{-*hp?1+Ey3e-CEDooTa!B;e(Q>TSF?bj>5At13y1p zriN3w3x~5SfZj{@J4M{kp{?=M_Lh2bV+5LH)Q)5W!-ePA$RgE1@5f1cyHki0Y}JyVEYZF(LD$xXlt$7A5CgE@ zpV-&l%vf;=5kZ2-2gi@Y6J&=cuwt>!vJ^#(&n|LcZyUzi6Duj$$hJ1s*HD-#;k-w@ zpdrwAuoDG_N2bvb07G$Zk*?Hc)JLtW4yqOnic_$zO7NZ#l>Fm){;fE?b$IbOaX2fe z0la4g0Dfw2xk7Wi7NapVD8YMPCZu?A1QCK*67dgsvRKBLFtrM>?$%&_lD1882mzdO zWPdw5KWw6IT`m1b_8=lS5jt8D3=RDa=&jWzR-)S@56WMslZ~mKu1)-wpXB>rNBQ>N zU#K`#1B&v|_AQK;7I~B}OdGiUT9LX>f0xm6<;LeP!=vFjPsUQF*wCJ*dO)4YBypgdiuF!=i@6Zyi7F|q#K zz?tlSZULa@t1D?$e;f@b36&N!V2mjOHw|*!53O z8?H=tQYk|>Cj(bDFFSisJm{3c{(`{{9`xX;UIalphYWYof516bqzvmo=&1(}owA8I zTsvIaF3p-W@%MgNf&p)P@P*&+d++=5@^}f@wvCfSkRF+bMMNS|#A0WlDB+YK2y={{ zW*9oIt1zrumX*`>*0!o1pxJDo@Os=aaDx@h2sp}Sh9mLomrtR$$4URHn(58*VY*Oi z&9a(#MlJg~aK&Ls&n?^^2&K-2;Cxz#=gubs7&sfn*d;msbZIB2S{7UrfgXloq%2d* zF3j|W`g;USJSf66O&^G(9Mw=QX*`2Qqd7M<8jJTQM2y^CN4;Lh=$*VLrZaJrqngQ) zSe!JTL93-tPmLw06MT7llkObDw?1&V_qCT-v93F2v6^d#{`49Vp7_O67-! z!^1X2F@(F1_KEX>I7w1Cc59BtGYE&fS2s!qEfMZL*67Z$v7F{`@062e1)F6@;~B_u zG*>7EYE?Ci{Ne>>6vKLMkZqg6L1*Ifi-XwRm+`qQ)kxzRh@!OJHkJ8jD`va0W8wXa zBs9%t->GoWnYgdtEj)jRHr0~Gw~4e_)dL$(Ug_D)6)QfTme7|F_;7z#J1DN{D3pwv zrrW+%4$#U@&i>}ZI}X~g|Ncx2)HZ)}r_;vArKDqUlK3mY0OPJGypT=p+5i9m07*qo IM6N<$f}tut9smFU literal 0 HcmV?d00001 diff --git a/contrib/fugue-icons-3.5.6/icons-shadowless/minus-white.png b/contrib/fugue-icons-3.5.6/icons-shadowless/minus-white.png new file mode 100644 index 0000000000000000000000000000000000000000..dba9c486971e53a2db7061ff3057ca07b2782fa4 GIT binary patch literal 625 zcmV-%0*?KOP)3)32mvp-R4J9R5t}C2 z-Rw_i-nt75HJ1*|+qW~{_r007vxPX0;UXr`QhT-!#bOC`{W2(uTID!y6T@rhi~n$4 z^t~vGny%{{_WP$W91fuMb~zD94FT&Mj2pGvMdQJ(OE5c=q>rt@Jm`$fR@>P?%oc{; zH3^c|LYBK;DZs+j5-i=6jaR!rn*At(RD`3&_x)-V*p2%7oHRef z(K`e|kaLPrjxdBJP9`uI3^!Nr>BfASKbM>nLCnfs-5^fZg5$Vrt4n1DNR9TR(FknY zVf!!)Se)ktxPL=~osTYE@(Us9pIfZXYWVcv@$>I+b~Zp!B8Kb?QPe<^rtk~tuw4(J zGR-qi)`B2t05MfEo=8@dB4Y7^k;XV_3yLz;?f$gQE1CzNwjWL$oL(=2BP-!#0;)Q_ z_oeNdPadgQfjv<`mK8YYj2S0uL6%F+R=d!%`YM*IV%f@n{nu|9zIMbOaWVm(7Z1Ha z*?Rpx@J{~73@8YBr`R!)-+Ta%u*AttB2AWc8pF#Sw^6$z8Ox>!b7hWQ?vWM3{x=_5 zZNF!`ac-0gG_orjCs>7)#%RLM{h83IY<6-OdhmI-k_cSH{t7SvZM+RG=2U-c00000 LNkvXXu0mjf5;Gu~ literal 0 HcmV?d00001 diff --git a/contrib/fugue-icons-3.5.6/icons-shadowless/plus-white.png b/contrib/fugue-icons-3.5.6/icons-shadowless/plus-white.png new file mode 100644 index 0000000000000000000000000000000000000000..e9ba4b96ca6ec862b12b19e524dcea94406cb178 GIT binary patch literal 648 zcmV;30(bq1P)N`F7rX^L=mM%)U*8VF*_l5J*zn5R`1YyWeP>LA%|8^1JCMK|H8nL4fmO;aFVB}UBCP!7{^~SGKBM2Z)BH+Vy-E81>iYp64 z%5cAcYw$de9&LvKW8`oTHK;`oPta<$muAOxV|YO1W<;~uWQf-+3DwLz)D86TBXqm= z-0b846T|~!%CmTH?i;o%)h8} z=F4ghGJPWR@Cqa;2@ql>=TZE>?=H~`rio)6OF+o=di{4Ne>3So<>M3=KYsp$&0B9r zIcHg?a9~CrUZbYqEzVIV|j zh}Vdt6BTx9vV|U=KosSD$0N(DA3W#yRAoX*BJg!B!+u!kI(FF8$_Z@QnZ;vTg%WevVgLM@D6nkyX5V+< i+eRizaFy{_fB^t{+6TRHzVy)m0000a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="

",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) +},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("