zabbix前端代码浅析之index.php

 

      离开了熟悉的工作环境,来到了新东家,开始做一些开发工作。这不,现在有需求要求我针对zabbix的前端进行二次开发。前端你们懂得,果断是php呀。so开始学习php,走起!

 

1.整体结构

首先看下zabbix的前端代码结构:

 

image

 

api目录下为api相关的文件,conf下为zabbix的相关配置文件,如数据库的配置文件(zabbix.conf.php),fonts为具体的 本地化的字体和一些相关配置(可以不用鸟它),include就比较重要了哇,是一些公共的库文件,js则是一些相关的js脚本,styles下存放了相 关的css文件。

 

    可以看出,整体结构还算很清晰的。

 

2.为啥是index.php?

 

  index基本是所有的web站的起点,zabbix的前端其实也是一个网站娃,所以我们还是从index.php开始入手去看。

3.index的脚本结构:

index脚本是非常重要的一个入口,所以结构上也比较复杂。

大致可以划分为库和宏定义、登录校验、前端展示等三个部分(这个是我自己划分的)。

 

4.库和宏定义:

 

<?php
require_once('include/config.inc.php');
require_once('include/forms.inc.php');

define('ZBX_NOT_ALLOW_ALL_NODES', 1);
define('ZBX_HIDE_NODE_SELECTION', 1);

$page['title']    = 'S_ZABBIX_BIG';
$page['file']    = 'index.php';

//        VAR            TYPE    OPTIONAL FLAGS    VALIDATION    EXCEPTION
    $fields=array(
        'name'=>            array(T_ZBX_STR, O_NO,    NULL,    NOT_EMPTY,    'isset({enter})', S_LOGIN_NAME),
        'password'=>        array(T_ZBX_STR, O_OPT,    NULL,    NULL,        'isset({enter})'),
        'sessionid'=>        array(T_ZBX_STR, O_OPT,    NULL,    NULL,        NULL),
        'message'=>            array(T_ZBX_STR, O_OPT,    NULL,    NULL,        NULL),
        'reconnect'=>        array(T_ZBX_INT, O_OPT,    P_SYS,    BETWEEN(0,65535),NULL),
        'enter'=>            array(T_ZBX_STR, O_OPT, P_SYS,    NULL,        NULL),
        'form'=>            array(T_ZBX_STR, O_OPT, P_SYS,  NULL,       NULL),
        'form_refresh'=>    array(T_ZBX_INT, O_OPT, NULL,   NULL,       NULL),
        'request'=>            array(T_ZBX_STR, O_OPT, NULL,     NULL,       NULL),
    );
    check_fields($fields);
?>

 

   require_once包含了相关的库,define不用多说了。$page数组定义了页面的title等信息,$fields数组保存了一些状态,具体干啥的。。我也不清楚。check_fields则检测$field数组。

不懂的我们暂时跳过吧,反正我是不会了。。。

      总而言之,这部发其实”无关紧要“,你都删掉了也没啥事。后续我会详细说这个。

 

5.登录校验:

登录校验是重点中的重点,所以这部分写的比较复杂,我也只是看个大概,下面大致说下。

      1.cookie、配置的读取

 

<?php
    $sessionid = get_cookie('zbx_sessionid', null);

    if(isset($_REQUEST['reconnect']) && isset($sessionid)){
        CUser::logout($sessionid);

        redirect('index.php');
        exit();
    }

    $config = select_config();

    $authentication_type = $config['authentication_type'];

    if($authentication_type == ZBX_AUTH_HTTP){
        if(isset($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_USER'])){
            if(!isset($sessionid)) $_REQUEST['enter'] = 'Enter';

            $_REQUEST['name'] = $_SERVER['PHP_AUTH_USER'];
            $_REQUEST['password'] = 'zabbix';//$_SERVER['PHP_AUTH_PW'];
        }
        else{
            access_deny();
        }
    }

   get_cookie用来读取用户浏览器已经保存的cookie,如果有则返回已经保存的,不然就返回空。具体定义如下:

 

function get_cookie($name, $default_value=null){
    if(isset($_COOKIE[$name]))    return $_COOKIE[$name];
return $default_value;
}

 

    可以看出来其实就是简单的读取_COOKIE超级数组中保存的值。

 

      if(isset($_REQUEST[‘reconnect’]) && isset($sessionid))

      这个if判断中应该是根据reconnect和sessionid去做重定向和跳转,具体的话。。其实我不懂。

      $config = select_config();

      这部分用来从数据库中读取相关的配置:

function select_config($cache = true){
    global $page;
    static $config;

    if($cache && isset($config)) return $config;

    $row = DBfetch(DBselect('SELECT * FROM config WHERE '.DBin_node('configid', get_current_nodeid(false))));

    if($row){
        $config = $row;
        return $row;
    }
    else if(isset($page['title']) && ($page['title'] != "S_INSTALLATION")){
        error(S_UNABLE_TO_SELECT_CONFIGURATION);
    }
return $row;
}

   DBfetch等函数是执行了相应的数据库的读写,具体实现不再解释了,有兴趣的继续追踪去看吧。这个函数是我下一段时间工作的重点,要对它做做手脚的。

 

      回到select_config这里,这里在登录校验这块最主要的就是根据用户的的校验类型(authentication_type)去做相应的校验,结合数据库我们来看一下:

image

 

我这里就一个用户,admin,所以就一条结果,可以看出来, authentication_type为0。再看ZBX_AUTH_HTTP等是个什么东东,grep下:

image

 

可以知道0表示为ZBX_AUTH_INTERNAL,即zabbix自身的验证机制,1表示为简单的http验证。相信大家都是http验证的缺点把 (简单、无法注销等),所以默认的采用的都是zabbix自身的验证,由zabbix自己来展示表格、读取数据等。

 

    再次回到前面的那个判断,现在再看就很简单了。就是判断如果是1则直接从_SERVER超级数组中读取用户名和密码并保存到_REQUEST数组中,如果读取失败则直接拒绝用户访问。

 

  这里要说的下为啥要读取了用户名和密码之后保存到_REQUEST数组中,继续看下面的这段代码:

    $request = get_request('request');
    if(isset($_REQUEST['enter'])&&($_REQUEST['enter']=='Enter')){
        global $USER_DETAILS;
        $name = get_request('name','');
        $passwd = get_request('password','');

        $login = CUser::authenticate(array('user'=>$name, 'password'=>$passwd, 'auth_type'=>$authentication_type));

        if($login){
            $url = is_null($request) ? $USER_DETAILS['url'] : $request;
            redirect($url);
            exit();
        }
    }

 

     看到了吧,这里再次使用get_request读去了相关的用户名和密码:

 

function get_request($name, $def=NULL){
    if(isset($_REQUEST[$name]))
        return $_REQUEST[$name];
    else
        return $def;
}

     当然这里应该是有一次再次调用index.php的过程,具体的我是没看到,唔,也许是没有吧,还望了解的大神指点。

 

       2.校验

 

      $login = CUser::authenticate(array(‘user’=>$name, ‘password’=>$passwd, ‘auth_type’=>$authentication_type));

      这个是具体的zabbix的校验函数,我们去看下是何方神圣:

public static function authenticate($user){
        try {
            self::BeginTransaction(__METHOD__);
            global $USER_DETAILS, $ZBX_LOCALNODEID;

            $name = $user['user'];
            $passwd = $user['password'];
            $auth_type = $user['auth_type'];

            if ($auth_type == ZBX_AUTH_HTTP) {
                // if PHP_AUTH_USER is not set, it means that HTTP authentication is not enabled
                if (!isset($_SERVER['PHP_AUTH_USER'])) {
                    throw new APIException(ZBX_API_ERROR_INTERNAL, S_CUSER_ERROR_CANNOT_LOGIN);
                }
                // check if the user name used when calling the API matches the one used for HTTP authentication
                elseif ($name !== $_SERVER['PHP_AUTH_USER']) {
                    throw new APIException(ZBX_API_ERROR_INTERNAL, S_CUSER_ERROR_USER_DOES_NOT_MATCH_HTTP_LOGIN);
                }

            }

            $password = md5($passwd);

            $sql = 'SELECT u.userid,u.attempt_failed, u.attempt_clock, u.attempt_ip '.
                    ' FROM users u '.
                    ' WHERE u.alias='.zbx_dbstr($name);

            $login = $attempt = DBfetch(DBselect($sql));

            if($login){
                if($login['attempt_failed'] >= ZBX_LOGIN_ATTEMPTS){
                    if((time() - $login['attempt_clock']) < ZBX_LOGIN_BLOCK){
                        throw new APIException(ZBX_API_ERROR_INTERNAL, S_CUSER_ERROR_ACCOUNT_IS_BLOCKED_FOR_XX_SECONDS_FIRST_PART.' '.(ZBX_LOGIN_BLOCK - (time() - $login['attempt_clock'])).' '.S_CUSER_ERROR_ACCOUNT_IS_BLOCKED_FOR_XX_SECONDS_SECOND_PART);
                    }
                    else{
                        DBexecute('UPDATE users SET attempt_clock='.time().' WHERE alias='.zbx_dbstr($name));
                    }
                }

                if($auth_type != ZBX_AUTH_HTTP){
                    switch(get_user_auth($login['userid'])){
                        case GROUP_GUI_ACCESS_INTERNAL:
                            $auth_type = ZBX_AUTH_INTERNAL;
                            break;
                        case GROUP_GUI_ACCESS_SYSTEM:
                        case GROUP_GUI_ACCESS_DISABLED:
                        default:
                            break;
                    }
                }

                switch($auth_type){
                    case ZBX_AUTH_LDAP:
                        $login = self::ldapLogin($user);
                        break;
                    case ZBX_AUTH_HTTP:
                        $login = true;
                        break;
                    case ZBX_AUTH_INTERNAL:
                    default:
                        $login = true;
                }
            }

            if($login){
                $sql = 'SELECT u.* '.
                        ' FROM users u'.
                        ' WHERE u.alias='.zbx_dbstr($name).
                            ((ZBX_AUTH_INTERNAL==$auth_type)? ' AND u.passwd='.zbx_dbstr($password):'').
                            ' AND '.DBin_node('u.userid', $ZBX_LOCALNODEID);

                $login = $user = DBfetch(DBselect($sql));
            }

            if($login){
                $login = (check_perm2login($user['userid']) && check_perm2system($user['userid']));
            }

            if($login){
                $sessionid = zbx_session_start($user['userid'], $name, $password);

                // we assign $USER_DETAILS first time, so that it could be used in Cprofile::get
                $USER_DETAILS = $user;

                add_audit(AUDIT_ACTION_LOGIN,AUDIT_RESOURCE_USER, 'Correct login ['.$name.']');
                if(empty($user['url'])){
                    $user['url'] = CProfile::get('web.menu.view.last','index.php');

                    // we assign $USER_DETAILS second time, to update 'url'
                    $USER_DETAILS['url'] = $user['url'];
                }

                $login = $sessionid;
            }
            else{
                $user = NULL;

                $_REQUEST['message'] = S_CUSER_ERROR_LOGIN_OR_PASSWORD_INCORRECT;
                add_audit(AUDIT_ACTION_LOGIN,AUDIT_RESOURCE_USER,'Login failed ['.$name.']');

                if($attempt){
                    $ip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']))?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];
                    $attempt['attempt_failed']++;
                    $sql = 'UPDATE users '.
                            ' SET attempt_failed='.$attempt['attempt_failed'].','.
                                ' attempt_clock='.time().','.
                                ' attempt_ip='.zbx_dbstr($ip).
                            ' WHERE userid='.zbx_dbstr($attempt['userid']);
                    DBexecute($sql);
                }
            }

            self::EndTransaction(true, __METHOD__);
            return $login;
        }
        catch (APIException $e) {
            self::EndTransaction(false, __METHOD__);
            $error = $e->getErrors();
            $error = reset($error);
            $_REQUEST['message'] = $error;
            return false;
        }
    }

 

   函数太长了,凑合着看就算根据前面拿到的用户名、密码去md5加密,然后从数据库中查找匹配的记录,如果存在则验证通过。当然这过 程中也顺带着写了下失败的登录次数、IP等信息进数据库。这个函数以后有时间我会详细解释,也是一个我要动手术的地方。

 

    redirect($url);

    这是一个跳转,在前面的用户验证过去之后跳转到dashboard.php这里去。感兴趣的可以试试,将这行改为redirect(‘index.php’)哈哈。

 

 6.前端展示:

 

  这部分主要是针对验证方式非http的登录表格展示和一些页头、页尾的展示。

 

include_once('include/page_header.php');

    if(isset($_REQUEST['message'])) show_error_message($_REQUEST['message']);

    if(!isset($sessionid) || ($USER_DETAILS['alias'] == ZBX_GUEST_USER)){
        switch($authentication_type){
            case ZBX_AUTH_HTTP:
                break;
            case ZBX_AUTH_LDAP:
            case ZBX_AUTH_INTERNAL:
            default:
//    konqueror bug #138024; adding useless param(login=1) to the form's action path to avoid bug!!
                $frmLogin = new CFormTable(S_LOGIN,'index.php?login=1','post','multipart/form-data');
                $frmLogin->setHelp('web.index.login');
                $frmLogin->addVar('request', $request);
                $lt = new CTextBox('name');
                $lt->addStyle('width: 150px');
                $frmLogin->addRow(S_LOGIN_NAME, $lt);

                $pt = new CPassBox('password');
                $pt->addStyle('width: 150px');
                $frmLogin->addRow(S_PASSWORD, $pt);
                $frmLogin->addItemToBottomRow(new CButton('enter','Enter'));
                $frmLogin->show(false);

                setFocus($frmLogin->getName(),'name');

                $frmLogin->destroy();
        }

    }
    else{
        echo '<div align="center" class="textcolorstyles">'.S_WELCOME.' <b>'.$USER_DETAILS['alias'].'</b>.</div>';
    }
?>

case语句匹配非http方式的方式,http方式的就直接break,不再展示表格鸟。。

    

      登录表格没什么可说的,html你懂就好。但这里其实不算直接写的html标签的形式去写死的表格,而是用了CFormTable、CTextBox、CPassBox等自定义的组建来实现的。

 

     setFocus($frmLogin->getName(),’name’);

      是将默认的光标放置到用户名输入框中,不得不说zabbix这点细节上做的比较赞。

 

      这里的大的if用来判断是否是已经登录,如果已经登录则展示一行欢迎语句:

    echo '<div align="center" class="textcolorstyles">'.S_WELCOME.' <b>'.$USER_DETAILS['alias'].'</b>.</div>';

          include_once(‘include/page_header.php’) 用来展示所有的zabbix的页首,即:

 

image

 

include_once(‘include/page_footer.php’); 用来展示所有的zabbix的页首,即:

 

image

 

    当然其实page_header.php不止是展示页首这么简单,还有很多的权限校验等,不过。。这个后续再说吧,这篇博文写不下了。

 

7.实践:

 

   看了那么久的代码肯定也累了,那么我们动手来做下,我们来修改下登录表格:

 

                $frmLogin = new CFormTable(S_LOGIN,'index.php?login=1','post','multipart/form-data');
                $frmLogin->setHelp('web.index.login');
                $frmLogin->addVar('request', $request);
                $lt = new CTextBox('name');
                $lt->addStyle('width: 150px');
                $frmLogin->addRow(S_LOGIN_NAME, $lt);
                //test furion 
                $ttt = new CTextBox('furion');
                $ttt->addStyle('width: 150px');
                $fromLogin->addRow('furion', $ttt);
                //test end

                $pt = new CPassBox('password');
                $pt->addStyle('width: 150px');
                $frmLogin->addRow(S_PASSWORD, $pt);
                $frmLogin->addItemToBottomRow(new CButton('enter','Enter'));
                $frmLogin->show(false);

                setFocus($frmLogin->getName(),'name');

                $frmLogin->destroy();

 

注意中间的furion这个表格就是我这里添加的,保存、刷新,我们看下效果:

 

image

     

    成功的添加了一行furion的表格。其他的玩法还有很多,例如修改登录成功的欢迎呀(S_WELCOME)这些,不再详细说了,有兴趣的自己动手试验吧。

 

8.总结:

 

Zabbix整体的设计还算很不错的,为了实现跨数据库、多语种做了很多的封装,爽是爽了,但看起来也着实蛋疼了下。其实就单说index.php我还有一些不懂的地方,后续还得慢慢挖掘下。

2 Responses to “zabbix前端代码浅析之index.php”

  1. 少林功夫好 says:

    打不开那些历史趋势图。这个chart.php在日志里包超时。
    script ‘/usr/local/nginx/html/zbx/chart.php’ (request: “GET /zbx/chart.php”) execution timed out (1578.272996 sec), terminating。
    能帮忙分析分析么。

  2. you are truly a good webmaster. The web site loading speed is
    amazing. It kind of feels that you’re doing any unique trick.
    Also, The contents are masterpiece. you’ve done a excellent job on this topic!

Leave a Reply

Your email address will not be published. Required fields are marked *


To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax