<?php
/**
 * htdigest plugin.
 *
 * @copyright   Copyright &copy; 2006-2007, Katsumi Saito <katsumi@jo1upk.ymt.prug.or.jp>
 * @version     $Id: htdigest.inc.php,v 0.9.1 2010/12/26 17:11:00 Logue Exp $
 *
 * $A1 = md5($data['username'] . ':' . $realm . ':' . $auth_users[$data['username']]);
 */

if (!defined('USE_APACHE_WRITE_FUNC')) {
	define('USE_APACHE_WRITE_FUNC', FALSE);
}

if (!defined('HTDIGEST_FILE_PATH')) {
	define('HTDIGEST_FILE_PATH', '');
}
if (!defined('HTDIGEST_FILE_NAME')) {
	define('HTDIGEST_FILE_NAME', '.htdigest');
}
if (!defined('HTDIGEST_FILE')) {
	define('HTDIGEST_FILE', HTDIGEST_FILE_PATH.HTDIGEST_FILE_NAME);
}
use PukiWiki\Auth\Auth;
//use PukiWiki\File\AuthFile;
use Zend\Crypt\BlockCipher;

function plugin_htdigest_init()
{
	$msg = array(
	  '_htdigest_msg' => array(
		'realm'				=> T_("realm"),
		'UserName'			=> T_("UserName"),
		'Passwd'			=> T_("Passwd"),
		'Calculate'			=> T_("Calculate"),
		'CALC'				=> T_("CALC"),
		'Update'			=> T_("Update"),
		'Result'			=> T_("Result"),
		'Crypt'				=> T_("Encryption key"),
		'msg_pass_admin'	=> T_("Please input Administrator password."),
		'msg_pass_old'		=> T_("Please input the password being used now."),
		'msg_pass_new'		=> T_("Please input a new password."),
		'msg_iis'			=> T_("It doesn't correspond to IIS of Microsoft Corporation."),
		'err_not_use'		=> T_("The writing function is limited."),
		'err_role'			=> T_("The authority more than Webmaster for World Wide Web Site is necessary to update it."),
		'err_key'			=> T_("The encryption key is not corresponding."),
		'err_md5'			=> T_("In this version, the Administrator password is supported only with {x-php-md5}."),
		'msg_realm'			=> T_("Realm is not corresponding."),
		'msg_1st'			=> T_("It newly made .htdigest."),
		'msg_not_update'	=> T_("Because the password was the same, it did not update it."),
		'msg_update'		=> T_("It updated .htdigest."),
		'msg_add'			=> T_("One was added."),
		'msg_err'			=> T_("ERROR."),
	  )
	);
        set_plugin_messages($msg);
}

function plugin_htdigest_action()
{
	global $vars, $_htdigest_msg;

	$msg = 'htdigest';
	$body = '';
	$func = (empty($vars['func'])) ? '' : $vars['func'];

	if (htdigest_is_iis()) {
		return array('msg'=>$msg,'body'=>$_htdigest_msg['msg_iis']);
	}

	// 初回起動時
	if (empty($func)) {
		return array('msg'=>$msg,'body'=>htdigest_menu());
	}

	// プラグインによる書き込み制限の場合
	if (! USE_APACHE_WRITE_FUNC) {
		return array('msg'=>$msg,'body'=>htdigest_menu($_htdigest_msg['err_not_use']));
	}

	switch ($func) {
	case 'save':
        	// サイト管理者権限が無い場合
		if (Auth::check_role('role_adm')) {
			return array('msg'=>$msg,'body'=>htdigest_menu($_htdigest_msg['err_role']));
		}
		// ADM
		if (USE_APACHE_WRITE_FUNC) {
			$rc_msg = htdigest_save($vars['username'], $vars['realm'], $vars['hash'], 2);
		}
		return array('msg'=>$msg,'body'=>htdigest_menu($rc_msg));

	case 'update':
		// サイト管理者未満は、自分のパスワードのみ更新ができる
		$role_level = Auth::get_role_level();
		if ($role_level < 2) {
			// Guest
			return array('msg'=>$msg,'body'=>htdigest_menu($_htdigest_msg['err_role']));
		}
		// Auth User
		global $realm;
		$user = Auth::check_auth();
		if (USE_APACHE_WRITE_FUNC) {
			$rc_msg = htdigest_save($user, $realm, $vars['hash'], $role_level);
		}
		return array('msg'=>$msg,'body'=>htdigest_menu($rc_msg));

	default:
		$body = $_htdigest_msg['msg_err'];
	}

	return array('msg'=>$msg,'body'=>$body);
}

function htdigest_is_iis()
{
	$srv_soft = (defined('SERVER_SOFTWARE')) ? SERVER_SOFTWARE : $_SERVER['SERVER_SOFTWARE'];
	$srv_soft = strtolower(substr($srv_soft,0,9));
	return ($srv_soft == 'microsoft') ? TRUE : FALSE;
}

function htdigest_menu($msg='&nbsp;')
{
	global $realm, $js_tags, $js_blocks, $_htdigest_msg;
	$js_tags[] = array('type'=>'text/javascript', 'src'=>JS_URI.'plugin/crypt/md4.js');
	$js_tags[] = array('type'=>'text/javascript', 'src'=>JS_URI.'plugin/crypt/md5.js');
	$js_tags[] = array('type'=>'text/javascript', 'src'=>JS_URI.'plugin/crypt/sha1.js');
	$js_tags[] = array('type'=>'text/javascript', 'src'=>JS_URI.'plugin/crypt/des.js');
	$js_tags[] = array('type'=>'text/javascript', 'src'=>JS_URI.'plugin/crypt/base64.js');

	// 使用する場合は、変更させることもコピーさせることも不要なので、抑止する
	$disabled = (USE_APACHE_WRITE_FUNC) ? 'disabled="disabled"' : '';

	$func = 'save';

	$role_level = Auth::get_role_level();
	if ($role_level > 2) {
		$user_disabled = 'disabled="disabled"';
		$user = Auth::check_auth();
		$func = 'update';
		$msg_pass = $_htdigest_msg['msg_pass_old'];
	} else {
		$user_disabled = $user = '';
		$msg_pass = ($role_level == 2) ? $_htdigest_msg['msg_pass_admin'] : '';
	}
	$script = get_script_uri();
	$j = <<< EOD
$('#set_hash').click(function(){
 var a1,ctr,pref,hash,des_key;
 var fn = function(){
   switch(objForm.algorithm.value) {
   case 'MD4':
     hash = hex_md4(a1);
     break;
   case 'SHA-1':
     hash = hex_sha1(a1);
     break;
   default:
     objForm.submit.disabled = false;
     hash = hex_md5(a1);
   }
 };

 var objForm = eval("document.htdigest");
// objForm.submit.disabled = true;
 $('form[name="htdigest"] input[type="submit"]').disabled(true);

 if (objForm.passwd.value == "" || objForm.key.value == "") {
   objForm.hash.value = "";
   objForm.algorithm.value = "";
 } else {

   ctr = objForm.scheme.length;
   for (i=0; i<ctr; i++) {
     if (objForm.scheme[i].checked) {
       objForm.algorithm.value = objForm.scheme[i].value;
       break;
     }
   }
EOD;
	if ($role_level > 2) {
		// a1
		$j .= "a1 = objForm.username.value+':'+objForm.realm.value+':'+objForm.key.value;\n";
	} else {
		// adminpass
		$j .= "a1 = objForm.key.value;\n";
	}

$j .= <<<EOD
   fn();
   des_key = hash;

   a1 = objForm.username.value+':'+objForm.realm.value+':'+objForm.passwd.value;
   fn();

   objForm.hash.value = base64encode( des(des_key, hash, 1, 0) );
   objForm.passwd.value = "";
 }

 if (objForm.hash.value == "") {
   objForm.hash_view.value = "";
 } else {
   objForm.hash_view.value = objForm.username.value+':'+objForm.realm.value+':'+hash;
 }
});
EOD;
	$js_blocks[] = $j;
	
$x = <<<EOD
<fieldset>
	<legend>htdigest</legend>
	<p>$msg</p>
	<form name="htdigest" action="$script" method="post">
		<input type="hidden" name="cmd" value="htdigest" />
		<input type="hidden" name="func" value="$func" />
		<input type="hidden" name="algorithm" />
		<input type="hidden" name="hash" />
		<table class="indented">
			<tr>
				<th>{$_htdigest_msg['realm']}</th>
				<td><input type="text" name="realm" size="30" value="$realm" /></td>
			</tr>
			<tr>
				<th>{$_htdigest_msg['UserName']}</th>
				<td><input type="text" name="username" size="10" value="$user" $user_disabled /></td>
			</tr>
			<tr>
				<th>{$_htdigest_msg['Passwd']}</th>
				<td><input type="password" name="passwd" size="10" />&nbsp;{$_htdigest_msg['msg_pass_new']}</td>
			</tr>

			<tr>
				<th>{$_htdigest_msg['Crypt']}</th>
				<td><input type="password" name="key" size="10" />&nbsp;{$msg_pass}</td>
			</tr>

			<tr>
				<th>{$_htdigest_msg['Calculate']}</th>
				<td>
					<input type="radio" name="scheme" value="MD5" checked="checked" id="md5" /><label for="md5">MD5</label>
					<input type="radio" name="scheme" value="SHA-1"  id="sha1" /><label for="sha1">SHA-1</label>
					<input type="radio" name="scheme" value="MD4" id="md4" /><label for="md4">MD4</label>
					&nbsp;
					<input type="button" id="set_hash" value="{$_htdigest_msg['CALC']}" />
				</td>
			</tr>
			<tr>
				<th>{$_htdigest_msg['Result']}</th>
				<td><input type="text" name="hash_view" size="80" $disabled /></td>
			</tr>
			<tr>
				<td colspan="2"><input type="submit" class="btn btn-default" name="submit" value="{$_htdigest_msg['Update']}" disabled="disabled" /></td>
			</tr>
		</table>
	</form>
</fieldset>
EOD;

	return $x;

}

function htdigest_get_hash($username,$p_realm='')
{
	global $realm;

	if (! file_exists(HTDIGEST_FILE)) return '';
	if (empty($p_realm)) $p_realm = $realm;

	if (!($fd = fopen(HTDIGEST_FILE,'r'))) return '';

	while ($data = @fgets($fd, 4096)) {
		$field = split(':', trim($data));
		if ($field[0] == $username && $field[1] == $p_realm) {
			fclose($fd);
			return $field[2];
		}
	}
	fclose($fd);
	return '';
}

function htdigest_save($username,$p_realm,$hash,$role)
{
	global $realm, $_htdigest_msg;

	if ($realm != $p_realm)
		return $_htdigest_msg['msg_realm'];

	// DES
	if ($role > 2) {
		$key = htdigest_get_hash($username,$p_realm);
	} else {
		// adminpass
		global $adminpass;
		list($scheme, $key) = Auth::passwd_parse($adminpass);
		// FIXME: MD5 ONLY
		if ($scheme != '{x-php-md5}') {
			return $_htdigest_msg['err_md5'];
		}
	}
	$blockCipher = BlockCipher::factory('mcrypt', array(
		'algo' => 'des',
		'mode' => 'cfb',
		'hash' => 'sha512',
		'salt' => $key,
		'padding' => 2
	));
	$decrypted_hash = $blockCipher->decrypt($hash);
//	$hash = des($key, base64_decode($hash), 0, 0, null);
	if (! preg_match('/^[a-z0-9]+$/iD', $decrypted_hash)) {
		return $_htdigest_msg['err_key'];
	}

	// SAVE
	if (file_exists(HTDIGEST_FILE)) {
		$lines = file(HTDIGEST_FILE);
	} else {
		$fp = fopen(HTDIGEST_FILE,'w');
		@flock($fp, LOCK_EX);
		fputs($fp, $username.':'.$realm.':'.$decrypted_hash."\n");
		@flock($fp, LOCK_UN);
		@fclose($fp);
		return $_htdigest_msg['msg_1st'];
	}

	$sw = FALSE;
	foreach($lines as $no=>$line) {
		$field = split(':', trim($line));
		if ($field[0] == $username && $field[1] == $p_realm) {
			if ($field[2] == $decrypted_hash) {
				return $_htdigest_msg['msg_not_update'];
			}

			$sw = TRUE;
			$lines[$no] = $field[0].':'.$field[1].':'.$decrypted_hash."\n";
			break;
		}
	}

	if (! $sw) {
		$fp = fopen(HTDIGEST_FILE,'a');
		@flock($fp, LOCK_EX);
		fputs($fp, $username.':'.$p_realm.':'.$decrypted_hash."\n");
		@flock($fp, LOCK_UN);
		@fclose($fp);
		return $_htdigest_msg['msg_add'];
	}

	$fp = fopen(HTDIGEST_FILE,'w');
	@flock($fp, LOCK_EX);
	foreach($lines as $line) {
		fwrite($fp, $line);
	}
	@flock($fp, LOCK_UN);
	@fclose($fp);
	return $_htdigest_msg['msg_update'];
}

/* End of file htdigest.inc.php */
/* Location: ./wiki-common/plugin/htdigest.inc.php */