<?php
/**
 *  DB.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('DB.php');
require_once('Secioss/AutoLogin/Container.php');

/**
 *  AutoLogin_Container_DB
 *
 *  @package    Auth
 *  @author     Kaoru Sekiguchi <sekiguchi.kaoru@secioss.co.jp>
 *  @copyright  2007 SECIOSS CORPORATION
 *  @version    CVS: $Id$
 */
class AutoLogin_Container_DB extends AutoLogin_Container
{
    // {{{ AutoLogin_Container_DB
    /**
     *  AutoLogin_Container_DBクラスのコンストラクタ
     *
     *  @access public
     *  @param  mixed   $options        DBの設定
     *  @return mixed   0:正常終了 PEAR_Error:エラー
     */
    function AutoLogin_Container_DB($options)
    {
        parent::AutoLogin_Container($options);
        if (!$this->options['logincol']) {
            $this->options['logincol'] = $this->options['usercol'];
        }

        $arr_dsn = explode(' ', $this->options['dsn']);
        foreach ($arr_dsn as $this->options['dsn']) {
            $rc = $this->_connect();
            if (!$rc) {
                break;
            }
        }
        register_shutdown_function(array(&$this, '_disconnect'));

        return $rc;
    }
    // }}}

    // {{{ _connect()
    /**
     * DBサーバに接続する
     *
     * @access private
     * @return mixed   0:正常終了 PEAR_Error:エラー
     */
    function _connect()
    {
        $this->conn = & DB::connect($this->options['dsn'], true);
        if (PEAR::isError($this->conn)) {
            $message = $this->conn->getMessage();
            $this->conn = null;
            return PEAR::raiseError($message, AUTO_LOGIN_ERROR);
        }

        return 0;
    }
    // }}}

    // {{{ _disconnect()
    /**
     * DBサーバへの接続を切断する
     *
     * @access private
     */
    function _disconnect()
    {
        if ($this->conn) {
            $this->conn->disconnect();
        }
    }

    // {{{ fetchData()
    /**
     * DBサーバからユーザ情報を取得する
     *
     * @param  string username
     * @return mixed
     */
    function fetchData($username)
    {
        if (!$username || strlen($username) > 255) {
            return PEAR::raiseError("Invalid user id", AUTO_LOGIN_INVALID_VALUE);
        }

        if (!$this->conn) {
            return PEAR::raiseError("Can't contact DB server", AUTO_LOGIN_ERROR);
        }

        if (preg_match('/^pgsql:/', $this->options['dsn'])) {
            $username = pg_escape_string($username);
        } else {
            $username = mysql_escape_string($username);
        }
        $sql = "select * from ".$this->options['table']." where ".$this->options['usercol']."='$username'";
        $res =& $this->conn->query($sql);
        if (PEAR::isError($res)) {
            return PEAR::raiseError($res->getMessage()."($sql)", AUTO_LOGIN_ERROR);
        }

        $num = $res->numRows();

        if ($num == 0) {
            return PEAR::raiseError("User doesn't exist", AUTO_LOGIN_NO_USER);
        } else if ($num != 1) {
            return PEAR::raiseError("User isn't unique", AUTO_LOGIN_ERROR);
        }

        $row = $res->fetchRow(DB_FETCHMODE_ASSOC);

        $this->id = $row[$this->options['idcol']];

        $this->prop = array();
        foreach ($row as $key => $value) {
            $this->prop[$key] = $value;
        }

        if (isset($this->prop[$this->options['logincol']])) {
            $this->loginid = $this->prop[$this->options['logincol']];
        }
    }
    // }}}

    // {{{ auth()
    /**
     *  ユーザの認証を行う
     *
     *  @access public
     *  @param  string   パスワード
     *  @return string   0:成功 1:失敗
     */
    function auth($password)
    {
        if (!$password || !isset($this->prop[$this->options['pwdcol']])) {
            return 1;
        }

        $userpasswd = $this->prop[$this->options['pwdcol']];

        return $this->hashPasswd($password) == $userpasswd ? 0 : 1;
    }
    // }}}

    // {{{ getPassword()
    /**
     *  暗号化されているパスワードを復号化して取得する
     *
     *  @access public
     *  @return string   パスワード
     */
    function getPassword()
    {
        if (isset($this->prop[$this->options['encryptpwdcol']])) {
            return $this->decrypt($this->prop[$this->options['encryptpwdcol']]);
        } else {
            return '';
        }
    }
    // }}}

    function setProp($prop)
    {
        if (!$this->id) {
            return PEAR::raiseError("Must fetch data", AUTO_LOGIN_ERROR);
        }

        $setval = '';
        foreach ($prop as $key => $value) {
            $value = is_array($value) ? $value[0] : $value;
            if (preg_match('/^pgsql:/', $this->options['dsn'])) {
                $value = pg_escape_string($value);
            } else {
                $value = mysql_escape_string($value);
            }
            $setval .= ($setval ? ',' : '')." $key = '$value'";
        }
        $sql = "update ".$this->options['table']." set $setval where ".$this->options['idcol']." = '".$this->id."'";
        $res =& $this->conn->query($sql);
        if (PEAR::isError($res)) {
            return PEAR::raiseError($res->getMessage()."($sql)", AUTO_LOGIN_ERROR);
        }

        return true;
    }

    // {{{ setPassword()
    /**
     *  パスワードを暗号化してDBに格納する
     *
     *  @access public
     *  @param  string   パスワード
     *  @return mixed    true:正常終了 PEAR_Error:エラー
     */
    function setPassword($password = null, $app = null)
    {
        if (!$this->id) {
            return PEAR::raiseError("Must fetch data", AUTO_LOGIN_ERROR);
        }

        if (!$password || strlen($password) > 255) {
            return PEAR::raiseError("Invalid password", AUTO_LOGIN_INVALID_VALUE);
        }

        $setval = $this->options['pwdcol']." = '".$this->hashPasswd($password)."'";
        if ($this->options['encryptpwdcol']) {
            $setval = $setval.", ".$this->options['encryptpwdcol']." = '".$encrypt."'";
        }
        if ($this->options['pwdtimecol']) {
            $setval = "$setval, ".$this->options['pwdtimecol']." = '".date("YmdHis")."'";
        }
        if ($this->options['encryptpwdcol']) {
            $encrypt = $this->encrypt($password);
            if (PEAR::isError($encrypt)) {
                return $encrypt;
            }
            $setval = $setval.", ".$this->options['encryptpwdcol']." = '".$encrypt."'";
        }

        $sql = "update ".$this->options['table']." set $setval where ".$this->options['idcol']." = '".$this->id."'";
        $res =& $this->conn->query($sql);
        if (PEAR::isError($res)) {
            return PEAR::raiseError($res->getMessage()."($sql)", AUTO_LOGIN_ERROR);
        }

        return true;
    }
    // }}}

    // {{{ _setDefaults()
    /**
     * optionsにデフォルト値を設定する
     *
     * @access private
     */
    function _setDefaults()
    {
        parent::_setDefaults();

        $this->options['dsn']          = '';
        $this->options['table']        = '';
        $this->options['idcol']        = '';
        $this->options['usercol']      = '';
        $this->options['pwdcol']       = '';
        $this->options['logincol']     = '';
        $this->options['encryptpwdcol']  = '';
        $this->options['pwdtimecol'] = '';
        $this->options['secretcol'] = 'initsecret';
        $this->options['pincol'] = '';
    }
    // }}}

    // {{{ setSecret()
    /**
     *  シークレットを暗号化してDBに格納する
     *
     *  @access public
     *  @param  string   シークレット
     *  @return mixed    true:正常終了 PEAR_Error:エラー
     */
    function setSecret($secret, $pin = null, $deviceid = null, $device = null, $otplen = null, $timewindow = null)
    {
        if (!$secret || strlen($secret) > 255) {
            return PEAR::raiseError("Invalid secret", AUTO_LOGIN_INVALID_VALUE);
        }

        $encrypt = $this->encrypt($secret);
        if (PEAR::isError($encrypt)) {
            return $encrypt;
        }

        $prop = array($this->options['secretcol'] => $encrypt);
        if ($this->options['pincol'] && $pin) {
            $encrypt = $this->encrypt($secret);
            if (PEAR::isError($encrypt)) {
                return $encrypt;
            }
            $prop[$this->options['pincol']] = $encrypt;
        }
        if ($otplen && $otplen > 0) {
            $prop['otp_length'] = $otplen;
        }
        if ($timewindow && $timewindow > 0) {
            $prop['otp_timewindow'] = $timewindow;
        }
        return $this->setProp($prop);
    }
    // }}}

    function add($username, $prop)
    {
        $cols = array();
        $vals = array();
        foreach ($prop as $key => $value) {
            $value = is_array($value) ? $value[0] : $value;
            if (preg_match('/^pgsql:/', $this->options['dsn'])) {
                $value = pg_escape_string($value);
            } else {
                $value = mysql_escape_string($value);
            }
            $cols[] = $key;
            $vals[] = "'$value'";
        }
        $sql = "insert into ".$this->options['table']."(".$this->options['usercol'].", ".join(', ', $cols).") values('$username', ".join(', ', $vals).")";
        $res =& $this->conn->query($sql);
        if (PEAR::isError($res)) {
            return PEAR::raiseError($res->getMessage()."($sql)", AUTO_LOGIN_ERROR);
        }

        return true;
    }
}

?>
