perl-ldap 简介
随着系统规模的扩大和它们支持的用户数量的增加,仅仅使用老式的 UNIX /etc/passwd 文件来管理系统变得越来越困难。解决这个问题的常见方案是使用轻型目录访问协议 (LDAP) 服务器。然而,使用 LDAP 服务器给系统管理员带来了一个问题,即数据库的内容不再以易于阅读或修改的格式提供。因此,必须编写新的工具,以便执行标准的日常任务,例如添加或删除用户。
这就是 perl-ldap 发挥作用的地方。 perl-ldap 提供了 Net::LDAP perl 模块,该模块可以轻松地从 Perl 脚本访问 LDAP 目录中包含的数据。这使得该模块成为系统管理员和 Web 开发人员的有用工具。 perl-ldap 的主页位于 http://perl-ldap.sourceforge.net/。
对于本文,我假设您对 LDAP 有合理的了解,并且是一位合格的 Perl 程序员。如果不是,互联网上有很多关于这两个主题的已发布资料。
如果您运行的是流行的 Linux 发行版之一,那么 perl-ldap 很可能已经为您打包好了,这使得安装变得简单。在 Debian Linux 下,perl-ldap 可以在 libnet-ldap-perl 软件包中找到。假设您的 /etc/apt/sources.list 文件包含最新的 Debian 服务器,则以下命令应安装 perl-ldap
apt-get update apt-get install libnet-ldap-perl
Mandrake 用户将在 perl-ldap 软件包中找到他们需要的内容;对于 Mandrake 9.1,特定的软件包是 perl-ldap-0.27.01-1mdk.noarch.rpm。如果您正确配置了 urpmi,则只需输入以下命令即可安装 perl-ldap
urpmi perl-ldap
此命令还会安装 perl-Authen-SASL 和 perl-XML-Parser 软件包,它们是 Mandrake 中的 perl-ldap 依赖项。
Red Hat 似乎没有提供 perl-ldap 软件包,因此此发行版的用户要么必须从另一个基于 RPM 的发行版获取它,要么从 tar.gz 软件包安装它,如下所述。
如果您的系统没有预构建的软件包,您必须从 CPAN 下载 tar.gz 软件包并自行安装。由于 LDAP 协议使用 ASN1 编码,您还需要 Convert::ASN1 库。虽然您可能可以在没有它的情况下安装 perl-ldap,但 perl-ldap 肯定无法运行,除非此库可用。这两个库都很容易安装
perl Makefile.PL make make test su root make install
与其他 Perl 库一样,perl-ldap 使用 use 语句调用
use Net::LDAP
使用 new() 函数调用打开新的 LDAP 连接。在以下示例中,我们打开与主机名为 ldapserver.domain.name 的机器的连接
$ldap = Net::LDAP->new("ldapserver.domain.name");
由于我们没有指定要使用的端口号,perl-ldap 假定默认端口为 389,即众所周知的 LDAP 端口。如果我们想使用不同的端口,例如 1389,我们需要传递 port 参数
$ldap = Net::LDAP->new("ldapserver.domain.name", port=>1389);
如果服务器无法访问,则上述函数调用将在 120 秒后返回错误。您可以使用 timeout 参数来更改此设置
$ldap = Net::LDAP->new("ldapserver.domain.name", timeout=>30);
连接初始化后,您不再需要显式引用 Net::LDAP 软件包。所有 perl-ldap 函数都作为从 new() 调用返回的引用的方法访问。perl-ldap 提供的最常用的方法如下
$ldap->add(); # Add an entry to the server $ldap->bind(); # Bind to a directory server $ldap->delete(); # Delete an entry from the server $ldap->moddn(); # Modify an entry's Distinguished Name (DN) $ldap->modify(); # Modify the contents of an entry $ldap->search(); # Perform a search on a directory $ldap->unbind(); # Unbind from a server
这些将在下面详细描述。
对于此示例,我们假设我有一个包含以下内容的 LDAP 目录
dn: dc=leapster,dc=org | -- dn: cn=admin,dc=leapster,dc=org | -- dn: ou=People,dc=leapster,dc=org | -- dn: uid=paul,ou=People,dc=leapster,dc=org | -- dn: uid=mike,ou=People,dc=leapster,dc=org
简而言之,我的 LDAP 基本 DN 是 dc=leapster,dc=org。系统的管理用户(具有超级用户控制权限的条目)是 cn=admin,dc=leapster,dc=org。它还包含两个用户条目,uid=paul 和 uid=mike。
连接到 LDAP 服务器后,您需要绑定到它。如果您正在编写程序来与公共 LDAP 目录对话,那么您很可能只需要使用匿名绑定
$mesg = $ldap->bind;
但是,如果您正在编写脚本来管理用于存储本地用户或客户帐户信息的服务器目录,您可能只允许特定的高权限用户进行写入访问。在这种情况下,您需要提供具有这些权限的 LDAP 条目的 DN 以及密码。例如
$mesg = $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");
在这种情况下,我使用系统上的以下特权用户:cn=admin,dc=leapster,dc=org。如果我绑定到非特权用户之一(例如,uid=paul,dc=leapster,dc=org),我可能根本无法访问系统上的任何读取或写入选项,具体取决于服务器的配置方式。
我们存储在 $mesg 中的返回值是 New::LDAP::Message 类的对象。本文稍后将对此进行讨论。
如果您希望关闭连接,则必须解除绑定
$ldap->unbind;
如果您的系统上有大量用户,则您可能不想通过 GUI 逐个将每个新用户添加到系统中。因此,您编写的第一件事之一是快速批量添加大量用户的脚本。或者,您可能会编写一个基于 Web 的系统,用户可以在其中输入自己的个人信息,并自动添加 LDAP 条目。 add() 方法用于向数据库添加条目
$result = $ldap->add("uid=john,ou=People,dc=leapster,dc=org", attr => [ 'cn' => 'John Smith', 'sn' => 'Smith', 'uid' => 'john', 'givenName' => 'John', 'homePhone' => '555-2020', 'mail' => 'john@domain.name', 'objectclass' => [ 'person', 'inetOrgPerson'] ] );
上面的代码片段将名为 John Smith 的用户添加到我们的数据库中。如您所见,属性作为列表提供给 attr 参数。您希望为其提供多个值的任何属性都应以列表形式提供(在上面的示例中,objectclass 就是这样一个属性)。
现在我们已经到了可以编写一个简单的脚本来批量添加大量用户的地步,我将此脚本称为 ldap_addusers。
#!/usr/bin/perl use Net::LDAP; $ldap = Net::LDAP->new("localhost"); $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret"); while(<>) { chomp $_; ($uid,$givenName,$sn,$mail) = split(/:/,$_); $cn="$givenName $sn"; $dn="uid=$uid,ou=People,dc=leapster,dc=org"; $result = $ldap->add($dn, attr => [ 'uid' => $uid, 'cn' => $cn, 'sn' => $sn, 'mail' => $mail, 'givenName' => $givenName, 'objectclass' => [ 'person', 'inetOrgPerson'] ] ); $result->code && warn "error: ", $result->error; }
上面的脚本接受一个以冒号分隔的用户文件,每行一个用户,在 stdin 上
tom:Tom:Jones:tom@domain.name dick:Dick:Tracy:dick@domain.name harry:Harry:Windsor:harry@domain.name
因此,如果这些名称存储在名为 userlist 的文件中,我们可以使用 ldap_addusers 脚本将它们输入到我们的 LDAP 数据库中,如下所示
cat userlist | ./ldap_addusers
ldap_addusers 脚本中有一行我们之前没有见过
$result->code && warn "error: ", $result->error;
如前所述,add() 方法返回 Net::LDAP::Message 类型的对象。在这里,此对象由 $result 引用。 $result->code 是从 LDAP 服务器在查询后的结果消息中返回的代码值(在本例中,是添加条目的请求)。通常,当请求成功时,返回零。因此,在我们上面的语句中,仅当 $result->code 不为零时才发出警告。
Net::LDAP::Message 中的其他一些有用的方法是
$result->dn The DN contained in the result message $result->error The error message in the result (only if there was an error) $result->done True if the request was completed $result->is_error True if the particular result is an error for the operation
有关其他方法的完整描述,请参阅 Net::LDAP::Message 的 perldoc 手册。
perl-ldap 还提供了 delete() 方法,用于在给定 DN 的情况下删除条目
$dn="uid=paul,ou=People,dc=leapster,dc=org"; $ldap->delete($dn);
例如,这将允许您编写一个脚本来从系统中批量删除过期的用户
#!/usr/bin/perl use Net::LDAP; $ldap = Net::LDAP->new("localhost"); $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret"); while(<>) { chomp $_; $dn="uid=$_,ou=People,dc=leapster,dc=org"; $ldap->delete($dn); }
此脚本删除所有用户,这些用户的 uid 在标准输入上馈送给它,每行一个。在生产服务器上使用此脚本时要小心;如果您不小心将错误的文件馈送给它,您可能会发现您的目录中没有用户了。
如果只能写入我们的 LDAP 服务器,那将没什么用;我们还需要能够从中读取。 perl-ldap search 命令用于在 LDAP 服务器上执行查找。
$mesg = $ldap->search(filter=>"(uid=paul)", base=>"dc=leapster,dc=org");
base 参数指定将从中进行搜索的基本对象条目。在上面的示例中,它搜索整个 LDAP 树。搜索可以仅限于 ou=People 分支,使用
$mesg = $ldap->search( filter=>"(uid=paul)", base=>"ou=People,dc=leapster,dc=org");
search() 方法还有许多其他可选参数可用
scope : 这可以是以下之一
base: 仅搜索基本对象
one: 仅搜索基本对象下一级的条目
sub: 搜索基本对象下的整个子树
如果我知道 ou=People 下面没有子树——或者即使有,但我不想从它们返回结果——我可以使用
$mesg = $ldap->search( filter=>"(uid=paul)", base=>"ou=People,dc=leapster,dc=org" scope=>"one");
timelimit: 设置请求可能花费的时间限制(以秒为单位)。默认值为 0,表示时间不受限制。
attrs: 设置应在搜索中返回的属性(作为对数组的引用)。如果未提供,则搜索返回所有属性。例如,以下搜索仅返回 uid、sn 和 givenName 属性
$mesg = $ldap->search( filter=>"(uid=paul)", base=>"ou=People,dc=leapster,dc=org", attrs=> ['uid', 'sn', 'givenName'] );
filter: 过滤器可以是字符串,采用标准 LDAP 过滤器格式(有关此描述,请参阅 ldap_search(3) 手册页),也可以是 Net::LDAP::Filter 对象(有关更多信息,请参阅 Net::LDAP:Filter 手册页)。
search() 方法返回 Net::LDAP::Search 对象。获取此对象内容的最简单方法是使用其 entries() 方法,该方法返回 Net::LDAP::Entry 对象数组(见下文)
@entries = $mesg->entries;
Net::LDAP::Search 对象还有许多其他有用的方法
$mesg->count; The number of entries returned in the search $mesg->entry(n); Returns the n'th entry (initial entry is 0) $mesg->sorted([list]) Returns a list of entry objects sorted by attr list
现在,我们可以编写一个小脚本来列出目录中的每个条目
#!/usr/bin/perl use Net::LDAP; $ldap = Net::LDAP->new("localhost"); $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret"); $mesg = $ldap->search(filter=>"(objectClass=*)", base=>"dc=leapster,dc=org"); @entries = $mesg->entries; foreach $entry (@entries) { $entry->dump; }
我在上面的脚本中作弊了一点,并使用了 Net::LDAP::Entry 的 dump() 方法,以便使事情更清楚。 dump() 主要用于调试;它只是将条目的 DN 和内容直接转储到标准输出,而不允许对结果进行任何操作。
Net::LDAP::Entry 对象最常用的方法是
attributes: 返回此条目中包含的属性列表。
@attrs = $entry->attributes();
dn: 返回当前条目的 DN。如果给定参数,则设置条目的 DN
$dn = "uid=pbd,ou=Users,dc=leapster,dc=org"; $entry->dn($dn);
get_value: 获取作为参数给出的属性的值。如果此方法用于赋值给标量变量,则它仅返回属性的第一个值;如果与数组一起使用,则它返回所有属性。
$phone = $entry->get_value("homePhone"); # returns only one phone number @phone = $entry->get_value("homePhone"); # returns all phone numbers for entry
add、delete、modify:这些方法允许对条目进行更改,并在下一节中进一步讨论。
update: 将对条目所做的任何更改推送到 LDAP 服务器(其对象作为参数给出)
$entry->add(homePhone => "555 3034"); $entry->update($ldap);
现在我们已经检查了 Net::LDAP::Entry 对象,我们可以进一步扩展上面的脚本。我们可以自己写出条目的内容
#!/usr/bin/perl use Net::LDAP; $ldap = Net::LDAP->new("localhost"); $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret"); $mesg = $ldap->search(filter=>"(objectClass=*)", base=>"dc=leapster,dc=org"); @entries = $mesg->entries; foreach $entry (@entries) { print "dn: " . $entry->dn() . "\n"; @attrs = $entry->attributes(); foreach $attr (@attrs) { printf("\t%s: %s\n", $attr, $entry->get_value($attr)); } }
目录中的条目是静态的将是不寻常的。各种属性可能会随着时间的推移而更改,例如用户更改电话号码、地址甚至姓名。至少,您会希望您的用户定期更改密码。 perl-ldap 提供了 modify() 方法来处理此类更改。
可以对 LDAP 条目执行的三个主要修改操作是
add: 向条目添加一个或多个属性
delete: 从条目中删除一个或多个属性
replace: 用不同的值替换一个或多个属性。
示例
$dn = "uid=paul,ou=People,dc=leapster,dc=org"; # add a 'homePhone' attribute and a 'mail' attribute $mesg = $ldap->modify($dn, add => { "homePhone" => "555 3030", "mail" => "paul\@mail.home"} ); # add two more 'homePhone' attributes $mesg = $ldap->modify($dn, add => { "homePhone" => ["555 3031", "555 3032"] }); # delete the mobile and pager attributes $mesg = $ldap->modify($dn, delete => [ 'mobile', 'pager' ] ); # change the mail attribute to 'paul@domain.name' $mesg = $ldap->modify($dn, replace => { "mail" => "paul\@domain.name" } );
如果您的属性具有多个值,并且只想删除其中一个值,则可以为 delete 提供特定的属性/值哈希以删除
$mesg = $ldap->modify($dn, delete => { 'homePhone' => "555 3031" } );
如果您希望一次进行多次更改,modify 还提供了 changes 参数,该参数接受 add、delete 和 replace 操作的列表
# Add an employeenumber and delete $mesg = $ldap->modify($dn, changes => [ add => [ employeeNumber => "4321" ], delete => [ mail => [] ] ]);
与大多数其他 perl-ldap 方法一样,modify() 返回一个 Net::LDAP::Message 对象。因此,您可以使用 $mesg-code 来检查是否返回了错误。
也可以直接修改 LDAP 条目的本地副本,然后将更改推送到服务器。 Net::LDAP::Entry 有许多方法可以做到这一点。每种方法都接受属性/值哈希列表(delete 也接受简单的属性名称列表)
add: 向条目添加一个或多个属性。
delete: 从条目中删除一个或多个属性。
replace: 替换条目中的一个或多个属性。
在调用 update() 方法之前,这些更改都不会传播到目录服务器。
$base = "ou=People,dc=leapster,dc=org"; $mesg = $ldap->search( filter=>"(uid=paul)", base=>$base); $entry = $mesg->entry(0); $entry->add(homePhone => "555 3035", pager => "555 4040"); $entry->delete("suburb"); $entry->replace(fax => "555 5050"); $entry->update($ldap);