P]MR30mK'6L{b ;|)t(wI_ 偍aQj]S@țgO#1NThOø ]wJ_b=G8pBMQdJnGQ6pCffɃ>o2 ,j]%E-X4=jL3x\3 TZC~1|_#dM=4JF+ć!'R$'!9V>6q|#W b%z~ۤ"x tY;E 7ÛBI(ŋ݉ )a a!XRn|7l>J Ri"]V|"[װ+uPtS o<;9D %k BvU3l7p7 oO`sW8eg^Љ6ڃ3uwI~gO܊s6 QcjԧBoC?a%2)BȰ3L>Ұ[sDM#(NQ3UG1"EX*AܴqpeY Nuɵïqke&# a7ÝV0]ԞDz@fdռ8Jc Ix_dc'eXBn?CPiЬݽ l\Z&m{th5NW`d{Sή &wc0' UC<3$|AX(MUvsRn9Q<{Ha 7[;Uu2)!P/AYƩO)Kw6SȻ􁶡dٛ^iR81|O (.<:guzJýjL.p6*{M낣V:N"7>&9t8hY"s@oeYwN_**?yA2|%Wg{D%dOܐ?bt8[X XS 6.+b&3*/^,^$38]6xoaMM9{θY 0dR22TJ =ʆUhT[ow)v1E%2зgu@pɪbs_w- O3|;oUZ)s`. &ϔQrVLx6=3SfCl=Ԑ$3[3B3|ӛc^x7ˀ*ñ: Hމk<㧞1\ `Uu@y!Jɼn6H_d>n$rŅE*پV75Q٣YY+ξOa e,j{'K1T>P "o|FTSw[|xmjE%4FçGG>2DQ*~ T H"\*L'#ǭpZw}R]ej@E#iHC-&N;gRJw + *BXוd^[f uנk*bI_ E"Q:JD|d/Tk|BRKaLHI]~ةJP 8ˀ4H7|+ǃ'c<EКRܳ;ݒ۲ݩDZAFE)"dM{x#pNβJ΅|rW8n֤*ʟ+U?۫4b#$k9W~Jc 鍴#UZDȢ gn 7 RZK(YW7tyP ܟcH0G>e-Daijf[%d:鸥?oG;ſ}H5O']+![:wƂYHj6EM1Jt_Y=pX*44bPW'{,f74Be>ER%-  L)Fʿ#zO."ye.±G p+@lȩXP@OV;ձrܩAe #@` u H8 k}-ȼjޗE1H?kWQ35u';+ ۸q&^?P'1;t]+I!^9ފGrTV}.9JS[ R[·3^:rV#=6xѷG9𘶲DmB1Yy3[-[V4<&OkQ)*E"j3'9<(!]LTIP,K[_feρQroJMm"nN=.(՞,%:Qn+9*R)^hU UV?c*T,|LѴ~^܉RT#:Ì2UIQ)PB<ܜ"$jiAW7tyP ܟ8 G~w"x& ޚjO@9UBz e_eX狟]k '֞yK $X|w_]x`;oλyH@p*tG!Wr!79^lgoJp1h0A׭-5$D0 ߿F(7uƫ@^a)ߠW38Z:4#2;ta>tՎGÍ[ߋ̱]I^A*J?&T9!\GBaZC)AyMkr# ڴ`J(f-Xӹiz>t=IѢi-Fee,^ T#Fce˔-8*N];S1—2\J-%؆.'M1z1m?Aq^KYXDN\ֳ Ԧ8!uJ ]9{f;lЧGƫήґZToC%+njp(8:E4~+ՊEKiOLaJ',I)`Mc'7q17'Jȓ%qL9T*}*`/&nEJceyP6'lė}8blyGy*S*nc+F߱ 9"F:4,p .H~іTR?#Qp{'S,M:(h="e jᴰ9i\k*!~A.ws%^e)SL2dĦr;3jI(O()) { // Log the fact that we cannot check because of configuration error. rcube::raise_error("Need curl or allow_url_fopen to check password strength with 'pwned'", true, true); } else { list($prefix, $suffix) = $this->hash_split($passwd); $suffixes = $this->retrieve_suffixes(self::API_URL . $prefix); if ($suffixes) { $result = $this->check_suffix_in_list($suffix, $suffixes); } } return $result; } function hash_split($passwd) { $hash = strtolower(sha1($passwd)); $prefix = substr($hash, 0, 5); $suffix = substr($hash, 5); return [$prefix, $suffix]; } function can_retrieve() { return $this->can_curl() || $this->can_fopen(); } function can_curl() { return function_exists('curl_init'); } function can_fopen() { return ini_get('allow_url_fopen'); } function retrieve_suffixes($url) { if ($this->can_curl()) { return $this->retrieve_curl($url); } else { return $this->retrieve_fopen($url); } } function retrieve_curl($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (self::ENHANCED_PRIVACY_CURL == 1) { curl_setopt($ch, CURLOPT_HTTPHEADER, ['Add-Padding: true']); } $output = curl_exec($ch); curl_close($ch); return $output; } function retrieve_fopen($url) { $output = ''; $ch = fopen($url, 'r'); while (!feof($ch)) { $output .= fgets($ch); } fclose($ch); return $output; } function check_suffix_in_list($candidate, $list) { // initialize to error in case there are no lines at all $result = self::SCORE_ERROR; foreach (preg_split('/[\r\n]+/', $list) as $line) { $line = strtolower($line); if (preg_match('/^([0-9a-f]{35}):(\d+)$/', $line, $matches)) { if ($matches[2] > 0 && $matches[1] === $candidate) { // more than 0 occurrences, and suffix matches // -> password is compromised return self::SCORE_LISTED; } // valid line, not matching the current password $result = self::SCORE_NOT_LISTED; } else { // invalid line return self::SCORE_ERROR; } } return $result; } }