Site hosted by Angelfire.com: Build your free website today!

LDAP在信息系统中的应用

西安交通大学 计算机系 刘杰

 

摘要

计算机和网络技术的飞速发展加快了整个社会的发展。基于网络技术的应用给人们提供了极大的便利。

LDAPLightweight Directory Access Protocol)是一种从X.500发展而来的目录访问协议, LDAP的从标准制订到现在仅仅过去了不到10年,而最新的LDAPv3经历的时间更是只有短短的三年。所以LDAP应用的规模也比较有限。但是随着Internet(全球互联网)的普及,LDAP也越来越多的被应用于各种用途,并且已经取代了X.500成为了目录访问的工业标准。

在现在,LDAP主要应用的领域包括电子黄页,企业以及公共地址簿,而众多的ICP(互联网内容提供商,Internet Content Provider,ISP(互联网服务提供商,Internet Service Provider),甚至是一般的企业以及公共服务部门,也可以利用LDAP服务器管理自己的客户档案。

LDAP目录服务是基于C/SClient/Server)模式的,因此在LDAP相关的各种应用中,服务器的配置以及客户机的开发就成为了两大主题。本文主要的论述两个方面,一是在应当如何配置LDAP服务器,特别是应当如何组织服务器集群,二是如何进行LDAP的开发,包括客户端的开发以及Web开发。

 

关键词:LDAP 目录服务 数据组织

 

Abstract

The fast development of computer and Internet technology has sped up the development of the whole society. Moreover, applications based on the Internet technology have been providing people great convenience.

The Lightweight Directory Access Protocol (LDAP) is a directory access protocol derived from X.500. However, it has been less than 10 years since the stipulation of the standards of the LDAP, while the latest LDAPv3 has only a history of three short years; therefore, the scale of the application of the LDAP is limited. With the dissemination of the Internet, the LDAP has been increasingly widely used for varied functions, and has taken X.500's place as the industrial standard for directory access.

Currently, the LDAP is mainly used in the areas of electronic yellow page, enterprise, and public directory inquiries, with many Internet Content Providers (ICPs), Internet Service Providers (ISPs), and even common enterprises and public service departments using LDAP servers to manage their clients' archives as well.

The directory service of the LDAP is based on the Client/Server (C/S) model; therefore, the configuration of the server and develop of the client machine programs have been the two subject matters among other applications related to LDAP. The present thesis is focused on two aspects: (1) How to configure the LDAP server, especially how to organize the server group; and (2) How to conduct LDAP development, including the development of the client terminal and that of the Web.

 

KeywordsLDAPDirectory ServiceData Organization

目录

摘要--- 1

Abstract- 2

目录--- 3

第一章   引言--- 5

第二章   目录服务及LDAP协议--- 7

2.1       目录服务的基本概念-- 7

2.2       LDAP协议的历史-- 8

2.3       LDAP协议的访问以及数据组织-- 9

2.4       LDAP URL- 13

2.5       访问控制-- 15

第三章       LDAP服务器--- 16

3.1       概述-- 16

3.2       LDAP服务器的部署-- 16

3.2.1        单服务器的目录服务-- 17

3.2.2        使用服务器群作为LDAP服务器-- 17

3.3       设计LDAP目录树-- 20

第四章       Openldap服务器配置--- 22

4.1       Openldap项目简介-- 22

4.2       安装Openldap服务器-- 22

4.2.1        获取软件-- 22

4.2.2        前期的准备-- 23

4.2.3        配置安装选项-- 23

4.2.4        编译软件-- 24

4.2.5        测试软件-- 24

4.2.6        安装软件-- 24

4.3       服务器的启动和关闭-- 25

4.4       slapd配置文件-- 25

第五章       LDAP程序开发实例--- 29

5.1       概述-- 29

5.2       使用C语言开发LDAP客户端-- 30

5.2.1        程序示例-- 30

5.2.2        搜索资料-- 31

5.2.3        目录树的增加,删除和修改-- 33

5.3       LDAPPHP的集成-- 36

5.3.1        使php支持LDAP- 36

5.3.2        注意事项-- 37

5.3.3        连接以及绑定程序示例-- 37

5.3.4        搜索信息-- 38

5.3.5        增加条目-- 39

5.3.6        修改条目-- 40

5.3.7        删除条目-- 40

第六章   总结和展望--- 42

参考文献--- 44

致谢--- 45

 

第一章   引言

从计算机的发明到现在已经经历了大约半个世纪。伴随着计算机技术以及随之而来的网络技术的飞速发展,人类社会在各个方面都取得了飞速的发展。在今天,特别是伴随着Internet的普及,计算机网络以令人难以置信的速度深入人心,应用于各个部门,在加速了生产力发展的同时,也改变了人们的生活方式。

由于资料存储量的大幅度提高和网络应用技术的日趋成熟,各种信息系统在经济生产部门和社会服务部门的应用也越来越广泛。基于网络技术可以进行资料共享和分布式计算的特点,人们的工作效率得到了极大的提高。在这同时,信息量也呈现爆炸式的增长,为了尽量有效的利用信息,并且避免数据冗余(Data redundancy),资料的组织成为了一项重要的问题。

在今天,我们看到已经有不少的资料组织方式。应用最为普及的应该属于关系数据库(RDBRelational Database)。而在另一个方面,由于应用目的不同,在许多方面我们需要其它的资料存储和组织方式,目录服务(Directory Service)就是其中的一种。而LDAP协议(Lightweight Directory Access Protocol)就是作为一种访问目录服务的协议而存在的。

通过使用LDAP协议,我们可以极为方便的在网络上对诸如一个单位的组织结构,个人档案,甚至是地理位置等进行方便的搜索。而由于LDAP协议可以通过网络协同工作的特点,这些资料可能是存在于多个不同的服务器上面的,我们并不需要知道所有的这些服务器的确切的位置,通过LDAP协议,在访问者看来,他们所访问的应该是通过同一个接口。

LDAP能够使得企事业单位更加方便的组织自己的各种信息,比如,人事信息,在另外一个方面,在Internet发展越来越迅速的今天,ICPISP的数量也与日俱增,对于诸如门户网站这样的ICP以及提供各种互联网接入的提供者ISP来讲,他们一般都为大量的用户进行服务,而在这些用户的档案的存储以及访问方面,LDAP可以得到相当广泛的应用。

与传统的DAPDirectory Access Protocol)相比,LDAP正如它的名字本身体现出来的特点那样,是更为轻量级,实现更加容易的目录访问协议,这样的特点使得LDAP迅速成为了目录访问的标准。在现在,诸如Yahoo!NetscapeBigfoot这样的网站都提供基于LDAP的目录服务。

第二章   目录服务及LDAP协议

2.1          目录服务的基本概念

目录服务是指通过网络提供用于访问目录的一种服务。要理解目录服务,首先必须了解目录的概念。

目录描述的是一组具有一种或者多种属性的对象,这里的对象所指的范围非常广阔,一个人,一辆车,一台打印机,都可以作为目录中所描述的对象。而对象的范畴也绝对不限于现实世界中,很多抽象的概念也可以作为对象加以描述。

目录是一种用于资料存储的数据库,它存储经过排序和分类的资料。它允许人们或者是各种程序对具有一定特征的资料资源进行查询。举例来讲,人们可以通过存储个人信息的目录来查询某人的电话号码,再比如一个具有局域网的公司中,人们可以通过存储打印机信息的目录来查询最近的打印机。

目录的访问方式有些类似于在电话的黄页中查找一个人的电话号码,查找的条件可以是比较模糊的,当然可以预料的是越模糊的查询必然会导致得到的结果越多。对于存储了海量信息的目录来讲,如何进行查询以便得到最为精确的结果就成为了一个问题。

与一般的数据库,比如关系数据库,网状数据库相比,目录具有以下的特点:

l        目录更容易被读取而不是是被访问,因此目录访问中读取记录操作的数量要比写入记录操作的数量多得多

l        目录的扩展性更好

l        目录更加适合于分布式的网络

l        目录可以更加方便的进行大量数据的复制

l        对于目录相关的各种行为更多的依赖于各种国际标准,而这是传统数据库所不具备的

目录中存储的数据一般呈现出一种树状的结构,这样的一种结构使得读取数据成为了目录访问的主要目的。虽然也可以进行写入,但是如果有大量的数据经常的被写入,那么存储途径还是使用传统的数据库,诸如OracleDB2等更好。

与文件系统相比较,在各种文件系统中,文件一般都会比较大,有的文件可能有几百兆,甚至会更大。而目录中存储的信息都会被划分为极小的单位。

2.2         LDAP协议的历史

1998年,CCITT(国际电话与电报咨询委员会,Consultative Committee on International Telephony and Telegraphy)制定了X.500标准,描述了目录服务的目录结构,命名方法,搜索机制,以及用于客户机和服务器之间通信的协议DAP。在1990年,这个标准被ISO(国际标准化组织,International Organization for Standardization)所引用,编号为ISO 9594但是,在实际应用的过程中,X.500存在着不少障碍。作为一种应用层的协议,DAP需要完整的 OSI(开放式互联系统参考模型Open System Interconnect Reference Model)协议栈,对相关层协议环境要求过多,在许多小系统上无法使用,TCP/IP协议体系的普及更使这种协议越来越不适应需要。在这种情况下,轻量级的DAP,也就是LDAP应运而生。LDAP最早是由密歇根大学(University of Michigan)研发的。与X.500相比较,LDAP需要的是实现更加简单,也更加流行的TCP/IPTransmission Control Protocol/Internet Protocol)协议栈。

早期设计的LDAP服务器不是独立的目录服务器,主要扮演LDAP客户机与X.500服务器间网关的角色,既是LDAP的服务器又是X.500的客户机。如今的LDAP服务器可取代X.500服务器而独立提供服务。

最早的LDAP协议规范是RFC 1497,它出现在1993年。仅仅过了没有多久就出现了LDAP的第二版,这一版本也被称为U-M LDAP,其中U-M代表密歇根大学(University of Michigan)。第二版的LDAP规范增加了包括RFC 1777RFC 1778RFC 1558RFC 1779RFC 1798RFC 1823RFC 1959

1999年,出现了LDAP第三版。于第二版相比,第三版增加了国际化的支持,LDAP v3支持UTF-8Unicode Transformation Format-8编码。同时支持SASLSimple Authentication and Security Layer)和TLSTransport Layer Security),从而从传输层确保了LDAP的安全性。而与LDAP相关的RFC也增加了RFC 1274RFC 2079RFC 2247RFC 2251RFC 2252RFC 2253RFC 2254RFC 2255RFC 2256RFC 2293RFC 2284RFC 2307RFC 2377RFC 2587RFC 2589RFC 2596RFC 2649RFC2696RFC2713RFC2714RFC2798RFC2829 RFC2830RFC2849RFC2891RFC3045RFC3062RFC3088RFC3112RFC3296RFC3377RFC3383

2.3         LDAP协议的访问以及数据组织

TCP/IP体系中,LDAP位于会话层(Session Layer一般来讲, DSADirectory Serverce Agent)将在389端口进行监听,而基于SSL的的LDAP DSA将在636端口进行监听。接受来自客户机的消息

LDAP目录服务式基于客户机/服务器模式的。一个或者多个LDAP服务器构成了DIT(目录信息树,Directory Information Tree)。客户机向服务器发出一个查询的请求,而服务器会根据客户的请求查询自己存储的资料,如果查询到的话则向客户机返回结果,如果没有查询到但是发现所查询的资料存储在另外一台服务器上的时候则返回后者的地址。

LDAP目录信息的基本单位是条目(entry),每个条目可以看作是一组属性的集合,不同的条目使用DNDistinguished Name)进行区别,每个条目的DN都是全局唯一的,所以不会引起重复的问题。条目的每个属性有一个型(type)和一个或者多个值(value)。一般来讲,属性的型都是一些具有现实意义的字符串,比如使用“common name”的缩写“cn”代表全名,用“mail”代表email地址。而属性值的类型取决于属性的型。比如cn的值可能是“Andy Lau”或者“Jay Chou”,而mail的值可能就是ljabc@mailei.xjtu.edu.cn。而对于诸如jpegphoto这样的属性来讲,它的值可能会是二进制的图片。

正如在前文中所提到的那样,LDAP中目录实体的组织是一种分层次的树状结构。这种树状结构通常和现实当中的关系有着很大的联系。在下页图一中描述了一个根据地理层次设计的目录树。从根开始,最上层的分支有两个,表示两个国家,用c表示国家名,而国家名一般都用缩写。如在本图中c=GB代表英国,而c=US代表美国。在第二层中st=California则代表加利福尼亚州。第三层的o=acme代表了一个组织名,o在这里代表Organization。第四层的ou=Sales等则代表该组织下属的组织单元。而最末一层则是一个人名。

在图二中,我们则看到了一个以Internet域名作为起始点的目录树,从第一层的dc=com,dc=net等开始,一直到最下层的以uid标示的一个用户名。这样的目录树在LDAP被用作与Internet相关的内容,比如作为身份认证系统,或者是作为后台的用户信息存储资料库时候,变得极为有用。Internet的迅猛发展也使得这种结构的目录树更加的流行。

图一 根据地理层次设计的目录树

图二 以域名作为起始点的目录树

 

每个条目都有自己的名字,比如uid=Jane这就是一个条目的名字,又如c=US,这样的名字被成为RDNRelative Distinguished Name)而在前文中提到过,条目必须使用DN作为唯一的标示符。条目的DN等于该条目的上层条目DN加上本身的RDN。如果RDNuid=Jane的条目的上层条目DN

ou=people,dc=example,dc=com

那么它本身的dn就是:

uid=Jane,ou=people,dc=example,dc=com

2.4         LDAP URL

LDAP标准中URLUniform Resource Locator)的定义与其他各种URL,比如WebURL很相似,如果LDAP DSA允许进行匿名搜索的话,那么用户可以使用浏览器通过LDAP URL向服务器进行查询,而服务器将相应这个查询,并且返回一个html页面说明查询结果。

一个LDAP URLUniform Resource Locator)形式如下所示:

ldap[s]://hostname:port/base_dn?attributes?scope?filter

其中ldap:// 协议用于通过非安全连接来连接 LDAP 服务器,而 ldaps:// 协议则用于通过 SSL 连接来连接 LDAP 服务器。其他各项的含义如同下表所示:

字段

含义

Hostname

LDAP 服务器的名称(或用点分隔的 IP 地址)。例如:

ldap.example.com 192.202.185.90

port

LDAP 服务器的端口号(例如 389)。 如果未指定端口,则使用标准 LDAP 端口 (389) LDAPS 端口 (636)

base_dn

目录中条目的基准DN。也就是将此 DN 标识作为搜索起始点的条目。

如果未指定基准DN,搜索过程就会从目录树的根开始。

attributes

所要返回的属性。要指定多个属性,请使用逗号来分隔属性,如("cn,mail,telephoneNumber")。

如果 URL 中未指定属性,则返回所有属性。  

Scope

搜索范围,可以取下列值之一:

base 将仅检索有关 URL 中所指定的以基准dn标识的条目的信息。

one 将检索有关 URL 中所指定的基准dn之下一级条目的信息。基准dn标示的条目并不包含在该范围内。

sub 将检索有关 URL 中所指定的基准dn 下属各级条目的信息。基准dn包含在该范围内。

如果未指定范围,服务器就会执行 base 搜索。

 

 

filter

应用于指定搜索范围内条目的搜索过滤器。

如果未指定过滤器,服务器就会使用过滤器 (objectClass=*)  

表一 LDAP URL中各项含义

举例来讲,一个完整的LDAP URL如下所示:

ldap://192.168.1.1/ou=person,dc=example,dc=com? "cn"?sub?(sn=Jane)

attributesscopefilter都以他们在URL中出现的顺序进行标识,如果不想指定任何属性,仍然需要使用问号来分隔界定该字段。

例如下面这个URL

ldap://192.168.1.1/dc=example,dc=com??sub?(sn=Jane)

其中attributes字段没有指明,但是仍然用两个连续的问号表示采用默认值。即返回所有属性。

URL 中的所有非安全字符都需要用特定的字符序列来表示。该过程称为非安全字符的转义。 例如,空格就是一个非安全字符,在 URL 必须表示20%。举例来讲,一个过滤器为(cn=Jay Chou),那么其中的空格必须转换为转义字符,也就是变成(cn=Jay20%Chou)

LDAP URL中的转义字符与HTTP URL中的转义字符是完全相同的。

2.5         访问控制

对于任何一种网络数据库来讲,都不可能允许客户在匿名的情况下随意的篡改和访问所有记录。LDAP服务器也是这样。LDAP的身份认证,并不需要外部的用户名或者其他的辅助。LDAP利用本身的dn来进行权限的管理。

对于一个条目或者是一个子树来讲,它可能会被有增加,删除和修改和访问的四种操作。LDAP服务器使用ACLAccess Control List)来进行访问控制。使用访问控制可以控制对整个目录、目录的子树、目录中特定条目(包括定义配置任务的条目)或特定条目属性集的访问权。您可以设置特定用户、所有属于特定组或角色的用户或所有目录用户的权限。最后,您还可以定义对特定位置(例如 IP 地址或 DNS 名称)的访问权。

为了确定客户端是否有进行它所请求操作的权限,LDAP 客户端在访问服务器的时候,服务器都会要求客户端使用一个条目的dn和密码进行认证。如果客户端并没有提交这样的dn和密码,那么服务器默认为匿名访问。一般来讲,匿名访问可以进行一些搜索,但是得不到所有的信息。

在上一段中所指的密码是作为该dn标示的条目的一个属性出现的。LDAP允许对目录树中的每种属性设定可操作的对象,比如password属性一般设定为仅有条目所表示的用户本身和超级用户(可能用另外一个dn表示)可以修改。

在第四章中,本文将结合Openldap服务器软件更加详细的阐述用户身份的认证。

第三章   LDAP服务器

3.1          概述

目前可用的LDAP服务器有很多,比较多的被应用的有OpenldapMicrosoft Exchange Server,和iPlanet Directory Server。在前文中我们已经提到。目录以及LDAP协议的行为都有着很多的标准进行规范。这也就使得目前的各种服务器在功能上并无大的差异。在所提到的三种服务器中,Openldap是自由软件,而后面两种则是商业软件。

本文将在第四章介绍Openldap的配置。

从目前来看,三种服务器软件都取得了各自的成功,在Unix以及它的各种相关操作系统中,比如在AIXSolarisLinux这样的平台中,Openldap被极为广泛的应用。因为Openldap并不只是一个免费的软件,它更有着商业级的表现。而在Windows平台中,则Exchange 有着较多的应用。而iPlanet作为一个跨平台的产品,在各种操作系统中都有被广泛的应用。则在由于Exchange ServeriPlanet本身并不只是提供LDAP服务器,而且还作为其他网络服务的服务器,所以用户或者企业在考虑部署LDAP服务器时候一般都要考虑到是否要同时使用该种软件作为其他网络服务的服务器。

3.2         LDAP服务器的部署

对于目录服务的配置,可能会有多种可供选择的方案,而如何配置必须视实际要求而定。如企业级的服务需要更加稳定和更高的性能,那么在这样的目的下,多台服务器组成一个分布式的LDAP服务器群就可以被考虑,而数据备份也是很重要的。而如果目录服务在规划中并不是极为重要必需的一环,从节约成本的考虑,则没有必要进行过于复杂的配置。

3.2.1        单服务器的目录服务

指仅仅使用一台服务器作为LDAP服务器,客户机通过这台服务器的地址进行访问。单服务器的目录服务特点是实现简单,因为不需要购买很多的服务器,所以成本很低。缺点是单个服务器本身能力有限,包括存储容量和网络带宽方面都会受到很多的限制,而且在数据备份方面有着天生的弱点,但是这一方面可以使用据如RAIDRedundant Array of Independent Disk)等技术进行相应的弥补。

3.2.2        使用服务器群作为LDAP服务器

单服务器作为LDAP服务器的缺点是显而易见的。为了提供高效的LDAP服务,使用多个服务器连接成为一个服务器群提供LDAP服务则是一个比较好的办法。

配置两台相同内容的LDAP服务器是不合理的。有的人希望能够在很多台服务器上配置相同的目录树,然后通过DNS轮询和网络均衡技术使得服务器的性能能够达到最大。也有一种看法,即是由于LDAP可以作为多种网络服务的认证工作,他们可能希望能够通过多台完全相同的目录服务器承载不同服务的认证,但是为了用户数据的一致性,又希望使用完全相同的目录树。的。其实这两种做法是最浪费资源和效率的一种方式,并没有必要这样做。这样一方面导致数据冗余,另一方面,如果服务器的资源需要被经常更新的话,那么就有一个数据同步的问题。而我们知道,没有一种同步程序可以使得多台服务器在同时具有完全相同的数据。

而且对于同步程序来讲,由于LDAP本身并不具有日志记录的功能。什么记录在什么时间被更新,仅仅依靠同步程序来解决显然是不够的,而且极有可能因此出现数据的错误

举例如下:

服务器AB都具有一个dnuid=Jay,dc=example,dc=com的条目,它的属性包括mailaddress等。现在Jay本人希望能够更改自己的邮件地址和住址,通过dns轮询技术,或者是不同的web接口,在很短的时间内,A服务器上Jay的资料中mail被修改了而address没有变,而B服务器上address被修改了,这样在同步程序希望进行同步时候它就无法信赖任何一个服务器。

当然也有一个办法就是通过一个另外的比较服务器,比如增加一台服务器C,在每次同步程序同步以后,在C上面备份所有的数据,在下一次比对时候,与C不同的数据则认为是已经更新的数据。但是这样仍然是不可取的。试想一下,如果JayA上修改了自己的mail以后突然发现自己输入时候有错误,于是在B上又进行了一次修改,那么在同步程序进行比对时候,就会发现ABC三台服务器上的数据中任何两个都不相同,从而无法进行下面的工作。

当然,我们可以通过第三方程序或者其他办法为条目的修改增加日志,但是这样大大提高了系统的复杂度,而且并没有达到当初所设想的提高性能的目的。在大型的应用中,面对着海量的数据与巨大的访问量,这样的做法是极为不可取的。

LDAP服务器群实施中比较合理的方案是通过引用技术和推的技术,提高数据访问和稳定性。

引用技术是指在客户机查看特定信息时通知客户机另外的服务器。如果客户机应用程序所请求的目录项在本地服务器上不存在,或者数据库已进行脱机维护,就会出现该重定向现象。如下图三所示

 

 

 

 

 

 

 

 


图三

LDAP允许对特定的目录树进行引用。这使得我们可以在多台服务器上布置平行的目录树或者分层的目录树。而具体到使用平行或者分层,则应该视所存储资料被访问的概率而定。而确定的原则是尽量提高用户查询的命中率,同时使得流量平衡,不会出现网络服务的堵塞。我们仍然可以通过使用CacheDNS轮询和流量均衡的技术,使得服务器的效率达到最大。比如可以通过LSANTLoad Sharing Network Address Transfer)将多台服务器网卡的不同IP地址翻译成一个VIP(Virtual IP)地址,这样方便了用户的访问,也使得网络流量更加平衡。

LDAP服务器数据的备份也是一个重要的问题。目前大多数LDAP服务器都具有非常完备而且实现简单的备份功能,这里主要使用了推的技术。即定时的将数据服务器上的目录树整体转移到备份服务器中去,由于备份服务器并不提供对客户的服务,所以并不需要担心网络流量的问题。如果对目录数量估算恰当的话,备份服务器的数量可以远小于数据服务器的数量。

对于LDAP服务器来讲,必要的安全措施是不可以被忽视的。特别是对于访问量巨大的服务器,除了要通过ACL进一步完备LDAP的用户认证以外,对诸如DOSDenial of Service)特别是DDOSDistributed Denial of Service)之类的攻击采取必要的防范措施也是必须的,这就要求必须有完备的防火墙。

3.3         设计LDAP目录树

对于任何一个希望部署LDAP服务的企业或者其他单位来讲,如何设计自己的目录树是一个极为重要的问题。好的目录树是能够满足需求并且扩展性好的目录树,事先设计一个好的目录树可以减少后期的维护量,而一个糟糕的目录树则可能使得在后期使用中问题层出不穷。甚至可能会使得所有的事情推倒重来。

如果LDAP只是用作用户认证的数据库的话,这一方面的目录树基本上都沿用一个模式,即使用uid作为标识,配合以各种属性。值得注意的一点是,如果LDAP同时被用来进行几种网络服务的认证服务,并没有必要为每一种网络服务配备不同的目录树。

同时LDAP的目录树要避免过度的复杂,特别是必须牢记LDAP是为了读而不是为了写而设计的,所以对于一些可能导致大量写操作的属性,就必须考虑是否有必要将它作为条目的属性之一。

企业对LDAP的应用中,作为地址簿的服务器是一个重要的功能。习惯了关系数据库的企业可能会将部门作为一个子树,将员工作为一个子树,然后将部门名作为员工条目的一个属性。这样的做法其实是有缺点的。这样的话如果部门被变更,那么就必须到员工子树中查找所有的该部门员工,然后逐一修改。但是如果我们将员工条目作为部门的下一级条目的话,这样的修改就容易得多,因为这个时候可以对整个部门为跟节点的子树进行操作。查找的一步就可以免掉了。

总之,在设计LDAP目录树的时候,必须牢记LDAP是一种树状的结构,我们也应该尽量按照这种结构进行设计,避免将树状的结构变成一种网状的结构。

第四章   Openldap服务器配置

4.1          Openldap项目简介

Openldap项目是由非盈利性质的Openldap基金所支持的,它的目的是发展一个健壮的,功能完备的商业级LDAP服务器。最早的Openldap 1.0发布于19988月,其后经过不断完善,已经成为各种平台下最为流行的LDAP 服务器之一。而最近的一个版本是20027月发布的Openldap 2.2

在本章中将介绍如何配置一个Openldap服务器,而在第五章中,将以Openldap为例介绍如何使用C语言编写一个Openldap客户端和在php中如何应用LDAP

关于Openldap更详细的情况,可以访问http://www.openldap.org

4.2         安装Openldap服务器

4.2.1        获取软件

Openldap是一个自由软件,所以可以从网上免费获取该软件。下载的地址是http://www.openldap.org/software/download/,也可以直接从FTP下载:ftp://ftp.openldap.org/pub/OpenLDAP/.

在文件被下载以后,还必须首先进行解压缩,然后进入到解压以后的目录中:

tar -zxvf openldap-VERSION.tgz

cd openldap-VERSION

其中VERSION指版本号。

4.2.2        前期的准备

Openldap需要一些第三方的软件包提供相应的功能。其中很多软件包已经在操作系统中被包扩了。需要哪些软件包必须用户需要的功能的情况而定。其中必要的有OpenSSLKerberos认证服务,Berkley DB v4等。至于C编译器等则是必须的。

4.2.3        配置安装选项

与一般的开源软件安装一样,在安装前都需要对安装脚本进行配置。配置脚本的格式为:

[[env] settings] ./configure [options]

options中的选项可以通过

./configure –help

得到

其中settrings表示环境标量的设定,如果不设定则脚本会用操作系统中相应的环境变量进行代替,环境变量如下表所示:

环境变量

含义

CC

C编译器的位置

CFLAGS

编译器编译时的环境变量

CPFLAGS

C预编译器标志

LDFLAGS

Link标志

LIBS

指定附加的库

举例来讲,一个完整的脚本命令如下所示:

env CPPFLAGS="-I/usr/local/include" LDFLAGS="/usr/local/lib" \

./configure --with-wrappers

但是在大多数情况下,并不需要设定这样多的选项,只需要使用默认命令

./configure

就可以了。

4.2.4        编译软件

一般来讲,在上一步中配置脚本会检查安装的服务器中是否存在所有编译时候需要的软件包,如果其中需要的软件包没有被安装,那么将会给出一个错误提示,在这种情况下,不能够继续进行编译,而是必须安装需要的软件包,然后重新进行脚本的设定。

如果上一步中配置脚本检查成功,那么就可以着手进行软件的编译工作。

通过以下的命令进行编译:

make depend

make

在执行这两个命令之后,必须仔细检查控制台所输出的信息,如果其中出错,那么可以根据出错信息进行相应的操作。

4.2.5        测试软件

在编译完成之后,就可以测试软件是否可以正常工作。

make test

4.2.6        安装软件

如果前面的工作一切顺利,那么就可以安装Openldap了。值得注意的是,Openldap的安装要求用户必须为超级用户,也就是root。如果此时用户登陆的身份不是root,那么可以执行以下命令:

su root –c “make install”

如果安装成功的话,那么Openldap将被默认安装在/usr/local

4.3         服务器的启动和关闭

slapdOpenldapDSA。默认的安装位置在/usr/local/libexec

在启动服务器之前必须正确的书写配置文件slapd.conf,关于slapd.conf的写法请参考4.4节。

slapd启动的命令行如下:

/usr/local/etc/libexec/slapd [<options>]

其中options的各种参数可以通过一下命令行得到帮助:

/usr/local/etc/libexec/slapd –-help

而停止守护进程可以用以下命令:

kill -INT `cat /usr/local/var/slapd.pid`

4.4         slapd配置文件

slapd配置文件slapd.conf默认位置在/usr/local/etc/openldap/slapd.conf

配置文件是否能够被正确的书写,是slapd能否成功启动的关键。在这个文件中,所有的语句分为三种类型:全局信息,后台信息和数据库信息。

slapd.conf中,以“#”开头的命令是作为注释出现的,和空行一样,都不起任何作用。

一般来讲,slapd.conf的格式如下所示:

 

       # 全局配置命令

        <global config directives>

 

        # 后台定义

        backend <typeA>

        <backend-specific directives>

 

        # 第一个数据库定义和配置命令

        database <typeA>

        <database-specific directives>

 

        #第二个数据库定义和配置命令

        database <typeB>

        <database-specific directives>

 

        # 其他数据库定义

        ...

下面给出一个slapd.conf配置的示例:

1. # example config file - global configuration section

2. include /usr/local/etc/schema/core.schema

3. referral ldap://root.example.com

4. access to * by * read

5. # BDB definition for the example.com

6. database bdb

7. suffix "dc=example,dc=com"

8. directory /usr/local/var/openldap-data

9. rootdn "cn=Manager,dc=example,dc=com"

10. rootpw secret

11. # replication directives

12. replogfile /usr/local/var/openldap/slapd.replog

13. replica host=slave1.example.com:389

14.        binddn="cn=Replicator,dc=example,dc=com"

15.        bindmethod=simple credentials=secret

16. replica host=slave2.example.com

17.        binddn="cn=Replicator,dc=example,dc=com"

18.        bindmethod=simple credentials=secret

19. # indexed attribute definitions

20. index uid pres,eq

21. index cn,sn,uid pres,eq,approx,sub

22. index objectClass eq

23. # database access control definitions

24. access to attr=userPassword

25.  by self write

26.  by anonymous auth

27.  by dn.base="cn=Admin,dc=example,dc=com" write

28.            by * none

29.    access to *

30.            by self write

31.            by dn.base="cn=Admin,dc=example,dc=com" write

32.            by * read

其中前面的顺序号是为了解释方便而加上的,在实际的slapd.conf文件中,并没有这些数字。

其中第1行是注释。第2行表示包括了另外一个配置文件/usr/local/etc/schema/core.schemaschema文件是用来定义各种条目类别及其所需要类别的文件。关于schema的详细资料,可以参考相关的RFC

3行提出了一个引用,即在本服务器上的数据库查询不到数据的情况下,客户端的请求将被重定向到所引用的地址。第4行是一个全局访问控制指令,即如果不作特别说明的话,条目的属性均可被读取。

第一到第四行均为全局指令。

5行开始了数据库指令。第6行指明数据库类型为bdb,即Berkeley DB。第7行指出数据库的后缀,第8行指明了数据库将要被存放的目录。第9行指出数据库的根dn。第10行是根dn进行访问的口令。第12行定义日志文件将要被写入的文件。第1315行定义了一个用来作为复制主机的访问dn以及口令。当本服务器上数据有所变更时,将把所变更的数据复制到所定义的主机上。1618行则定义了第二个用来作为数据复制的主机。

20行到第22行定义了索引的特性。LDAP中的索引有三种,分别是存在索引(pres),等同索引(eq),近似索引(approx

24行到第32行定义了对一些属性诸如密码的访问控制。

第五章   LDAP程序开发实例

5.1          概述

本章将通过一些实例来具体阐述如何进行LDAP开发。而针对这个主题将分为两个部分进行阐述,一是客户端的开发,另一方面是Web开发。在客户端方面,本文将以C语言为例,而在Web开发方面,将以php为例。

由于LDAP在传输层是通过有连接的TCP或者TLS作为基础的。所以与大多数基于虚电路的网络程序一样,LDAP客户端的操作基本上如图五所示:

 

 

 

 

 

 

 

 

 

 

 

 

 


图五

 

在图五中,任何一种操作中出错一般都会导致处理中断。

而对于LDAP来讲,一般的程序流程是首先进行客户端的初始化工作,然后连接到服务器并且进行绑定,而这里的绑定一般都有一个用户认证的问题。在绑定之后即可进行各种操作,比如搜索,数据的更新等。然后取消绑定,断开连接,程序进行其他工作以后结束。

5.2         使用C语言开发LDAP客户端

C语言开发LDAP客户端必须使用一系列的API。在使用C语言进行LDAP客户端开发时要包含头文件ldap.h,在编译时候必须包含LDAP库即LLDAP

5.2.1        程序示例

下面提供一个最简单的程序:

#include <stdio.h>

#include <ldap.h>

main (int argc, int **argv) {

LDAP *ld;

int rc = 0;

printf("Test de connexion Ldap en C\n");

/* intialize a connection */

if ((ld = ldap_init("example.com", LDAP_PORT) )== NULL)

{

 perror ("ldap_init");

}

printf("Connected to the ldap Server\n");

// Bind to a server

rc = ldap_simple_bind_s(ld,"cn=admin,dc=example,dc=com","password");

if (rc != LDAP_SUCCESS)

{

 fprintf(stderr,"Ldap error: %s\n", ldap_err2string(rc));

}

 else

{

 printf("Bind OK!\n");

}

// unbind

if ( ldap_unbind(ld) != LDAP_SUCCESS )

{

 perror("ldap_unbind");

 }

}

从这个程序中可以看到一般ldap程序的流程。但是在这个程序中并没有实现什么具体的功能。

5.2.2        搜索资料

LDAP中最重要的查找资料的功能可以通过ldap_search_ext_s()或者ldap_search_ext()函数实现,其中前者是同步的而后者是异步的。而得到的结果可以通过ldap_result()函数等实现。在下文的示例程序中,将看到一个查找后得到结果条目的全部dn的例子:

#include <stdio.h>

#include <ldap.h>

……

LDAP *ld;

LDAPMessage *result, *e;

char *dn;

char *my_searchbase = "o=example.com";

char *my_filter = "(sn=Jay)";

//Connect to the Server,Bind…….

... 

/* Search the directory. */

if ( ldap_search_s( ld, my_searchbase, LDAP_SCOPE_SUBTREE, my_filter,

      NULL, 0, &result ) != LDAP_SUCCESS ) {

  ldap_perror( ld, "ldap_search_s" );

  return( 1 );

}

/* For each matching entry found, print the name of the entry.*/

for ( e = ldap_first_entry( ld, result ); e != NULL;

      e = ldap_next_entry( ld, e ) ) {

  if ( ( dn = ldap_get_dn( ld, e ) ) != NULL ) {

    printf( "dn: %s\n", dn );

    /* Free the memory used for the DN when done */

    ldap_memfree( dn );

  }

}

/* Free the result from memory when done. */

 

ldap_msgfree( result );

//Release the connection and exit from the program

………

为了更加简明扼要的说明问题,诸如连接,绑定这样的操作都被省略了。

5.2.3        目录树的增加,删除和修改

虽然LDAP本身被设计为一种读多写少的服务器,但是LDAP中的写操作也是不可缺少的。

添加条目的函数有ldap_add_ext()ldap_add_ext_s()等。修改条目的函数包括ldap_modify_ext()ldap_modify_ext_s()等。而同样的,可以通过ldap_delete_ext()ldap_delete_ext_s()等函数来实现条目的删除操作。下面给出了一个用来添加条目的完整程序

#include <stdio.h>

#include "ldap.h"

/* Change these as needed. */

#define HOSTNAME "example.com"

#define PORTNUMBER LDAP_PORT

#define BIND_DN "cn=Manager,dc=example,dc=com"

#define BIND_PW "secret"

#define NEW_DN "uid=wbjensen,ou=People,o=airius.com"

#define  NUM_MODS 5

int

main( int argc, char **argv )

{

LDAP           *ld;

LDAPMod        **mods;

char           *matched_msg = NULL, *error_msg = NULL;

int             i, rc;

char *object_vals[] = { "top", "person", "organizationalPerson", "inetOrgPerson", NULL };

char *cn_vals[] = { "Jay Chou",  "Zhou Jielun", NULL };

char *sn_vals[] = { "Jay", NULL };

char *givenname_vals[] = { "Jay", NULL };

char *telephonenumber_vals[] = { "029-2670443", NULL };

/* Get a handle to an LDAP connection. */

if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) {

    perror( "ldap_init" );

    return( 1 );

}

 

/* Bind to the server as the Directory Manager. */

rc = ldap_simple_bind_s( ld, BIND_DN, BIND_PW ); 

  if ( rc != LDAP_SUCCESS ) {

    fprintf( stderr, "ldap_simple_bind_s: %s\n", ldap_err2string( rc ) );

    ldap_get_lderrno( ld, &matched_msg, &error_msg );

    if ( error_msg != NULL && *error_msg != '\0' ) {

    fprintf( stderr, "%s\n", error_msg );

    }

    if ( matched_msg != NULL && *matched_msg != '\0' ) {

      fprintf( stderr,

        "Part of the DN that matches an existing entry: %s\n",  matched_msg );

    }

 

    ldap_unbind_s( ld );

    return( 1 );

}

/* Construct the array of LDAPMod structures representing the attributes

   of the new entry. */

mods = ( LDAPMod ** ) malloc(( NUM_MODS + 1 ) * sizeof( LDAPMod * ));

if ( mods == NULL ) {

    fprintf( stderr, "Cannot allocate memory for mods array\n" );

    exit( 1 );

}

 

for ( i = 0; i < NUM_MODS; i++ ) {

    if (( mods[ i ] = ( LDAPMod * ) malloc( sizeof( LDAPMod ))) == NULL ) {

      fprintf( stderr, "Cannot allocate memory for mods element\n" );

      exit( 1 );

    }

}

mods[ 0 ]->mod_op = 0;

mods[ 0 ]->mod_type = "objectclass";

mods[ 0 ]->mod_values = object_vals;

mods[ 1 ]->mod_op = 0;

mods[ 1 ]->mod_type = "cn";

mods[ 1 ]->mod_values = cn_vals;

mods[ 2 ]->mod_op = 0;

mods[ 2 ]->mod_type = "sn";

mods[ 2 ]->mod_values = sn_vals;

mods[ 3 ]->mod_op = 0;

mods[ 3 ]->mod_type = "givenname";

mods[ 3 ]->mod_values = givenname_vals;

mods[ 4 ]->mod_op = 0;

mods[ 4 ]->mod_type = "telephonenumber";

mods[ 4 ]->mod_values = telephonenumber_vals;

mods[ 5 ] = NULL;

/* Perform the add operation. */

 

rc = ldap_add_ext_s( ld, NEW_DN, mods, NULL, NULL );

  if ( rc != LDAP_SUCCESS ) {

    fprintf( stderr, "ldap_add_ext_s: %s\n", ldap_err2string( rc ) );

    ldap_get_lderrno( ld, &matched_msg, &error_msg );

    if ( error_msg != NULL & *error_msg != '\0' ) {

      fprintf( stderr, "%s\n", error_msg );

    }

    if ( matched_msg != NULL && *matched_msg != '\0' ) {

      fprintf( stderr,"Part of the DN that matches an existing entry: %s\n", matched_msg );

    }

} else {

    printf( "%s added successfully.\n", NEW_DN );

  }

  ldap_unbind_s( ld );

  for ( i = 0; i < NUM_MODS; i++ ) {

    free( mods[ i ] );

  }

  free( mods );

  return 0;

} 

由于篇幅原因,本文无法对LDAP API函数进行一一讲解,关于这些函数调用方法更详细的资料可以参阅Openldap的系统调用手册。

5.3         LDAPPHP的集成

LDAP作为数据服务器或者用户认证信息服务器与Web结合,一直是近年LDAP应用的一个热点。一方面,这种模式的应用在做诸如电子黄页这样的网站中显示出极高的效率,另一方面,通过LDAP进行用户的身份认证更快,同时也更加能够方便的加以利用。本节主要介绍在Linux平台下,如何在PHP中使用各种LDAP的功能。

5.3.1        使php支持LDAP

目前在市场上占主导地位的几种Linux操作系统均在安装时可以选择安装php,这样的话就不需要另外进行安装。当然我们也可以通过下载php的源代码进行编译后实现所需要的功能。

值得注意的是,为了使得php支持LDAP各项功能,在安装编译php之前运行配置脚本的时候,必须加参数 –with-ldap

一个简单的编译和安装php如下所示:

tar -zxvf php-4.3.2.tar.gz

cd php-4.3.2.tar.gz

./configure --with-ldap

make

make install

5.3.2        注意事项

如果已经安装了php,那么可以根据需要修改php.ini中这个变量的值:

ldap.max_links 表示可以进行ldap连接的最大数量

它的默认值为-1,如果修改为 PHP_INI_SYSTEM则为可变值

由于LDAP v2LDAP v3不同,所以在使用之前务必要分清使用的LDAP版本。Php 4默认支持LDAP v2,如果使用的是LDAP v3的话则必须在程序进行连接以后添加以下语句:

ldap_set_option($ds,LDAP_OPT_PROTOCOL_VERSION,3)

其中dsldap_connect()函数返回的值。

否则的话则会在绑定时出错。

5.3.3        连接以及绑定程序示例

下文展示了一个最为简单的php程序:

<?php

echo "<h3>LDAP query test</h3>";

echo "Connecting ...";

$ds=ldap_connect("localhost");  // 进行连接

ldap_set_option($ds,LDAP_OPT_PROTOCOL_VERSION,3)

echo "connect result is ".$ds."<p>";

if ($ds) {      //连接成功

    echo "Binding ...";

    $r=ldap_bind($ds);     // 匿名的绑定,只具有读权限

                          

    echo "Bind result is ".$r."<p>";

    echo "Searching for (sn=S*) ...";   //进行搜索

    // Search surname entry

    $sr=ldap_search($ds,"cn=test,cn=example,cn=com", "sn=S*"); 

    echo "Search result is ".$sr."<p>";

    echo "Number of entires returned is ".ldap_count_entries($ds,$sr)."<p>";

    echo "Getting entries ...<p>";

    $info = ldap_get_entries($ds, $sr);

    echo "Data for ".$info["count"]." items returned:<p>";

    for ($i=0; $i<$info["count"]; $i++) {

        echo "dn is: ". $info[$i]["dn"] ."<br>";

        echo "first cn entry is: ". $info[$i]["cn"][0] ."<br>";

        echo "first email entry is: ". $info[$i]["mail"][0] ."<p>";

    }

    echo "Closing connection";

    ldap_close($ds);

} else {

    echo "<h4>Unable to connect to LDAP server</h4>";

}

?>

这也反映了一般php程序使用ldap的基本流程。其中ldap_connect()函数用来建立和服务器的连接,ldap_bind()函数则绑定到服务器并且进行身份的认证。而ldap_close()函数关闭到服务器的连接。

5.3.4        搜索信息

php中通过使用ldap_search()函数进行条目的搜索,搜索以后通过ldap_get_entries等函数返回所取得的值。示例程序如下所示:

<?php

$ldap_host = "localhost";

$base_dn = "DC=example,DC=com";

$filter = "(cn=Joe User)";

$ldap_user  = "CN=Joe User,OU=Sales,DC=example,DC=com";

$ldap_pass = "secret";

$connect = ldap_connect( $ldap_host, $ldap_port)

      or exit(">>Could not connect to LDAP server<<");

ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);

$bind = ldap_bind($connect, $ldap_user, $ldap_pass)

     or exit(">>Could not bind to $ldap_host<<");

$read = ldap_search($connect, $base_dn, $filter)   //搜索

     or exit(">>Unable to search ldap server<<");

$info = ldap_get_entries($connect, $read);   //返回值

echo $info["count"]." entries returned

";

$ii=0;

for ($i=0; $ii<$info[$i]["count"]; $ii++){

   $data = $info[$i][$ii];

   echo $data.":&nbsp;&nbsp;".$info[$i][$data][0]."

";

}

ldap_close($connect);

?>

5.3.5        增加条目

php中通过ldap_add()函数来增加一个条目,示例程序如下:

<?php

$ds=ldap_connect("localhost");  // assuming the LDAP server is on this host

ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);

if ($ds) {

    // bind with appropriate dn to give update access

    $r=ldap_bind($ds,"cn=Manager, dc=example, dc=com", "secret");

    // 各项定义

    $info["cn"]="Jay Lau";

    $info["sn"]="Jay";

    $info["mail"]="jay@example.com";

    $info["objectclass"]="person";

    // 添加条目

    $r=ldap_add($ds, "cn=Jay Lau,cn=Manager,dc-example,dc=com ", $info);

    ldap_close($ds);

} else {

    echo "Unable to connect to LDAP server";

}

?>

5.3.6        修改条目

对条目的修改有着一系列的函数可以使用,如ldap_modify()ldap_mod_add()ldap_mod_delete()等等,下面以ldap_modify()举例:

<?php

$ds=ldap_connect("localhost"); 

ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);

if ($ds) {

    $r=ldap_bind($ds,"cn=Manager, dc=example, dc=com", "secret");

    $info["cn"]="Jay Lau";

    $info["sn"]="Jay";

    $info["mail"]="jay@example.com";

    $info["objectclass"]="person";

    //修改条目

    $r=ldap_modify($ds, "cn=Liu Jie,cn=Manager,dc-example,dc=com ", $info);

    ldap_close($ds);

} else {

    echo "Unable to connect to LDAP server";

}

?>

5.3.7        删除条目

ldap_delete()函数用来删除一个条目,举例如下:

<?php

$ds=ldap_connect("localhost");

ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);

if ($ds) {

    $r=ldap_bind($ds,"cn=Manager, dc=example, dc=com", "secret");

    //删除条目

    $r=ldap_delete($ds, "cn=Jay Lau,cn=Manager,dc-example,dc=com ");

    ldap_close($ds);

} else {

    echo "Unable to connect to LDAP server";

}

第六章   总结和展望

LDAP从确立标准到现在只过去了大约10年的时间,而这10年的时间也恰好是Internet在全球飞速发展的10年。但是我们至今也仍然看到,比起其他的网络应用,至今,LDAP并没有得到应有的重视。LDAP至今似乎被作为了专为电子黄页和身份资料储存而存在的一种协议,实际上,即使在这两个领域,LDAP也并没有起到应有的主导作用。而在国内,LDAP的应用更为稀少。

展望未来,一方面LDAP将在传统的应用中继续发挥作用。实际上相对于其他的方案,在上一段所提到的两个领域中,LDAP的优势是极为明显的。特别是在各个行业的身份资料存储中,LDAP基于网络的特点将极大的提高效率,减少成本。

而在另一方面,可以看到的是LDAP本身也将不断的增加各种新特点。从而使得LDAP更加高效,可靠和安全。

在目前,仍然有很多的服务商在支持各种目录服务,在我们看来,单纯的,面向大众的目录服务市场将逐渐缩小。但是目录服务在后台作为支持数据库,前台采用Web服务器的各种新型服务将逐渐增多。这也就是说,目录服务+Web服务将是未来目录服务被应用的主要领域。

阻碍LDAP发展的另一个方面是LDAP开发的复杂性。目前可以开发的LDAP应用,仅仅可以依靠几种有限的接口,而这些调用一般来说都比较复杂,在RADRapid Application Development)大行其道的今天,LDAP的开发却使得很多人感到迷茫。所以,更多更好的编程接口,将有助于推动LDAP的应用。

 

参考文献

【1】          韩华,代亚非,李晓明,郭朝阳,“一种基于分布式LDAP的分布对象名字服务机制”,高技术通讯,200210

【2】          Gerald Carter,“LDAP System Administration”,O'ReillyISBN 1-56592-491-6March 2003

【3】          Timothy A. Howes, Ph.D., Mark C. Smith, Gordon S. Good,“Understanding and Deploying LDAP Directory Services”,New Riders PublishingISBN : 1-57870-070-1December, 1998

【4】          R. Allen Wyke,, Michael J. Walker,, Robert Cox,“PHP Developer's Dictionary”,Sams Publishing ISBN 0-672-32029-0December , 2000

【5】          David Sklar, Adam Trachtenberg,“PHP Cookbook”,O'ReillyISBN : 1-56592-681-1November 2002

6  Openldap项目:www.openldap.org

 

 

致谢

感谢我的父母和家人

感谢朱海萍老师和陈妍老师的指导。

感谢林煊治同学与我一起从事本课题的研究。

感谢赵宇洲工程师对我们的指导。

感谢我的朋友,外经贸大学的李吏瑾同学在毕业设计中帮我所做的一些资料的翻译工作。

感谢西安交通大学计算机92班的同学们在我大学经历中给我的帮助。

感谢我的朋友张兆川,赵中雷,任莎给我的鼓励和支持。