limit = 1000; // the number of passwords to generate (limit) } /** * removes the password index databse, if it exists. */ public function deletePassIndex() { $query = 'DROP TABLE IF EXISTS tiki_password_index;'; $this->query($query, []); } /** * Creates the word index database table. */ public function createPassIndex() { $query = 'CREATE TABLE `tiki_password_index` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT , `password` VARCHAR(30) NOT NULL , UNIQUE (`password`) , `length` TINYINT(30) NULL DEFAULT NULL , `numchar` BOOLEAN NULL DEFAULT NULL , `special` BOOLEAN NULL , PRIMARY KEY (`id`), UNIQUE (`password`)) ENGINE = InnoDB;'; $this->query($query, []); } /** * * Given a filename, it will load the pass index database with its contents. * Files should be word lists separated by new lines. * * @param $filename string * @param $load bool Specifies if LOAD DATA INFILE is used. One needs to * be running mysql locally and have permission to use it * however it can handle much larger sets of data. */ public function loadPassIndex($filename, $load = false) { if ($load) { $query = "LOAD DATA INFILE '" . $filename . "' IGNORE INTO TABLE `tiki_password_index` LINES TERMINATED BY '\n' (`password`);"; $this->query($query, []); } else { $passwords = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $passwords = array_map('strtolower', $passwords); $passwords = array_map('trim', $passwords); $passwords = array_unique($passwords); $passwords = array_map('addslashes', $passwords); $passwords = "('" . implode("'),('", $passwords) . "')"; $query = "INSERT INTO tiki_password_index (password) VALUES $passwords"; $this->query($query); } unlink($filename); // delete used temp file. $query = 'UPDATE tiki_password_index SET password = LOWER(password), length = CHAR_LENGTH(password), numchar = IF(password REGEXP \'[a-z]\' && password REGEXP \'[0-9]\',1,0), special = password REGEXP \'[!@#$%^&*()=+?><\\,.`;:{}~\\\'/"]\''; // the above indexes the password list with length, if the pasword contains both a letter and number, and if it contains special charactes (except for [], casue i couldnt figure it out! $this->query($query, []); } /** * Find the number of indexed passwords currently stored *@$result TikiDb_Pdo_Result * * @return int */ public function passIndexNum() { // first check if table exists, and return 0 if it does not. $query = 'SELECT 1 FROM information_schema.COLUMNS WHERE table_name = \'tiki_password_index\' LIMIT 1;'; $result = $this->query($query, []); $tableExists = $result->fetchRow(); if ($tableExists[1] != 1) { return 0; } // if table does exits, find number of results and return. $query = 'SELECT MAX(id) FROM tiki_password_index'; $result = $this->query($query, []); $num_rows = $result->fetchRow(); return $num_rows['MAX(id)']; } /** * * Generates a formatted list of passwords, with new lines separating each password * * @param $toDisk bool if the file is written to disk or to screen. * * @return bool true on success and false on failure. */ public function generatePassList($toDisk) { global $prefs; $query = 'SELECT password FROM tiki_password_index WHERE length >= ?'; if ($prefs['pass_chr_special']) { $query .= ' && special'; } if ($prefs['pass_chr_num']) { $query .= ' && numchar'; } $query .= ' ORDER BY id ASC LIMIT ' . $this->limit; $result = $this->query($query, [$prefs['min_pass_length']]); $this->actual = $result->NumRows(); if ($this->actual == 0) { Feedback::error(tr('There is no passwords that fit your criteria. You will need a more extensive word list to generate a password list.')); } elseif ($this->actual < $this->limit) { Feedback::warning("There wasn't enough words to meet your password limit. There was $this->actual passwords blacklisted."); } if ($toDisk) { $filename = $this->generateBlacklistName(); if (! is_dir(dirname($filename))) { if (! mkdir(dirname($filename))) { // if the directory isnt there create it. Feedback::error(tr('Could not create /storage/pass_blacklists directory.')); return false; } } if (file_exists($filename)) { if (unlink($filename)) { // if the file already exists, then delete. Feedback::warning(tr('Existing password blacklist file was overwritten.')); } else { Feedback::error(tr('Existing password blacklist file could not be overwritten.')); return false; } } $pointer = @fopen($filename, 'x'); if (! $pointer) { Feedback::error(tr('File Error. Password file not created.')); return false; }; while ($foo = $result->fetchrow()) { if (! fwrite($pointer, $foo['password'] . PHP_EOL)) { Feedback::error(tr('File Error. Password file generation interrupted.')); return false; } } fclose($pointer); } else { while ($foo = $result->fetchrow()) { echo $foo['password'] . PHP_EOL; } } Feedback::success(tr('Password blacklist file generated.')); return true; } /** * Generates the name for a password file * * @param bool $asFile should the directory be returned as a file name with directory, if false only the name without extension * * @return string * */ public function generateBlacklistName($asFile = true) { global $prefs; $filename = ''; if ($asFile) { $filename = 'storage/pass_blacklists/'; // directory } $filename .= $prefs['pass_chr_num']; $filename .= '-' . $prefs['pass_chr_special']; $filename .= '-' . $prefs['min_pass_length']; $filename .= '-1-'; // indicates user created file $filename .= $this->actual; if (! $asFile) { return $filename; } $filename .= '.txt'; return $filename; } public function whatFileUsing() { if ($GLOBALS['prefs']['pass_blacklist'] == 'n' || ! isset($GLOBALS['prefs']['pass_blacklist'])) { return 'Disabled'; } elseif ($GLOBALS['prefs']['pass_blacklist_file'] == 'auto') { return $this->readableBlackName(explode('-', $GLOBALS['prefs']['pass_auto_blacklist'])) . ' - Auto Selected'; } else { return $this->readableBlackName(explode('-', $GLOBALS['prefs']['pass_blacklist_file'])); } } /** * Formatts a blacklist file name into a human readable description. * * @param $NameArray array of blacklist file specifycations * * @return string * */ private function readableBlackName($NameArray) { $readable = 'Num & Let: ' . $NameArray['0']; $readable .= ', Special: ' . $NameArray['1']; $readable .= ', Min Len: ' . $NameArray['2']; $readable .= ', Custom: ' . $NameArray['3']; $readable .= ', Word Count: ' . $NameArray['4']; return $readable; } /** * * populates the password blacklist database table with the contents of a file of passwords. * * @param $filename string the name & path of the saved password */ public function loadBlacklist($filename) { if (is_readable($filename)) { $passwords = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $passwords = array_map('strtolower', $passwords); $passwords = array_map('trim', $passwords); $passwords = array_unique($passwords); $passwords = array_map('addslashes', $passwords); $passwords = "('" . implode("'),('", $passwords) . "')"; $tikiDb = new TikiDb_Bridge(); $query = "TRUNCATE TABLE tiki_password_blacklist"; $tikiDb->query($query, []); $query = "INSERT INTO tiki_password_blacklist (password) VALUES $passwords"; $tikiDb->query($query); } else { Feedback::error(tr('Unable to Populate Blacklist: File "%0" does not exist or is not readable.', $filename)); } } /** * Obtains blacklists available, and returns one according to which one is best suited to current settings. * This function may only be called when values being updated, as it relies on the $_POST vars differing from * saved settings * * @var $file[0] bool chracter & number * @var $file[1] bool special character * @var $file[2] int minimum number of characters * @var $file[3] bool is user generated * @var $file[4] int number of passwords (limit) * * @param $pass_chr_num string the post var being updated * @param $pass_chr_num string the post var beign updated * @param $length string length value being updated * * * @return array|bool the file name (without extension) that is best suited to govern the blacklist, or false on no suitable files. */ public function selectBestBlacklist($pass_chr_num, $pass_chr_special, $length) { $fileIndex = $this->genIndexedBlacks(false); $bestFile = false; $chrnum = false; $special = false; if ($pass_chr_num == 'on') { $chrnum = true; } if ($pass_chr_special == 'on') { $special = true; } foreach ($fileIndex as $file) { if ( $file[0] == $chrnum && // first qualify the options $file[1] == $special && $file[2] <= $length ) { $count = 2; while ($count < 5) { // then pick the best option if ($file[$count] >= $bestFile[$count]) { if ($file[$count] > $bestFile[$count]) { $bestFile = $file; } $count++; } else { $count = 5; } } } } return $bestFile; } /** * reads available password list files from disk and returns a sorted array of files * * @param $returnFormatted bool if false, will return a human readable array, if false, will return the same array with only numbers. * * @return array */ public function genIndexedBlacks($returnFormatted = true) { $blacklist_options = array_diff(scandir(__DIR__ . '/../pass_blacklists'), ['..', '.', 'index.php', '.htaccess', '.svn', '.DS_Store', 'readme.txt']); if (is_dir('storage/pass_blacklists')) { $blacklist_options = array_merge($blacklist_options, array_diff(scandir(__DIR__ . '/../../storage/pass_blacklists'), ['..', '.', 'index.php', '.htaccess', '.svn', '.DS_Store', 'readme.txt'])); } sort($blacklist_options); $fileindex = []; foreach ($blacklist_options as $blacklist_file) { $blacklist_file = substr($blacklist_file, 0, -4); $fileindex[$blacklist_file] = explode('-', $blacklist_file); if ($returnFormatted) { $fileindex[$blacklist_file] = $this->readableBlackName($fileindex[$blacklist_file]); } } return $fileindex; } }