PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/user/blacklistlib.php

https://gitlab.com/ElvisAns/tiki
PHP | 360 lines | 283 code | 22 blank | 55 comment | 8 complexity | a600c9b3bcdc51ed80c1ef1d91921589 MD5 | raw file
  1. <?php
  2. // (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
  3. //
  4. // All Rights Reserved. See copyright.txt for details and a complete list of authors.
  5. // Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
  6. // $Id$
  7. /**
  8. *
  9. * A Group of functions related to Password Blacklist & Password Index Handling
  10. *
  11. * Class blacklist
  12. */
  13. class blacklistLib extends TikiLib
  14. {
  15. /**
  16. * @var int the number of passwords to generate (limit) or actual number, after the fact.
  17. */
  18. public $limit;
  19. /**
  20. * @var int the actual number of passwords generated.
  21. */
  22. public $actual;
  23. /**
  24. * Set default values
  25. *
  26. * blacklist constructor.
  27. */
  28. public function __construct()
  29. {
  30. $this->limit = 1000; // the number of passwords to generate (limit)
  31. }
  32. /**
  33. * removes the password index databse, if it exists.
  34. */
  35. public function deletePassIndex()
  36. {
  37. $query = 'DROP TABLE IF EXISTS tiki_password_index;';
  38. $this->query($query, []);
  39. }
  40. /**
  41. * Creates the word index database table.
  42. */
  43. public function createPassIndex()
  44. {
  45. $query = 'CREATE TABLE `tiki_password_index` (
  46. `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  47. `password` VARCHAR(30) NOT NULL , UNIQUE (`password`) ,
  48. `length` TINYINT(30) NULL DEFAULT NULL ,
  49. `numchar` BOOLEAN NULL DEFAULT NULL ,
  50. `special` BOOLEAN NULL , PRIMARY KEY (`id`), UNIQUE (`password`)) ENGINE = InnoDB;';
  51. $this->query($query, []);
  52. }
  53. /**
  54. *
  55. * Given a filename, it will load the pass index database with its contents.
  56. * Files should be word lists separated by new lines.
  57. *
  58. * @param $filename string
  59. * @param $load bool Specifies if LOAD DATA INFILE is used. One needs to
  60. * be running mysql locally and have permission to use it
  61. * however it can handle much larger sets of data.
  62. */
  63. public function loadPassIndex($filename, $load = false)
  64. {
  65. if ($load) {
  66. $query = "LOAD DATA INFILE '" . $filename . "' IGNORE INTO TABLE `tiki_password_index` LINES TERMINATED BY '\n' (`password`);";
  67. $this->query($query, []);
  68. } else {
  69. $passwords = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  70. $passwords = array_map('strtolower', $passwords);
  71. $passwords = array_map('trim', $passwords);
  72. $passwords = array_unique($passwords);
  73. $passwords = array_map('addslashes', $passwords);
  74. $passwords = "('" . implode("'),('", $passwords) . "')";
  75. $query = "INSERT INTO tiki_password_index (password) VALUES $passwords";
  76. $this->query($query);
  77. }
  78. unlink($filename); // delete used temp file.
  79. $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 \'[!@#$%^&*()=+?><\\,.`;:{}~\\\'/"]\'';
  80. // 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!
  81. $this->query($query, []);
  82. }
  83. /**
  84. * Find the number of indexed passwords currently stored
  85. *@$result TikiDb_Pdo_Result
  86. *
  87. * @return int
  88. */
  89. public function passIndexNum()
  90. {
  91. // first check if table exists, and return 0 if it does not.
  92. $query = 'SELECT 1 FROM information_schema.COLUMNS
  93. WHERE table_name = \'tiki_password_index\'
  94. LIMIT 1;';
  95. $result = $this->query($query, []);
  96. $tableExists = $result->fetchRow();
  97. if ($tableExists[1] != 1) {
  98. return 0;
  99. }
  100. // if table does exits, find number of results and return.
  101. $query = 'SELECT MAX(id) FROM tiki_password_index';
  102. $result = $this->query($query, []);
  103. $num_rows = $result->fetchRow();
  104. return $num_rows['MAX(id)'];
  105. }
  106. /**
  107. *
  108. * Generates a formatted list of passwords, with new lines separating each password
  109. *
  110. * @param $toDisk bool if the file is written to disk or to screen.
  111. *
  112. * @return bool true on success and false on failure.
  113. */
  114. public function generatePassList($toDisk)
  115. {
  116. global $prefs;
  117. $query = 'SELECT password FROM tiki_password_index WHERE length >= ?';
  118. if ($prefs['pass_chr_special']) {
  119. $query .= ' && special';
  120. }
  121. if ($prefs['pass_chr_num']) {
  122. $query .= ' && numchar';
  123. }
  124. $query .= ' ORDER BY id ASC LIMIT ' . $this->limit;
  125. $result = $this->query($query, [$prefs['min_pass_length']]);
  126. $this->actual = $result->NumRows();
  127. if ($this->actual == 0) {
  128. Feedback::error(tr('There is no passwords that fit your criteria. You will need a more extensive word list to generate a password list.'));
  129. } elseif ($this->actual < $this->limit) {
  130. Feedback::warning("There wasn't enough words to meet your password limit. There was $this->actual passwords blacklisted.");
  131. }
  132. if ($toDisk) {
  133. $filename = $this->generateBlacklistName();
  134. if (! is_dir(dirname($filename))) {
  135. if (! mkdir(dirname($filename))) { // if the directory isnt there create it.
  136. Feedback::error(tr('Could not create /storage/pass_blacklists directory.'));
  137. return false;
  138. }
  139. }
  140. if (file_exists($filename)) {
  141. if (unlink($filename)) { // if the file already exists, then delete.
  142. Feedback::warning(tr('Existing password blacklist file was overwritten.'));
  143. } else {
  144. Feedback::error(tr('Existing password blacklist file could not be overwritten.'));
  145. return false;
  146. }
  147. }
  148. $pointer = @fopen($filename, 'x');
  149. if (! $pointer) {
  150. Feedback::error(tr('File Error. Password file not created.'));
  151. return false;
  152. };
  153. while ($foo = $result->fetchrow()) {
  154. if (! fwrite($pointer, $foo['password'] . PHP_EOL)) {
  155. Feedback::error(tr('File Error. Password file generation interrupted.'));
  156. return false;
  157. }
  158. }
  159. fclose($pointer);
  160. } else {
  161. while ($foo = $result->fetchrow()) {
  162. echo $foo['password'] . PHP_EOL;
  163. }
  164. }
  165. Feedback::success(tr('Password blacklist file generated.'));
  166. return true;
  167. }
  168. /**
  169. * Generates the name for a password file
  170. *
  171. * @param bool $asFile should the directory be returned as a file name with directory, if false only the name without extension
  172. *
  173. * @return string
  174. *
  175. */
  176. public function generateBlacklistName($asFile = true)
  177. {
  178. global $prefs;
  179. $filename = '';
  180. if ($asFile) {
  181. $filename = 'storage/pass_blacklists/'; // directory
  182. }
  183. $filename .= $prefs['pass_chr_num'];
  184. $filename .= '-' . $prefs['pass_chr_special'];
  185. $filename .= '-' . $prefs['min_pass_length'];
  186. $filename .= '-1-'; // indicates user created file
  187. $filename .= $this->actual;
  188. if (! $asFile) {
  189. return $filename;
  190. }
  191. $filename .= '.txt';
  192. return $filename;
  193. }
  194. public function whatFileUsing()
  195. {
  196. if ($GLOBALS['prefs']['pass_blacklist'] == 'n' || ! isset($GLOBALS['prefs']['pass_blacklist'])) {
  197. return 'Disabled';
  198. } elseif ($GLOBALS['prefs']['pass_blacklist_file'] == 'auto') {
  199. return $this->readableBlackName(explode('-', $GLOBALS['prefs']['pass_auto_blacklist'])) . ' - Auto Selected';
  200. } else {
  201. return $this->readableBlackName(explode('-', $GLOBALS['prefs']['pass_blacklist_file']));
  202. }
  203. }
  204. /**
  205. * Formatts a blacklist file name into a human readable description.
  206. *
  207. * @param $NameArray array of blacklist file specifycations
  208. *
  209. * @return string
  210. *
  211. */
  212. private function readableBlackName($NameArray)
  213. {
  214. $readable = 'Num & Let: ' . $NameArray['0'];
  215. $readable .= ', Special: ' . $NameArray['1'];
  216. $readable .= ', Min Len: ' . $NameArray['2'];
  217. $readable .= ', Custom: ' . $NameArray['3'];
  218. $readable .= ', Word Count: ' . $NameArray['4'];
  219. return $readable;
  220. }
  221. /**
  222. *
  223. * populates the password blacklist database table with the contents of a file of passwords.
  224. *
  225. * @param $filename string the name & path of the saved password
  226. */
  227. public function loadBlacklist($filename)
  228. {
  229. if (is_readable($filename)) {
  230. $passwords = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  231. $passwords = array_map('strtolower', $passwords);
  232. $passwords = array_map('trim', $passwords);
  233. $passwords = array_unique($passwords);
  234. $passwords = array_map('addslashes', $passwords);
  235. $passwords = "('" . implode("'),('", $passwords) . "')";
  236. $tikiDb = new TikiDb_Bridge();
  237. $query = "TRUNCATE TABLE tiki_password_blacklist";
  238. $tikiDb->query($query, []);
  239. $query = "INSERT INTO tiki_password_blacklist (password) VALUES $passwords";
  240. $tikiDb->query($query);
  241. } else {
  242. Feedback::error(tr('Unable to Populate Blacklist: File "%0" does not exist or is not readable.', $filename));
  243. }
  244. }
  245. /**
  246. * Obtains blacklists available, and returns one according to which one is best suited to current settings.
  247. * This function may only be called when values being updated, as it relies on the $_POST vars differing from
  248. * saved settings
  249. *
  250. * @var $file[0] bool chracter & number
  251. * @var $file[1] bool special character
  252. * @var $file[2] int minimum number of characters
  253. * @var $file[3] bool is user generated
  254. * @var $file[4] int number of passwords (limit)
  255. *
  256. * @param $pass_chr_num string the post var being updated
  257. * @param $pass_chr_num string the post var beign updated
  258. * @param $length string length value being updated
  259. *
  260. *
  261. * @return array|bool the file name (without extension) that is best suited to govern the blacklist, or false on no suitable files.
  262. */
  263. public function selectBestBlacklist($pass_chr_num, $pass_chr_special, $length)
  264. {
  265. $fileIndex = $this->genIndexedBlacks(false);
  266. $bestFile = false;
  267. $chrnum = false;
  268. $special = false;
  269. if ($pass_chr_num == 'on') {
  270. $chrnum = true;
  271. }
  272. if ($pass_chr_special == 'on') {
  273. $special = true;
  274. }
  275. foreach ($fileIndex as $file) {
  276. if (
  277. $file[0] == $chrnum && // first qualify the options
  278. $file[1] == $special &&
  279. $file[2] <= $length
  280. ) {
  281. $count = 2;
  282. while ($count < 5) { // then pick the best option
  283. if ($file[$count] >= $bestFile[$count]) {
  284. if ($file[$count] > $bestFile[$count]) {
  285. $bestFile = $file;
  286. }
  287. $count++;
  288. } else {
  289. $count = 5;
  290. }
  291. }
  292. }
  293. }
  294. return $bestFile;
  295. }
  296. /**
  297. * reads available password list files from disk and returns a sorted array of files
  298. *
  299. * @param $returnFormatted bool if false, will return a human readable array, if false, will return the same array with only numbers.
  300. *
  301. * @return array
  302. */
  303. public function genIndexedBlacks($returnFormatted = true)
  304. {
  305. $blacklist_options = array_diff(scandir(__DIR__ . '/../pass_blacklists'), ['..', '.', 'index.php', '.htaccess', '.svn', '.DS_Store', 'readme.txt']);
  306. if (is_dir('storage/pass_blacklists')) {
  307. $blacklist_options = array_merge($blacklist_options, array_diff(scandir(__DIR__ . '/../../storage/pass_blacklists'), ['..', '.', 'index.php', '.htaccess', '.svn', '.DS_Store', 'readme.txt']));
  308. }
  309. sort($blacklist_options);
  310. $fileindex = [];
  311. foreach ($blacklist_options as $blacklist_file) {
  312. $blacklist_file = substr($blacklist_file, 0, -4);
  313. $fileindex[$blacklist_file] = explode('-', $blacklist_file);
  314. if ($returnFormatted) {
  315. $fileindex[$blacklist_file] = $this->readableBlackName($fileindex[$blacklist_file]);
  316. }
  317. }
  318. return $fileindex;
  319. }
  320. }