注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

onefish资料库

成功要素---耐得住孤独!

 
 
 

日志

 
 

PPTP插件编写指南  

2013-02-04 16:48:57|  分类: 程序 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
PPTP介绍

点对点隧道协议(PPTP,Point to Point Tunneling Protocol)是一种主要用于VPN的传输层网络协议。PPTP的协定规范本身并未描述加密或身份验证的特性,然而常见的如Microsoft Windows带有的实现都具备这些。因其方便部署,被windows所支持,应用较广泛,目前主要用于用户接入VPN网络。这里谈到的PPTP版本指的是:http://ppp.samba.org提供的Linux版本。

一、      插件原理

从2.3.10版本开始,pppd包含了对’插件’的支持。插件的初衷是为人们提供一种方式自定义pppd的行为,而不需要将本地补丁打到每一个或者把他们的补丁合入到标准发布版本。允许用户根据PPTP提供的接口编写共享库模块,在PPTP会话接入的时候加载,可以实替换原有流程实现认证、流量统计、限速等功能。

一个插件即是一个标准的共享库,一般文件名以.so结尾。使用dlopen()库调用来加载它们,所以它们只支持能使用共享库和dlopen调用的系统。目前pppd编译版本只能在Linux和Solaris下面支持插件。pppd使用plugin选项来加载插件,只有一个参数:插件文件名。plugin是一个特有的选项。如果指定的文件名不包含斜线,pppd会到/usrlib/pppd/<version>目前下面查找该文件,这里面version是pppd的版本,比如2.4.2。建议给文件的全路么或是基本名称,避免一些别有用心的用户把你原本想要加载的插件文件替换成了其它的。比如通过设置LD_LIBRARY_PATH变量。

本文主要围绕认证认证插件的编写,应用实例来自于SSL M5.1项目,需要将PPTP认证与设备上的认证结合。

二、      插件使用范围

插件使用至少下面所列的四种方式来影响pppd的行为:

ü         添加pppd可以添加的扩展选项。使用叫做add_options()的过程和指一个指向option_t 结构的数据来完成。数据组的最后一项必须设置name字段为NULL。

ü         pppd包含过程指针的hook变量。如果一个给定的hook非NULL,pppd会在处理过程中调用该指针指向的过程。插件可以任意设置自己的过程到这些hook。参见下面关于目前hook实现的描述。

ü         插件代码可以调用pppd中任意的全局过程、访问全局变量。

ü         插件可以注册过程特定事件触发时候调用的过程,使用’notifier’机制。hook和notifier的区别是一个hook只能调用一个函数,而notifier可以任意数量的,同时hook一般返回一些值给pppd,但是notifier函数什么都不返回。

 

三、      编写范例

一个典型的认证插件如下

#include “pppd.h”

 

char pppd_version[]                  = VERSION;

 

void plugin_init(void)

{

/* add hook for chap authentication. */

chap_check_hook = pppd_chap_check;

chap_verify_hook  = pppd_chap_verify;

allowed_address_hook = check_address_allowed;

ip_choose_hook = pppd_ip_choose;

allowed_address_hook = pppd_allowed_address;

add_notifier(&exitnotify, pppd_user_logout, NULL);

}

 

static int32_t pppd_chap_check(void) {

 

info(“=============pppd_chap_check=============”);

/* return one, because we need to ask peer everytime for authentication. */

return 1;

}

 

 

static int32_t pppd_chap_verify(char *user, char *ourname, int id,

struct chap_digest_type *digest,

unsigned char *challenge, unsigned char *response,

char *message, int message_space)

{

UserInfo* uf = get_local_userinfo(user);

if(uf)

{

error(“user is not exists !”);

return 0;

}

if(digest->verify_response(id, user, uf->password, strlen(uf->password), challenge, response, message, message_space))

{

return 1;

}

else

{

error(“pppd_chap_verify failed !”);

return 0;

}

}

 

 

static int pppd_allowed_address(u_int32_t addr)

{

LINE_INFO(“enter pppd_allowed_address.”);

return 1;

}

 

static void pppd_ip_choose(u_int32_t *addrp)

{

LINE_INFO(“enter pppd_ip_choose.”);

u_int32_t ip = 0;

LoopUpIP(g_sessid, &ip);

*addrp = ip;

}

 

static void pppd_user_logout(void *arg, int stat)

{

LINE_INFO(“enter pppd_user_logout.”);

destoryVariables();

}

 

static int check_address_allowed(u_int32_t addr)

{

LINE_INFO(“enter check_address_allowed.”);

return 1;

  1. 必须包含的部分

1)         PPPD版本申明

char pppd_version[]                  = VERSION;

主要用于表明这个插件可以运行的pppd版本,如果该申明被包含,pppd不会加载跟编译成的二进制文件不同版本的模块。

2)         plugin_init

每一个插件必须包含一个全局过程叫做“plugin_init”。这个过程在插件被加载后立即被调用,每个插件都应有该函数。可以在这个函数里面挂上需要的钩子函数,实现特定的功用。

void plugin_init(void)

{

/* add hook for chap authentication. */

chap_check_hook = pppd_chap_check;

chap_verify_hook  = pppd_chap_verify;

allowed_address_hook = check_address_allowed;

ip_choose_hook = pppd_ip_choose;

allowed_address_hook = pppd_allowed_address;

add_notifier(&exitnotify, pppd_user_logout, NULL);

}

 

  1. 钩子函数的选用

基本各项功能需要靠钩子函数来实现,可以根据不同的需要选用不同的钩子函数,具体的钩子介绍见第下面的钩子函数介绍。需要注意的是

1)        钩子函数在pppd内是是通过extern的方式实现的一个全局函数指针,通过赋值的方式来实现挂钩子,因此当多个插件使用相同钩子的时候,因为加载插件的钩子会覆盖前面的钩子。

2)        对于认证相关的钩子函数,根据PPTP支持的认证方式不同钩子函数也不一样,比如pap认证可以获取明文密码,但是chap认证只能获取到认证密码摘要,编写插件之前,请确认认证需要来选择钩子函数。

以当前认证插件的需要,我们需要使用到钩子函数

chap_verify_hook = pppd_chap_verify;

 

  1. 认证函数的实现

static int32_t pppd_chap_verify(char *user, char *ourname, int id,

struct chap_digest_type *digest,

unsigned char *challenge, unsigned char *response,

char *message, int message_space)

{

UserInfo* uf = get_local_userinfo(user);

if(uf)

{

error(“user is not exists !”);

return 0;

}

if(digest->verify_response(id, user, uf->password, strlen(uf->password), challenge, response, message, message_space))

{

return 1;

}

else

{

error(“pppd_chap_verify failed !”);

return 0;

}

}

这里的认证采用的是ms-chapv2,所以选用了chap的认证钩子函数。该认证函数可以获取客户端认证的用户、密码摘要值。因此我们需要做的是从本地密码库里面查找该用户是否存在,如果存在获取其密码,通过digest->verify_response函数,验证该用户是否合法,该函数内会通过密码和当前会话的挑战码等情况计算摘要值并对比是否跟客户端的摘要值是否一样。认证成功返回1,失败返回0,PPPD会终止当前会话,并给出提示到客户端。

四、      编译

插件通常由C写成,在使用的平台以特定方式编译链接成共享对象文件。使用Linux下的gcc,一个’xyz’的插件可以使用下面的命令进行编译、链接。

gcc -c -O xyz.c

gcc -shared -o xyz.so xyz.o

 

五、      插件钩子函数列表

int (*idle_time_hook)(struct ppp_idle *idlep);

idle_time_hook在这个连接第一次开始的时候被调用(比如,第一个网络协议开始的时候),那之后被定时调用。在第一次调用的时候,idlep参数是NULL,返回值则是pppd检查连接活跃前的秒数,或者0表示没有超时。

在后来的调用中,idlep指令一个指向最包被发送、接收秒数的结构体。如果返回的值大于0,pppd会在再次检查之前等一些的秒数。如果小于等于0,就是说该连接在不活跃的情况下应该被终结。

 

int (*holdoff_hook)(void);

当尝试拌连接失败或是连接被终结的时候,holdoff_hook被调用,persist或是demand选项被使用。它返回PPTP重新建立连接应该等待的秒数(0表示立即)。

 

int (*chap_check_hook)(void);

int (*chap_passwd_hook)(char *user, char *passwd);

int (*chap_auth_hook)(char *user, u_char *remmd, int remmd_len, chap_state *cstate);

这些hook被设计用来在插件里面替换常规的CHAP密码处理过程(比如外部服务器的认证)。

 chap_check_hook被调用来检查对端是否有必要向我们认证其自身。如果返回1,pppd将询问询问其自身,否则返回0(如果该认证被要求了,在网络协议协商之前pppd会退出或是终结当前连接)。如果返回-1,pppd将查找chap-secrets文件使用常规方式处理。

 

chap_passwd_hook决定pppd应该使用什么密码来使用CHAP来向对端认证自身。user字符串使用’user’选项或者’name’选项、主机名进行初始化,有必须可以进行修改。这个钩子只有当ppdp是客户客户端而非服务的时候被调用。passwd可以容纳最多MAXSECRETLEN 字节。如果钩子返回0,PPPD使用 *passwd,如果返回 -1,pppd认证失败。

 

chap_auth_hook 钩子确定由对应提供的CHAP挑战响应是否有效。user指向一个包含对端提供用户名的非NULL结束的字符串。remmd向向对端提供的响应,由remmd_len表示其长度 。cstate是PPTP维护的内部CHAP状态结构体。chap_auth_hook应该返回CHAP_SUCCESS 或者CHAP_FAILURE。

 

int (*null_auth_hook)(struct wordlist **paddrs, struct wordlist **popts);

这个钩子允许插件确定当请求的认证被对端拒绝的时候应该采取的策略。 如果返回0,连接被终结;1,连接被允许处理,这种情况下 *paddrs和*popts可像pap_auth_hook一样被设置,以指定被允许的IP地址列表和任意扩展属性。如果返回-1,pppd查找pap-secret文件按常规处理。

void (*ip_choose_hook)(u_int32_t *addrp);

该钩子在IPCP协商开始的时候调用。它使得插件有机会设置对端的IP地址。地方应该保存在*addrp。如果*addrp里什么也没有存储的庆,pppd使用常规方式决定对端的地址。

int (*allowed_address_hook)(u_int32_t addr)

这个钩子确认对端是否可以使用指定的IP地址。如果钩子返回1,地址可以接受,返回0为拒绝。如果返回-1,将使用常规方式查找适当的选项和secrets文件来决定。

void (*snoop_recv_hook)(unsigned char *p, int len)

void (*snoop_send_hook)(unsigned char *p, int len)

这些钩子在接受或是发送数据包的时候被调用。数据包在p里同,长度由 len表式。使得插件可以检查pppd的会话。这些钩子在实现L2TP的时候将会起到很大的作用。

六、      消息列表

插件可以使用notifier注册自身,通过申明一个下面形式的过程:

void my_notify_proc(void *opaque, int arg);

然后使用适当的notifier,以下面形式调用来注册这个过程。

add_notifier(&interesting_notifier, my_notify_proc, opaque);

add_notifier 中的’opaque’参数每次调用notifier的时候传递给my_notify_proc 。传递的’arg’参数决定于notifier一个nofify过程可以使用下面的方式从当前的notifier列表中被移除。

remove_notifier(&interesting_notifier, my_notify_proc, opaque);

 

下面是目前pppd实现的notifier列表。

ü         pidchange 由其父进程在pppd已经forked并且子进程在继续pppd’s处理时调用,比如pppd从其控制终端中分离出来的时候。参数就是这个子进程的pid。

ü         phasechange 当pppd从一个阶段操作转移到另一个的时候调用。参数是新阶段的编号。

ü         exitnotify 在pppd退出前调用。参数是pppd退出的状态。(比如exit()的参数)。

ü         sigreceived 在收到信号的时候调用,存在于signal handle里面。参数是signal的编号。

ü         ip_up_notifier 在IPCP发生的时候调用。

ü         ip_down_notifier 在IPCP关闭的时候调用。

ü         auth_up_notifier 在对端认证自身成功的时候调用。

ü         link_down_notifier 在连接关闭的时候调用。

  评论这张
 
阅读(411)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018