分类 C/C++ 下的文章

php禁用eval

The eval() language construct is very dangerous because it allows execution of arbitrary PHP code.

eval是语言结构,不是函数,所以无法使用disable_functions来禁用

之前写过:从 php 内核挂载钩子解密源码,禁用的原理和这个差不多

static zend_op_array* guard_compile_string(zval *source_string, char *filename)
{
    // php_printf("s2: %s %Z\n", filename, source_string);
    if (strstr(filename, "eval()'d code")) {
        return NULL;
    }
    return old_compile_string(source_string, filename);
}

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(guard)
{
    old_compile_string = zend_compile_string;
    zend_compile_string = guard_compile_string;
    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(guard)
{
    zend_compile_string = old_compile_string;
    return SUCCESS;
}

为什么要写这篇文章

主要是因为之前太浪了:

  1. MySQL之类的端口都是直接绑定到公网的(并没有进行防火墙限制)
  2. 博客的目录权限为了偷懒直接设置成了 0777

最主要的是产生的严重后果:今天写文章的时候突然发现文章附件多了个为归属的fileadmin.zip;瞬间菊花一紧,上服务器一看,各种web shell。

在线工具的各种配置更新的还是比较及时,端口也收的比较紧,review之后发现应该不会产生类似的问题;这个问题暂时出现在了博客的vps上。

后面怎么解决这样的问题

  1. 不向外暴露内部的端口
  2. php hook eval
  3. 及时同步最新的php配置等

其他问题

laravel 中使用了 jeremeamia/superclosure 包,而这个包中使用了eval,所以不能正常工作;这样就需要在上面的代码中做个白名单。

php扩展实践zend_execute_ex层获取实参

其实在实现的 php 函数里面是很容易获取到的,参考 php 的 builtin 函数 func_get_args() 就可以知道了。

void **p;
int arg_count;
int i;
zend_execute_data *ex = EG(current_execute_data);

if (!ex || !ex->function_state.arguments) {
    RETURN_FALSE;
}

p = ex->function_state.arguments;
arg_count = (int)(zend_uintptr_t) *p;

for (i = 0; i < arg_count; i++) {
    zval *element, *arg;
    arg = *((zval **) (p - (arg_count - i)));
    php_var_dump(&arg, 1 TSRMLS_CC);
}

但是在 zend_execute_ex 中,是不能使用 function_state.arguments 来获取参数的,需要从 argument_stack 中获取调用函数的实参。

static void (*old_zend_execute_ex) (zend_execute_data *execute_data TSRMLS_DC);

ZEND_API void learn_execute_ex (zend_execute_data *execute_data TSRMLS_DC)
{
    php_printf("====== extension debug start ======\n");
    php_printf("function name: %s\n", get_active_function_name(TSRMLS_C));

    old_zend_execute_ex(execute_data TSRMLS_CC);

    int stacked = 0;
    void **top;
    void **bottom;
    zval *arguments;
    smart_str buf = {0};

    array_init(arguments);
    
    top = zend_vm_stack_top(TSRMLS_C) - 1;
    if (top) {
        stacked = (int)(zend_uintptr_t) *top; // argc
        if (stacked) {
            bottom = zend_vm_stack_top(TSRMLS_C);
            EG(argument_stack)->top = top + 1;
            if (zend_copy_parameters_array(stacked, arguments TSRMLS_CC) == SUCCESS) {
                php_json_encode(&buf, arguments, 0 TSRMLS_CC);
            }
            EG(argument_stack)->top = bottom;
        }
    }

    smart_str_0(&buf);

    php_printf("%s\n", buf.c);

    smart_str_free(&buf);
    zval_dtor(arguments);

    php_printf("====== extension debug end ======\n");
}

PHP_MINIT_FUNCTION(learn)
{
    old_zend_execute_ex = zend_execute_ex;
    zend_execute_ex = learn_execute_ex;

    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(learn)
{
    zend_execute_ex = old_zend_execute_ex;

    return SUCCESS;
}

2015-11-04 00:38 更新

后来看到,其实不用上面这中方法就可以实现, php 5.5之后要从 prev 里面去取

/**
 * php_var_dump defined in this head file.
 */
#include "ext/standard/php_var.h"

zend_execute_data *real_execute_data = execute_data->prev_execute_data;

void **p = real_execute_data->function_state.arguments;
int arg_count = (int) (zend_uintptr_t) * p;
zval *argument_element;
int i;
// zval *obj = real_execute_data->object;
unsigned long start = mach_absolute_time();
for (i = 0; i < arg_count; i++) {
    argument_element = *(p - (arg_count - i));
    php_var_dump(&argument_element, 1);
}

php扩展实践之数组操作

初始化数组

zval *arr;
MAKE_STD_ZVAL(arr);
array_init(arr);

插入和更新

// 索引
add_index_bool(arr, 10, 1);
add_next_index_long(arr, 1000);
// 关联数组
add_assoc_stringl(arr, "name", ZEND_STRL("xiaozi"), 1);

查找和删除

char *name = "name";
int nameLen = strlen(name);
// 查找
zend_hash_exists(Z_ARRVAL_P(arr), name, nameLen + 1);
// 删除
zend_hash_del(Z_ARRVAL_P(arr), name, nameLen + 1);

读取

char *name = "name";
int nameLen = strlen(name);
zval **value;
if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
    php_var_dump(value, 1 TSRMLS_CC);
}

php扩展实践之implements内置接口

这次用 php 扩展的方式来实现一下 Laravel5.1 Support 中的 Fluent,实现一下 php 内置接口 ArrayAccess 的方法

method参数的定义

#include "Zend/zend_interfaces.h"

zend_class_entry *fluent_ce;

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___construct, 0, 0, 1)
    ZEND_ARG_ARRAY_INFO(0, attributes, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetexists, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetget, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetset, 0, 0, 2)
    ZEND_ARG_INFO(0, offset)
    ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetunset, 0, 0, 1)
    ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___get, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___set, 0, 0, 2)
    ZEND_ARG_INFO(0, name)
    ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___isset, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___unset, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

定义类方法

ZEND_METHOD(fluent, __construct) {
    zval *attributes, *instance;
    MAKE_STD_ZVAL(attributes);
    array_init(attributes);
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &attributes) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    zend_update_property(fluent_ce, instance, ZEND_STRL("attributes"), attributes TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetExists) {
    zval *value, *instance, *member;
    char *offset;
    int offsetLen;
    int exists;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    MAKE_STD_ZVAL(member);
    ZVAL_STRINGL(member, offset, offsetLen, 1);
    exists = std_object_handlers.has_property(instance, member, 0, 0 TSRMLS_DC);
    zval_ptr_dtor(&member);
    RETURN_BOOL(exists);
}
ZEND_METHOD(fluent, offsetGet) {
    zval *value, *instance;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    php_printf("%s\n", offset);
    value = zend_read_property(fluent_ce, instance, offset, offsetLen, 1 TSRMLS_CC);
    RETURN_ZVAL(value, 1, 0);
}
ZEND_METHOD(fluent, offsetSet) {
    zval *value, *instance;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &offset, &offsetLen, &value) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    zend_update_property(fluent_ce, instance, offset, offsetLen, value TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetUnset) {
    zval *instance, *member;
    char *offset;
    int offsetLen;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    MAKE_STD_ZVAL(member);
    ZVAL_STRINGL(member, offset, offsetLen, 1);
    std_object_handlers.unset_property(instance, member, 0 TSRMLS_DC);
    zval_ptr_dtor(&member);
}
ZEND_METHOD(fluent, __get) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    zval **value;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
        RETURN_ZVAL(*value, 1, 0);
    } else {
        RETURN_NULL();
    }
}
ZEND_METHOD(fluent, __set) {
    char *name;
    int nameLen;
    zval *value, *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &nameLen, &value) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    add_assoc_zval(attributes, name, value);
    zval_add_ref(&value);
}
ZEND_METHOD(fluent, __isset) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    if (zend_hash_exists(Z_ARRVAL_P(attributes), name, nameLen + 1) == SUCCESS) {
        RETURN_BOOL(1);
    } else {
        RETURN_BOOL(0);
    }
}
ZEND_METHOD(fluent, __unset) {
    char *name;
    int nameLen;
    zval *attributes, *instance;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
    zend_hash_del(Z_ARRVAL_P(attributes), name, nameLen + 1);
}

static zend_function_entry fluent_methods[] = {
    ZEND_ME(fluent, __construct, arginfo_fluent___construct, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetExists, arginfo_fluent_offsetexists, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetGet, arginfo_fluent_offsetget, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetSet, arginfo_fluent_offsetset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, offsetUnset, arginfo_fluent_offsetunset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __get, arginfo_fluent___get, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __set, arginfo_fluent___set, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __isset, arginfo_fluent___isset, ZEND_ACC_PUBLIC)
    ZEND_ME(fluent, __unset, arginfo_fluent___unset, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

模块初始化的时候注册类

PHP_MINIT_FUNCTION(learn)
{
    zval *attributes;
    MAKE_STD_ZVAL(attributes);
    array_init(attributes);
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Fluent", fluent_methods);
    fluent_ce = zend_register_internal_class(&ce TSRMLS_CC);
    // implements ArrayAccess
    zend_class_implements(fluent_ce TSRMLS_DC, 1, zend_ce_arrayaccess);
    zend_declare_property_null(fluent_ce, ZEND_STRL("attributes"), ZEND_ACC_PROTECTED TSRMLS_CC);

    return SUCCESS;
}

php扩展实践之调用类

PHP_FUNCTION(learn_call) {
    // 实例化类
    zval *learn;
    MAKE_STD_ZVAL(learn);
    object_init_ex(learn, learn_ce);

    char *name = "xiaozi";
    char *methodName = "__construct";
    zval *param, *method, *callResult;
    zval **params[1];
    MAKE_STD_ZVAL(method);
    ZVAL_STRINGL(method, methodName, strlen(methodName), 1);
    MAKE_STD_ZVAL(param);
    ZVAL_STRINGL(param, name, strlen(name), 1);
    params[0] = &param;
    // 调用类的 __construct 方法
    call_user_function_ex(&(learn_ce)->function_table, &learn, method, &callResult, 1, params, 0, NULL TSRMLS_CC);
    zval_ptr_dtor(&method);
    zval_ptr_dtor(&param);
    zval_ptr_dtor(&learn);
    if (callResult) {
        zval_ptr_dtor(&callResult);
    }
}

php扩展实践之定义类

zend_class_entry *learn_ce;

ZEND_BEGIN_ARG_INFO_EX(arginfo_learn___construct, 0, 0, 1)
    ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()

// Learn::__construct 的实现
ZEND_METHOD(learn, __construct) {
    char *name = "tianzi", *methodName;
    zval *method, *callResult, *instance;
    int nameLen = strlen(name);
    // 带有默认值的传入参数
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &nameLen) == FAILURE) {
        RETURN_NULL();
    }
    instance = getThis();
    // 更新类的属性
    zend_update_property_stringl(learn_ce, instance, ZEND_STRL("name"), ZEND_STRL(name) TSRMLS_DC);
}
// Learn::hi 的实现
ZEND_METHOD(learn, hi) {
    zval *instance = getThis();
    // 读取类的属性
    zval *name = zend_read_property(learn_ce, instance, ZEND_STRL("name"), 0 TSRMLS_CC);
    char *theName = estrndup(Z_STRVAL_P(name), Z_STRLEN_P(name));
    php_printf("hi %s\n", theName);
    efree(theName);
}
// 类方法的属性
static zend_function_entry learn_methods[] = {
    ZEND_ME(learn, __construct, arginfo_learn___construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    ZEND_ME(learn, hi, NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

PHP_MINIT_FUNCTION(learn)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Learn", learn_methods);
    // 注册类
    learn_ce = zend_register_internal_class(&ce TSRMLS_CC);
    // 定义类属性
    zend_declare_property_null(learn_ce, ZEND_STRL("name"), ZEND_ACC_PUBLIC TSRMLS_CC);

    return SUCCESS;
}

php扩展实践之定义常量

PHP_MINIT_FUNCTION(learn)
{
    // CONST_CS 区分大小写
    // CONST_PERSISTENT 在模块加载的时候都是有效的,否则每次 request 之后就会删除
    REGISTER_BOOL_CONSTANT("LEARN", 1, CONST_CS | CONST_PERSISTENT);

    return SUCCESS;
}

撸 php 源码

快速定位某个 php 函数在源码中的位置

# 所有php函数
ag 'PHP_FUNCTION\(\w+\)'
# 指定php函数
ag 'PHP_FUNCTION\(array_flip\)'

自定义个 shell 函数,方便搜索

# search php funtion in c source
function phpsrc()
{
    if [ $# -eq 0 ]; then
        echo 'usage: phpsrc <function>[ <dirname>]'
        return
    fi
    dirname="$2"
    if [ -z "$dirname" ]; then
        dirname=$(pwd)
    fi
    ag 'PHP_FUNCTION\('"$1"'\)' "$dirname"
}

快速定位 php 中的语言结构

# Zend/zend_compile.c
ag 'void zend_do_' Zend/zend_compile.c

从 php 内核挂载钩子解密源码

背景

大多数的php代码加密(不需要额外扩展就能运行的)原理上都是使用eval进行代码的执行,理论上,只要我们在php内核执行eval函数的时候,将其dump出来,就可以得到源代码。需要注意的是:

  1. 用户上传的代码是不可信的,因此需要一个沙盒
  2. 此法虽然方便,看似是一个万能解密的办法,但是 dump 数据的时候会有很多中间值,还是需要人工的做一个特征库,去识别过滤出需要的代码段

实现

在 php 扩展中, module init 的时候替换掉 zend_compile_string,主要代码如下

static zend_op_array *edump_compile_string(zval *source_string, char *filename TSRMLS_DC)
{
    int c, len;
    char *copy;
 
    if (Z_TYPE_P(source_string) != IS_STRING) {
        return orig_compile_string(source_string, filename TSRMLS_CC);
    }
 
    len  = Z_STRLEN_P(source_string);
    copy = estrndup(Z_STRVAL_P(source_string), len);
    if (len > strlen(copy)) {
        for (c=0; c<len; c++) if (copy[c] == 0) copy[c] == '?';
    }
 
    php_printf("----- [tool.lu start] -----\n");
    php_printf("%s\n", copy);
    php_printf("----- [tool.lu end] -----\n");
 
    yes = 1;

    return orig_compile_string(source_string, filename TSRMLS_CC);
}

PHP_MINIT_FUNCTION(edump)
{
    if (edump_hooked == 0) {
        edump_hooked = 1;
        orig_compile_string = zend_compile_string;
        zend_compile_string = edump_compile_string;
    }
    return SUCCESS;
}

使用docker作为沙盒

一些php扩展的资料整理

Thread-Safe 宏

TSRM

Thread-Safe Resource Manager
线程安全资源管理

ZTS

Zend Thread Safety
Zend 线程安全

tsrm_ls

TSRM local storage
线程安全资源管理 本地存储

TSRMLS_??

TSRMLS_C tsrm_ls
TSRMLS_D void ***tsrm_ls
TSRMLS_CC , tsrm_ls
TSRMLS_DC , void ***tsrm_ls
CC - call with comma
     前置逗号的调用
C  - call
     调用
DC - declaration with comma
     前置逗号的声明
D  - declaration
     声明

zend_parse_parameters

类型符号

类型字符php参数类型c类型
llonglong
ddoubledouble
sstringchar* / int
bbooleanzend_bool
rresourcezval*
aarrayzval*
zzvalzval*
Zzvalzval**
ffunctionfunction
o/Oclasszval, zend_class_entry

控制字符

控制字符作用
|它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。
!如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。
/如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1.

返回值

返回函数
RETURN_RESOURCE(resource)
RETURN_BOOL(bool)
RETURN_NULL()
RETURN_LONG(long)
RETURN_DOUBLE(double)
RETURN_STRING(string, duplicate)
RETURN_STRINGL(string, length, duplicate)
RETURN_EMPTY_STRING()
RETURN_FALSE()
RETURN_TRUE()

helpers

helperexpanded解释
ZEND_FE_END(){NULL, NULL, NULL}用于zend_function_entry的最后一行
ZEND_STRL("str")"str", strlen("str")用于同时需要字符串和其长度的函数中

C实现进度条

#include <stdio.h>

int main() {
    int pwidth = 75;
    int i, j;
    for (i = 0; i < pwidth; i++) {
        // 构建进度条
        printf("%3d%% [", i * 100 / pwidth);
        for (j = 0; j <= i; j++)
            putchar('=');
        for (; j < pwidth; j++)
            putchar(' ');
        putchar(']');
        fflush(stdout);
        sleep(1);
        // 把刚才输出的都擦掉,用\r会有问题。
        for (j = 0; j < pwidth + 7; j++)
            putchar('\b');
    }
    putchar('\n');
    return 0;
}

02-19,补充:


Terminal Control Escape Sequences 找到

Erase End of Line <ESC>[K

  • Erases from the current cursor position to the end of the current line.

Erase Start of Line <ESC>[1K

  • Erases from the current cursor position to the start of the current line.

Erase Line <ESC>[2K

  • Erases the entire current line.

Erase Down <ESC>[J

  • Erases the screen from the current line down to the bottom of the screen.

Erase Up <ESC>[1J

  • Erases the screen from the current line up to the top of the screen.

Erase Screen <ESC>[2J

  • Erases the screen with the background colour and moves the cursor to _home_.

若是tty的话,可以这样:

printf("\r\x1b[K%3d%% [", i * 100 / pwidth);

SimpleIni修改my.cnf

为了实现自动化修改mysql的配置文件,使用C++程序对改ini样式的文件进行修改

#include "SimpleIni.h"
int main(int argc, char* argv[]) {
	CSimpleIniA ini;
	const char* filename = "/etc/my.cnf";
	ini.SetUnicode();
	ini.LoadFile(filename);
	ini.SetValue("mysqld", "server-id", "1");
	ini.SetValue("mysqld", "binlog-do-db", "mydb");
	ini.SaveFile(filename, false);

	return 0;
}

C++ split的实现

#include 
#include 
#include 
#include 
#include 

using namespace std;

vector &split(const string &s, char delim, vector &elems) {
    istringstream iss(s);
    string item;
    while(getline(iss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}


vector split(const string &s, char delim) {
    vector elems;
    split(s, delim, elems);
    return elems;
}

int main() {
	vector elems = split("Hello, world!", ',');
	for(vector::iterator it = elems.begin(); it < elems.end(); it++) {
		cout << *it << endl;
	}
	return 0;
}

C++读取配置文件

配置文件格式

 # nimei
host = 127.0.0.1
 port = 80

	key
	=123
mask = 255.255.255.0
# comment

C++代码

/**
 * @author xiaozi<245565986@qq.com>
 */
#include 
#include 
#include 
#include 

using namespace std;

string trim(const string& str);

int main(int agrc, char* argv[]) {
	const char* filename = "parse.conf";
	ifstream ifs(filename);

	string line = "";
	string key = "";
	string value = "";
	string::size_type pos = string::npos;

	map options;

	while(! ifs.eof()) {
		getline(ifs, line);

		line = trim(line);
		// 空行 和 注释行 和 不存在等号的行,跳过
		if(line.empty() || line.at(0) == '#') {
			continue;
		}

		if((pos = line.find('=')) == string::npos) {
			// cout << "语法错误" << endl;
			continue;
		}

		key = trim(line.substr(0, pos));
		value = trim(line.substr(pos + 1, line.size() - pos - 1));

		// key不为空的时候,留下该行数据
		if(! key.empty()) {
			options[key] = value;
		}
	}

	ifs.close();

	// 输出得到的数据
	map::iterator it;
	for(it = options.begin(); it != options.end(); it++) {
		cout << (*it).first << " => " << (*it).second << endl;
	}

	return 0;
}

string trim(const string& str) {
	if(str.empty()) {
		return str;
	}
	string::size_type pos = str.find_first_not_of(" \t\n\r\0\x0B");
	if(pos == string::npos) {
		return str;
	}
	string::size_type pos2 = str.find_last_not_of(" \t\n\r\0\x0B");
	return str.substr(pos, pos2 - pos + 1);
}