<?php
/**
 *  AutoLogin_Container.php
 *
 *  PHP versions 4 and 5
 *
 *  @package    Auth
 *  @author     Kaoru Sekiguchi <sekiguchi.kaoru@secioss.co.jp>
 *  @copyright  2007 SECIOSS CORPORATION
 *  @version    CVS: $Id$
 */
require_once('PEAR.php');

define('AUTO_LOGIN_ERROR', -1);
define('AUTO_LOGIN_NO_USER', -2);
define('AUTO_LOGIN_NO_PASSWD', -3);
define('AUTO_LOGIN_INVALID_VALUE', -4);
define('AUTO_LOGIN_MIN_AGE', -5);
define('AUTO_LOGIN_IN_HISTORY', -6);
define('AUTO_LOGIN_BAD_PASSWD', -7);
define('AUTO_LOGIN_WEAK_PASSWD', -8);
define('AUTO_LOGIN_WEAK_PASSWD_WARN', -9);
define('AUTO_LOGIN_MIN_PASSWD', -10);
define('AUTO_LOGIN_MAX_PASSWD', -11);
define('AUTO_LOGIN_ALREADY_EXISTS', -12);

define('SECRETKEY_DIRECTIVE', 'TKTAuthSecret');

/**
 *  AutoLogin_Container
 *
 *  @package    Auth
 *  @author     Kaoru Sekiguchi <sekiguchi.kaoru@secioss.co.jp>
 *  @copyright  2007 SECIOSS CORPORATION
 *  @version    CVS: $Id$
 */
class AutoLogin_Container
{
    /**
     * ストレージの設定
     * @var array
     */
    var $options = array();

    /**
     * ストレージに対する接続
     * @var mixed
     */
    var $conn;

    /**
     * ユーザのID
     * @var string
     */
    var $id;

    /**
     * ユーザのログインID
     * @var string
     */
    var $loginid;

    /**
     * ユーザのステータス
     * @var string
     */
    var $status;

    /**
     * ユーザのプロパティ
     * @var array
     */
    var $prop;

    var $pwdlockout;

    /**
     * エラーメッセージ
     * @var array
     */
    var $error;

    // {{{ AutoLogin_Container
    /**
     *  AutoLogin_Containerクラスのコンストラクタ
     *
     *  @access public
     *  @param  mixed   $options        ストレージの設定
     *  @return mixed   0:正常終了 PEAR_Error:エラー
     */
    function AutoLogin_Container($options)
    {
        $this->_setDefaults();
        $this->_parseOptions($options);
        if ($this->options['libs']) {
            $libs = split(" ", $this->options['libs']);
            foreach (@libs as $lib) {
                require_once($lib);
            }
        }
    }
    // }}}

    // {{{ auth()
    /**
     *  ユーザの認証を行う
     *
     *  @access public
     *  @param  string   パスワード
     *  @return string   0:成功 1:失敗
     */
    function auth($password)
    {
        return 1;
    }
    // }}}

    // {{{ get
    /**
     *  プロパティへのアクセサ(R)
     *
     *  @access public
     *  @param  string  $key    プロパティ名
     *  @return mixed   プロパティ
     */
    function get($key)
    {
        if (isset($this->prop[$key])) {
            return $this->prop[$key];
        }
    }
    // }}}

    function getProp($keys = null)
    {
        $prop = array();
        if ($keys) {
            foreach ($keys as $key) {
                if (isset($this->prop[$key])) {
                    $prop[$key] = $this->prop[$key];
                }
            }
        } else {
            $prop = $this->prop;
        }
        return $prop;
    }

    // {{{ getLoginId
    /**
     *  ログインIDを取得する。
     *
     *  @access public
     *  @return string   ログインID
     */
    function getLoginId()
    {
        return $this->loginid;
    }
    // }}}

    // {{{ getStatus
    /**
     *  ステータスを取得する。
     *
     *  @access public
     *  @return string   ステータス
     */
    function getStatus()
    {
        return $this->status;
    }
    // }}}

    // {{{ getPassword()
    /**
     *  暗号化されているパスワードを復号化して取得する
     *
     *  @access public
     *  @return string   パスワード
     */
    function getPassword($app = null)
    {
    }
    // }}}

    function getTenants($tenant = null)
    {
    }

    // {{{ set
    /**
     *  プロパティへのアクセサ(W)
     *
     *  @access public
     *  @param  string  $key    プロパティ名
     *  @param  string  $value  値
     *  @return mixed   プロパティ
     */
    function set($key, $value)
    {
        return $this->setProp(array($key => $value));
    }
    // }}}

    function setProp($prop)
    {
    }

    // {{{ setPassword()
    /**
     *  パスワードを暗号化してストレージに格納する
     *
     *  @access public
     *  @param  string   パスワード
     *  @param  string   アプリケーション
     *  @return mixed    true:正常終了 PEAR_Error:エラー
     */
    function setPassword($password, $app = null, $init = false, $random = null)
    {
    }
    // }}}

    function deletePassword($app)
    {
    }

    // {{{ getPwdChangedTime()
    /**
     *  パスワード変更時間を取得する
     *
     *  @access public
     *  @return string   パスワード変更時間（秒）
     */
    function getPwdChangedTime()
    {
        return 0;
    }
    // }}}

    // {{{ getPwdHistory()
    /**
     *  パスワード履歴を取得する
     *
     *  @access public
     *  @return mixed   パスワード履歴
     */
    function getPwdHistory()
    {
        return array();
    }
    // }}}

    // {{{ getPwdLockout
    /**
     *  ロックアウトを取得する。
     *
     *  @access public
     *  @return string   ロックアウト
     */
    function getPwdLockout()
    {
        return $this->pwdlockout;
    }
    // }}}

    function isPwdMustChange()
    {
        return false;
    }

    // {{{ validatePasswd()
    /**
     *  パスワードの妥当性を確認する
     *
     *  @access public
     *  @param  string   パスワード
     *  @return mixed    0:正常終了
     */
    function validatePasswd($password)
    {
        $pwlen = strlen($password);

        if ($pwlen < $this->options['pwminlen']) {
            return AUTO_LOGIN_MIN_PASSWD;
        }

        if ($pwlen > $this->options['pwmaxlen']) {
            return AUTO_LOGIN_MAX_PASSWD;
        }

        if ($this->options['pwallow']) {
            $pwallows = $this->_to_array($this->options['pwallow']);
            foreach ($pwallows as $pwallow) {
                if (!preg_match("/$pwallow/", $password)) {
                    return AUTO_LOGIN_INVALID_VALUE;
                }
            }
        }

        if ($this->options['pwdeny']) {
            $pwdenys = $this->_to_array($this->options['pwdeny']);
            foreach ($pwdenys as $pwdeny) {
                if (preg_match("/$pwdeny/", $password)) {
                    return AUTO_LOGIN_INVALID_VALUE;
                }
            }
        }

        if ($this->options['pwminage'] && !$this->isPwdMustChange()) {
            $pwdtime = $this->getPwdChangedTime();
            if ($pwdtime && time() < $pwdtime + $this->options['pwminage']) {
                return AUTO_LOGIN_MIN_AGE;
            }
        }

        if ($this->options['pwinhistory']) {
            $pwdhistory = $this->getPwdHistory();

            for($i = 0; $i < $this->options['pwinhistory'] && $i < count($pwdhistory); $i++){
                $pwhash = null;
                $oldpasswd = $pwdhistory[$i];
                if (preg_match('/^{([^}]+)}(.+)$/', $pwdhistory[$i], $matches)) {
                    $pwhash = $matches[1];
                    $oldpasswd = '{'.$pwhash.'}'.$matches[2];
                }
                if ($this->cmpPasswd($password, $oldpasswd, $pwhash)) {
                    return AUTO_LOGIN_IN_HISTORY;
                }
            }
        }

        if ($this->options['pwstrength']) {
            $level = $this->options['pwstrength'];

            $dict = crack_opendict('/usr/share/cracklib/pw_dict');
            if (!$dict) {
                return AUTO_LOGIN_ERROR;
            }

            $check = crack_check($dict, $password);
            $message = crack_getlastmessage();
            if ($message != 'strong password') {
                crack_closedict($dict);
                $this->error = $message;
                switch($level) {
                  case 1:
                    return AUTO_LOGIN_WEAK_PASSWD_WARN;
                  default:
                    return AUTO_LOGIN_WEAK_PASSWD;
                }
            }
            crack_closedict($dict);
        }

        return 0;
    }
    // }}}

    function cmpPasswd($passwd, $hashedpwd, $pwhash = null)
    {
        if (!$pwhash) {
            $pwhash = $this->options['pwhash'];
        }
 
        $tab = '';
        if ($pwhash == 'CRYPT' || $pwhash == 'MD5' || $pwhash == 'SHA') {
            $tab = '{'.$pwhash.'}';
        }

        if ($pwhash == 'CRYPT') {
            $salt = substr($hashedpwd, strlen($tab), 2);
            return ($hashedpwd == $tab.$this->hashPasswd($passwd, $pwhash, $salt));
        } else {
            return ($hashedpwd == $tab.$this->hashPasswd($passwd, $pwhash));
        }
    }

    // {{{ hashPasswd()
    /**
     * パスワードの暗号化
     *
     * パスワードを暗号化する。対応する暗号方式はCRYPT、MD5、SHA。
     * @access private
     * @param string $passwd  パスワード
     * @return string  暗号化されたパスワード
     */
    function hashPasswd($passwd, $pwhash = null, $salt = null)
    {
        if (!$pwhash) {
            $pwhash = $this->options['pwhash'];
        }

        if (!$pwhash) {
            return $passwd;
        }

        $opts = split(':', $pwhash);
        $htype = $opts[0];
        $otype = isset($opts[1]) ? $opts[1] : '';
        $num = isset($opts[2]) ? $opts[2] : 1;

        for ($i = 0; $i < $num; $i++) {
            switch($htype) {
              case 'CRYPT':
                $token = array_merge(range('0', '9'), range('a', 'z'), range('A', 'Z'));
                if (!$salt) {
                    $salt = $token[rand(0, count($token) - 1)].$token[rand(0, count($token) - 1)];
                }
                $passwd = crypt($passwd, $salt);
                break;
              case 'MD5':
                $passwd = md5($passwd);
                $passwd = $otype == 'hex' ? $passwd : base64_encode(pack('H*', $passwd));
                break;
              case 'SHA':
                $passwd = sha1($passwd);
                $passwd = $otype == 'hex' ? $passwd : base64_encode(pack('H*', $passwd));
                break;
              case 'AD':
                $passwd = "\"".$passwd."\"";
                $adpasswd = '';
                for ($j = 0; $j < strlen($passwd); $j++) {
                    $adpasswd .= "{$passwd{$j}}\000";
                }
                return $adpasswd;
                break; 
              case 'CLEARTEXT':
                return $passwd;
                break;
              default:
                return $passwd;
            }
        }

        return $passwd;
    }
    // }}}

    // {{{ encrypt()
    /**
     *  文字列の暗号化を行う
     *
     *  @access public
     *  @param  string   文字列
     *  @return string   暗号化された文字列
     */
    function encrypt($string)
    {
        if ($string === null || $string === '') {
            return $string;
        }

        if ($this->options['publickey']) {
            return $this->ssl_encrypt($string);
        } else {
            return $this->des_encrypt($string);
        }
    }
    // }}}

    function des_encrypt($string)
    {
        $key = $this->_getSecretKey($this->options['keyfile']);
        if (!$key) {
            return PEAR::raiseError("Can't get secret key", AUTO_LOGIN_ERROR);
        }

        srand((double) microtime() * 1000000);
        $key = md5($key);
        $string = $key.$string;

        $td = mcrypt_module_open('tripledes', '', 'cfb', '');
        $key = substr($key, 0, mcrypt_enc_get_key_size($td));
        $iv_size = mcrypt_enc_get_iv_size($td);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

        if (mcrypt_generic_init($td, $key, $iv) != -1) {
            $c_t = mcrypt_generic($td, $string);
            mcrypt_generic($td, $string);
            mcrypt_module_close($td);
            $c_t = $iv.$c_t;
            return base64_encode($c_t);
        }
    }

    function ssl_encrypt($string)
    {
        $publickey = openssl_get_publickey('file://'.$this->options['publickey']);
        if (openssl_public_encrypt($string, $encrypted, $publickey, OPENSSL_PKCS1_OAEP_PADDING)) {
            return base64_encode($encrypted);
        }
    }

    // {{{ decrypt()
    /**
     *  文字列の復号化を行う
     *
     *  @access public
     *  @return string   復号化された文字列
     */
    function decrypt($string)
    {
        if ($string === null || $string === '') {
            return $string;
        }

        if ($this->options['privatekey']) {
            return $this->ssl_decrypt($string);
        } else {
            return $this->des_decrypt($string);
        }
    }
    // }}}

    function des_decrypt($string)
    {
        $keyfiles = array($this->options['keyfile']);
        if ($this->options['oldkeyfile']) {
            array_push($keyfiles, $this->options['oldkeyfile']);
        }

        $string = base64_decode($string);
        foreach ($keyfiles as $keyfile) {
            $key = $this->_getSecretKey($keyfile);
            if (!$key) {
                return PEAR::raiseError("Can't get secret key", AUTO_LOGIN_ERROR);
            }

            $tmpstr = $string;
            $key = md5($key);
            $checksum = $key;

            $td = mcrypt_module_open('tripledes', '', 'cfb', '');
            $key = substr($key, 0, mcrypt_enc_get_key_size($td));
            $iv_size = mcrypt_enc_get_iv_size($td);
            $iv = substr($tmpstr, 0, $iv_size);
            $tmpstr = substr($tmpstr, $iv_size);

            if (mcrypt_generic_init($td, $key, $iv) != -1) {
                $c_t = mdecrypt_generic($td, $tmpstr);
                mcrypt_generic_deinit($td);
                mcrypt_module_close($td);
                if (!strncmp($c_t, $checksum, 32)) {
                    return substr($c_t, 32);
                }
            }
        }

        return PEAR::raiseError("Encrypted password is invalid", AUTO_LOGIN_ERROR);
    }

    function ssl_decrypt($string)
    {
        $keyfiles = array($this->options['privatekey']);
        if ($this->options['oldprivatekey']) {
            array_push($keyfiles, $this->options['oldprivatekey']);
        }

        $string = base64_decode($string);
        foreach ($keyfiles as $keyfile) {
            $privatekey = openssl_get_privatekey('file://'.$keyfile);
            if (openssl_private_decrypt($string, $decrypted, $privatekey, OPENSSL_PKCS1_OAEP_PADDING)) {
                return $decrypted;
            }
        }

        return PEAR::raiseError("Encrypted password is invalid", AUTO_LOGIN_ERROR);
    }

    function getSecretKey()
    {
        return $this->_getSecretKey($this->options['keyfile']);
    }

    // {{{ _to_array
    /**
     *  スカラー値を要素数1の配列として返す
     *
     *  @param  mixed   $v  配列として扱う値
     *  @return array   配列に変換された値
     */
    function _to_array($v)
    {
        if (is_array($v)) {
            return $v;
        } else if ($v) {
            return array($v);
        } else {
            return array();
        }
    }
    // }}}

    // {{{ _getSecretKey()
    /**
     *  暗号化用のキーをファイルから取得する
     *
     *  @access public
     *  @param  string   ファイル
     *  @return string   キー
     */
    function _getSecretKey($file)
    {
        $matches = array();
        $secretKey = "";

        $content = file_get_contents($file);
        if( $content === false ){
            // Cannot read key file
            return null;
        }

        if(preg_match( "/^\s*".SECRETKEY_DIRECTIVE."\s+\"(.*?)\"/mi", $content, $matches)) {
            $secretKey = $matches[1];
        }

        return $secretKey;
    }
    // }}}

    // {{{ _parseOptions()
    /**
     * optionsに値を設定する
     *
     * @access private
     * @param  array
     */
    function _parseOptions($array)
    {
        if (is_array($array)) {
           foreach ($array as $key => $value) {
               if (array_key_exists($key, $this->options)) {
		 $this->options[$key] = $value;
               }
	   }
        }
    }
    // }}}

    // {{{ _setDefaults()
    /**
     * optionsにデフォルト値を設定する
     *
     * @access private
     */
    function _setDefaults()
    {
        $this->options['pwhash']       = '';
        $this->options['pwminlen']     = 0;
        $this->options['pwmaxlen']     = 255;
        $this->options['pwallow']      = null;
        $this->options['pwdeny']       = null;
        $this->options['pwminage']     = 0;
        $this->options['pwinhistory']  = 0;
        $this->options['pwstrength']   = 0;
        $this->options['keyfile']      = '';
        $this->options['oldkeyfile']   = '';
        $this->options['privatekey'] = '';
        $this->options['oldprivatekey'] = '';
        $this->options['publickey'] = '';
        $this->options['libs']         = '';
    }
    // }}}

    // {{{ validateSecret()
    /**
     *  added by okazaki 20090710
     *  シークレットの妥当性を確認する
     *
     *  @access public
     *  @param  string   シークレット
     *  @return mixed    0:正常終了
     */
    function validateSecret($secret)
    {
        $len = strlen($secret);

        if ($len != 16) {
            return AUTO_LOGIN_INVALID_VALUE;
        }

        return 0;
    }
    // }}}

    // {{{ setSecret()
    /**
     *  added by okazaki 20090710
     *  シークレットを暗号化してストレージに格納する
     *
     *  @access public
     *  @param  string   シークレット
     *  @param  string   PIN
     *  @return mixed    true:正常終了 PEAR_Error:エラー
     */
    function setSecret($secret, $pin, $deviceid, $device)
    {
    }
    // }}}

    // {{{ getSecret()
    /**
     *  added by okazaki 20090710
     *  暗号化されているシークレットを復号化して取得する
     *
     *  @access public
     *  @return string   シークレット
     */
    function getSecret($app = null)
    {
    }
    // }}}

    function random($len)
    {
        $token = array_merge(range('0', '9'), range('a', 'z'), range('A', 'Z'));

        $str = '';
        for ($i = 0; $i < $len; $i++) {
            $str = $str.$token[rand(0, count($token) - 1)];
        }

        return $str;
    }
}

?>
