htpasswd.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php
  2. include_once ("model/meta_model.php");
  3. include_once ("hash_tool.php");
  4. /**
  5. * htpasswd tools for Apache Basic Auth.
  6. *
  7. * now uses crypt_apr1_md5
  8. */
  9. class htpasswd {
  10. var $fp;
  11. var $metafp;
  12. var $filename;
  13. var $metafilename;
  14. var $use_metadata;
  15. /* All ht-files. These files are stored within the secured folder. */
  16. const HTPASSWD_NAME = ".htpasswd";
  17. const HTACCESS_NAME = ".htaccess";
  18. const HTMETA_NAME = ".htmeta";
  19. function htpasswd($configpath, $use_metadata = false) {
  20. $path = realpath ( $configpath );
  21. $htaccessfile = $path . "/" . self::HTACCESS_NAME;
  22. $htpasswdfile = $path . "/" . self::HTPASSWD_NAME;
  23. @$this->use_metadata = $use_metadata;
  24. if (! file_exists ( $htaccessfile )) {
  25. $bdfp = fopen ( $htaccessfile, 'w' );
  26. $htaccess_content = "AuthType Basic\nAuthName \"Password Protected Area\"\nAuthUserFile \"" . $htpasswdfile . "\"\nRequire valid-user" . "\n<Files .ht*>\nOrder deny,allow\nDeny from all\n</Files>";
  27. fwrite ( $bdfp, $htaccess_content );
  28. }
  29. @$this->fp = @$this::open_or_create ( $htpasswdfile ) or die('Cannot open file for R/W: '.$htpasswdfile);
  30. if ($use_metadata) {
  31. $htmetafile = $path . "/" . self::HTMETA_NAME;
  32. @$this->metafp = @$this::open_or_create ( $htmetafile );
  33. $this->metafilename = $htmetafile;
  34. }
  35. $this->filename = $htpasswdfile;
  36. }
  37. function user_exists($username) {
  38. return self::exists ( @$this->fp, $username );
  39. }
  40. function meta_exists($username) {
  41. return self::exists ( @$this->metafp, $username );
  42. }
  43. function meta_find_user_for_mail($email) {
  44. while ( ! feof ( $this->metafp ) && $meta = explode ( ":", $line = rtrim ( fgets ( $this->metafp ) ) ) ) {
  45. if (count ( $meta ) > 1) {
  46. $username = trim ( $meta [0] );
  47. $lemail = $meta [1];
  48. if ($lemail == $email) {
  49. return $username;
  50. }
  51. }
  52. }
  53. return null;
  54. }
  55. function get_metadata() {
  56. rewind ( $this->metafp );
  57. $meta_model_map = array ();
  58. $metaarr = array ();
  59. while ( ! feof ( $this->metafp ) && $line = rtrim ( fgets ( $this->metafp ) ) ) {
  60. $metaarr = explode ( ":", $line );
  61. $model = new meta_model ();
  62. $model->user = $metaarr [0];
  63. if (count ( $metaarr ) > 1) {
  64. $model->email = $metaarr [1];
  65. }
  66. if (count ( $metaarr ) > 2) {
  67. $model->name = $metaarr [2];
  68. }
  69. if (count ( $metaarr ) > 3) {
  70. $model->mailkey = $metaarr [3];
  71. }
  72. $meta_model_map [$model->user] = $model;
  73. }
  74. return $meta_model_map;
  75. }
  76. function get_users() {
  77. rewind ( $this->fp );
  78. $users = array ();
  79. $i = 0;
  80. while ( ! feof ( $this->fp ) && trim ( $lusername = array_shift ( explode ( ":", $line = rtrim ( fgets ( $this->fp ) ) ) ) ) ) {
  81. $users [$i] = $lusername;
  82. $i ++;
  83. }
  84. return $users;
  85. }
  86. function user_add($username, $password) {
  87. if ($this->user_exists ( $username ))
  88. return false;
  89. fseek ( $this->fp, 0, SEEK_END );
  90. //fwrite ( $this->fp, $username . ':' . self::htcrypt ( $password ) . "\n" );
  91. fwrite ( $this->fp, $username . ':' . self::crypt_apr1_md5 ( $password ) . "\n" );
  92. // quick and dirty 1. solution to add crypt_apr1_md5 encrypted passwords using htpasswd program
  93. // echo "htpasswd -bm \"$this->filename\" \"$username\" \"$password\"";
  94. // exec("htpasswd -bm \"$this->filename\" \"$username\" \"$password\"");
  95. return true;
  96. }
  97. function meta_add(meta_model $meta_model) {
  98. if (self::exists ( @$this->metafp, $meta_model->user )) {
  99. return false;
  100. }
  101. fseek ( $this->metafp, 0, SEEK_END );
  102. fwrite ( $this->metafp, $meta_model->user . ':' . $meta_model->email . ':' . $meta_model->name . ':' . $meta_model->mailkey . "\n" );
  103. return true;
  104. }
  105. /**
  106. * Login check
  107. * first 2 characters of hash is the salt.
  108. *
  109. * @param user $username
  110. * @param pass $password
  111. * @return boolean true if password is correct!
  112. */
  113. function user_check($username, $password) {
  114. rewind ( $this->fp );
  115. while ( ! feof ( $this->fp ) && $userpass = explode ( ":", $line = rtrim ( fgets ( $this->fp ) ) ) ) {
  116. $lusername = trim ( $userpass [0] );
  117. $hash = trim ($userpass [1] );
  118. if ($lusername == $username) {
  119. $validator = self::create_hash_tool($hash);
  120. return $validator->check_password_hash($password, $hash);
  121. }
  122. }
  123. return false;
  124. }
  125. function user_delete($username) {
  126. return self::delete ( @$this->fp, $username, @$this->filename );
  127. }
  128. function meta_delete($username) {
  129. return self::delete ( @$this->metafp, $username, @$this->metafilename );
  130. }
  131. function user_update($username, $password) {
  132. rewind ( $this->fp );
  133. while ( ! feof ( $this->fp ) && trim ( $lusername = array_shift ( explode ( ":", $line = rtrim ( fgets ( $this->fp ) ) ) ) ) ) {
  134. if ($lusername == $username) {
  135. //fseek ( $this->fp, (- 15 - strlen ( $username )), SEEK_CUR );
  136. //fwrite ( $this->fp, $username . ':' . self::htcrypt ( $password ) . "\n" );
  137. fseek ( $this->fp, (- 39 - strlen ( $username )), SEEK_CUR );
  138. fwrite ( $this->fp, $username . ':' . self::crypt_apr1_md5 ( $password ) . "\n" );
  139. return true;
  140. }
  141. }
  142. rewind ( $this->fp );
  143. while ( ! feof ( $this->fp ) && trim ( $lusername = array_shift ( explode ( ":", $line = rtrim ( fgets ( $this->fp ) ) ) ) ) ) {
  144. if ($lusername == $username) {
  145. //exec("htpasswd -bm \"$this->filename\" \"$username\" \"$password\"");
  146. if ($use_metadata) {
  147. $meta_model = new meta_model ();
  148. $meta_model->user = $username;
  149. $meta_model->email = $_POST ['email'];
  150. $meta_model->name = $_POST ['name'];
  151. $meta_model->mailkey = random_password(16);
  152. }
  153. return true;
  154. }
  155. }
  156. // quick and dirty 1. solution to add crypt_apr1_md5 encrypted passwords using htpasswd program
  157. //echo "htpasswd -bm \"$this->filename\" \"$username\" \"$password\"";
  158. //exec("htpasswd -bm \"$this->filename\" \"$username\" \"$password\"");
  159. return false;
  160. }
  161. function meta_update(meta_model $meta_model) {
  162. $this->meta_delete ( $meta_model->user );
  163. $this->meta_add ( $meta_model );
  164. return false;
  165. }
  166. static function write_meta_line($fp, meta_model $meta_model) {
  167. fwrite ( $fp, $meta_model->user . ':' . $meta_model->email . ':' . $meta_model->name . "\n" );
  168. }
  169. static function exists($fp, $username) {
  170. rewind ( $fp );
  171. while ( ! feof ( $fp ) && trim ( $lusername = array_shift ( explode ( ":", $line = rtrim ( fgets ( $fp ) ) ) ) ) ) {
  172. if ($lusername == $username)
  173. return true;
  174. }
  175. return false;
  176. }
  177. static function open_or_create($filename) {
  178. if (! file_exists ( $filename )) {
  179. return fopen ( $filename, 'w+' );
  180. } else {
  181. return fopen ( $filename, 'r+' );
  182. }
  183. }
  184. static function delete($fp, $username, $filename) {
  185. $data = '';
  186. rewind ( $fp );
  187. while ( ! feof ( $fp ) && trim ( $lusername = array_shift ( explode ( ":", $line = rtrim ( fgets ( $fp ) ) ) ) ) ) {
  188. if (! trim ( $line ))
  189. break;
  190. if ($lusername != $username)
  191. $data .= $line . "\n";
  192. }
  193. $fp = fopen ( $filename, 'w' );
  194. fwrite ( $fp, rtrim ( $data ) . (trim ( $data ) ? "\n" : '') );
  195. fclose ( $fp );
  196. $fp = fopen ( $filename, 'r+' );
  197. return true;
  198. }
  199. static function htcrypt($password) {
  200. return crypt ( $password, substr ( str_replace ( '+', '.', base64_encode ( pack ( 'N4', mt_rand (), mt_rand (), mt_rand (), mt_rand () ) ) ), 0, 22 ) );
  201. }
  202. static function crypt_apr1_md5($plainpasswd)
  203. {
  204. // htpassword function for Apache > 2.2.18, Nginx
  205. // https://www.virendrachandak.com/techtalk/using-php-create-passwords-for-htpasswd-file/
  206. // https://stackoverflow.com/questions/2994637/how-to-edit-htpasswd-using-php/8786956#8786956
  207. $salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
  208. $len = strlen($plainpasswd);
  209. $text = $plainpasswd.'$apr1$'.$salt;
  210. $bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
  211. for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }
  212. for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $plainpasswd{0}; }
  213. $bin = pack("H32", md5($text));
  214. for($i = 0; $i < 1000; $i++)
  215. {
  216. $new = ($i & 1) ? $plainpasswd : $bin;
  217. if ($i % 3) $new .= $salt;
  218. if ($i % 7) $new .= $plainpasswd;
  219. $new .= ($i & 1) ? $bin : $plainpasswd;
  220. $bin = pack("H32", md5($new));
  221. }
  222. for ($i = 0; $i < 5; $i++)
  223. {
  224. $k = $i + 6;
  225. $j = $i + 12;
  226. if ($j == 16) $j = 5;
  227. $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
  228. }
  229. $tmp = chr(0).chr(0).$bin[11].$tmp;
  230. $tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
  231. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  232. "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
  233. return "$"."apr1"."$".$salt."$".$tmp;
  234. }
  235. static function create_hash_tool($hash) {
  236. if (strpos($hash, '$apr1') === 0) {
  237. return new md5_hash_tool();
  238. } else {
  239. return new crypt_hash_tool();
  240. }
  241. }
  242. }
  243. ?>