构建和集成小型办公室 Intranet

作者:Dave Jones

Intranet 已经存在很长时间了。早在 1990 年代初期,它们就是万维网技术的首批替代用途之一。将一些 Web 体验引入内部的想法非常吸引人,但与现有系统集成却很困难。因此,许多 intranet 只不过是经过美化的公告板,并加入了一些用户发布功能。现在的情况已经不同了,开源软件已经准备好从一个良好的 intranet 设置中消除大部分成本和部分复杂性。所谓的 LAMP 堆栈为将许多不同的软件组件集成到用户交互的单一入口点提供了完美的中立平台。这正是我们在公司尝试做的事情。

我们的 intranet 最初于 1999 年以基于 Web 的公告板和公司日历的形式在运行 Apache 的 Red Hat 6.0 服务器上启动。这是一个静态 HTML 站点,由我们的营销经理设计和维护。在她在 2002 年离开公司后,我们需要使 intranet 更具动态性,使其不再依赖一个人来保持更新。通常情况下,多年来我们添加了越来越多的功能,现在拥有了一个非常有用、用户友好的 intranet 站点,而没有大量不必要的或需要维护的静态内容。在本文中,我以我们的 intranet 为例,说明如何解决小型企业管理员在设置基于 LAMP 的 intranet 时可能遇到的四个更常见的集成任务。

技术概述

我们的 intranet 目前为大约 70 名员工提供服务,并在运行 Fedora Core 4 的 IBM x335 服务器上运行。我们使用标准的 LAMP 堆栈(Linux、Apache 1.3x、MySQL 和 Perl),并使用 mod_perl 来提高性能。Apache 当前与我们的电子邮件扫描程序、内部 DNS、Jabber、Samba 和其他一些服务共享服务器。将所有这些都运行在单个 Linux 服务器上非常方便,因为它减少了对 NFS 挂载的需求并减少了网络流量。对于某些站点来说,这种方法可能太大,但我们设计中的任何内容都不会阻止它在多服务器设置中工作。我们所有的用户都运行 Windows XP 并通过 Active Directory 进行身份验证。我们使用 GroupWise 作为我们的电子邮件软件,它运行在 NetWare 6 服务器上,其所有信息都由 Novell 的 eDirectory 处理。我们还有一个时间和计费系统,它运行在 Windows NT 4.0 服务器上,并将其数据存储在 Microsoft SQL Server 数据库中。您可以在图 1 中看到所有内容如何链接在一起的布局。

Building and Integrating a Small Office Intranet

图 1. 我们的企业服务是如何连接的

服务器端凭证

我们很早就决定,我们的用户不应该以任何方式向我们的 intranet 进行身份验证。该站点应该根据他们的 IP 地址以及从网络上收集的关于谁当前从该地址登录的信息来自动“知道”他们是谁。我们将这种技术称为服务器端凭证 (SSC)。我们最初通过使用一段自定义编写的客户端软件来实现这一点,每当服务器需要检查用户身份时,CGI 脚本都会联系该软件。这可行,但它对客户端的信任度过高。嗅探器和 Perl 脚本可以很好地从任何客户端计算机伪造用户身份。我们现在使用 Samba 和 winbindd 来完成这项任务。

由于我们的 intranet 服务器位于受信任的内部网络上,因此它可以访问网络上的当前状态,包括谁从哪里登录。办公室中的每台计算机在登录期间都将驱动器号映射到 Samba 服务器,因此每当服务器需要当前用户的身份时,它只需在 Samba 连接列表中查找其 IP 地址即可。映射的驱动器只是专门用于 SSC 机制的虚拟驱动器。我认为这是一个重要的功能,因为它降低了站点从编程角度来看的复杂性,并允许用户自由浏览,而无需担心注册或登录。用户已经有足够多的用户名和密码需要跟踪,我们不需要再增加他们的负担。

您设置 SSC 的方式取决于您的用户如何在您的网络上进行身份验证。我们使用 Active Directory,所以这是我在这里演示的内容。Active Directory 很烦人(惊喜,惊喜),因为它不会在其目录中存储连接状态信息。您必须使用带有 Samba 的 net 命令的传统 RPC 调用才能获得可靠的结果。我们的 SSC 脚本名为 smbconn.sh,它看起来像这样

#!/bin/sh

net status sessions parseable              \
| grep -i "\\\\$1\\\\"                     \
| sed 's/^.*\\\(.*\)\\.*\\.*\\.*$/\1/g'    \
| sed 's/DOMAIN+//g' | tr -d ' '

很简单,对吧?只需记住将 DOMAIN 更改为您的 Active Directory 的域名即可。此脚本返回从我们通过命令行传递给它的 IP 地址登录到 Samba 的用户对象的名称。它返回的名称对应于 Active Directory 的 sAMAccountName 属性。有了这些信息,我们现在可以运行 LDAP 查找以获取用户的全名或我们可能需要的任何其他数据。我们在清单 1 中找到了用于执行此操作的脚本。它将用户的 sAMAccountName 作为其第一个参数,并将您希望返回其值的可选属性作为第二个参数。如果您不提供可选属性,则脚本返回用户的全名。您可以在自定义 mod_perl 处理程序中完成所有这些操作,以便其信息始终可用,但这对于大多数站点来说似乎有点过分。我们的站点只有少数受限制的部分会用到此信息,因此我们只是让每个 CGI 脚本根据需要运行它。以下是我们其中一个 CGI 脚本中的典型 SSC 调用

##: Get this connection's user credentials
my $remoteip=$ENV{'REMOTE_ADDR'};

open(SMBCONN,"smbconn.sh $remoteip |");
my $cn=<SMBCONN>;
$cn=~s/\s+//g;    ##: Strip whitespace
close(SMBCONN);

open(GETEMPINFO,"getempinfo.pl $cn |");
my $username=<GETEMPINFO>;
close(GETEMPINFO);
if($username eq "") {
  $username="Guest";
}

这段代码使我们在 $cn 变量中获得了用户的 sAMAccountName,并在 $username 变量中获得了用户的全名。如果 $username 变量包含 Guest,则表示查找失败,或者访问此 CGI 脚本的计算机没有已登录的用户在操作它。我们现在可以使用此关键信息来决定用户是否有权访问此 CGI 脚本旨在提供的信息。我们还可以使用此信息来返回为该特定用户定制的页面。我使用来自 index.cgi 文件的一段代码来演示这一点,该文件用于提供我们的主页

##: My Intranet section
my $mint="";
if(($username eq "Guest") || ($username eq "")) {
  open(EMPSNAP,"./random-employee.pl 2>&1 |");
  my @snap=<EMPSNAP>;
  close(EMPSNAP);
  $mint.=join("\n",@snap);
  chop($mint);
} else {
  $mint.=&get_emp_card($cn);
  $mint.="<b>E-Mail Controls:</b><br>\n";
  $mint.="<a href='selfserv.cgi'>My Mail</a>\n";
  ...
}
...
print STDOUT $mint;

您可以在这里看到我们检查查看主页的人员是否实际上是经过身份验证的用户。如果不是,我们会在主页的此部分提供随机员工的照片和个人资料。如果该人员是经过身份验证的用户,我们会从 LDAP 目录中抓取相应的个人信息,并继续在主页的此部分组装“我的 Intranet”区域,用户可以在其中编辑其员工个人资料、控制邮件首选项等等。get_emp_card($cn) 例程只是在 Active Directory 中查找用户的当前信息,并返回一个格式良好的 HTML 部分来显示它(图 2)。

Building and Integrating a Small Office Intranet

图 2. Intranet 上的示例用户个人资料

清单 1. getempinfo.pl

#!/usr/bin/perl -w
use Net::LDAP;
use strict;

my $cn=$ARGV[0] || "none";
my $attr=$ARGV[1] || "none";

##: If nothing was given on command line then return
if($cn eq "none") {
  print STDERR "ERROR: No LDAP cn given\n";
  exit(1);
}

##: Bind anonymously to the ldap database
my $ldap=Net::LDAP->new('directory.domain.com',timeout=>5)
  or die "Couldn't connect to directory server.\n";
my $mesg=$ldap->bind('proxyuser@domain.com',password=>'proxyuser')
  or die "Couldn't connect to directory server.\n";

##: Query LDAP to get a list of employees
if($attr ne "none") {
  $mesg=$ldap->search( base=> "ou=Domain Users,dc=domain,dc=com",
                       filter=> "(sAMAccountName=$cn)",
                       attrs=> ['givenName','sn',"$attr"] );
} else {
  $mesg=$ldap->search( base=> "ou=Domain Users,dc=domain,dc=com",
                       filter=> "(sAMAccountName=$cn)",
                       attrs=> ['givenName','sn'] );
}

my $count=$mesg->count();
($count==1) or die "Error: LDAP enumeration error.";

my $entry=$mesg->entry();
my $value;
my @values;
if($attr ne "none") {
  $value="";
  @values=$entry->get_value("$attr");
  my $i=1;
  for(@values) {
    if($i>1) {
      $value.="/$_";
    } else {
      $value.=$_;
    }
    $i++;
  }
} else {
  $value=($entry->get_value('givenName')." ";
  $value.=$entry->get_value('sn'));
}

##: See if that attribute was defined for the given cn
if(!(defined($value))) {
  print STDERR "ERROR: That attribute was not defined.\n";
  exit(1);
}

$mesg=$ldap->unbind;
print("$value\n");

Active Directory 集成

我们 intranet 的另一个有价值的补充是通过 LDAP 将其与我们的 Active Directory 用户数据库集成。我们使用它来提供一个公司目录,其中列出了我们所有的员工。该目录是在每次访问时实时构建的,这为管理员节省了大量时间。每当使用正常的 Active Directory 工具添加新用户时,他们都会立即显示在 intranet 目录中。我们也允许我们的用户编辑他们自己的个人信息,这些编辑由 CGI 脚本放入 Active Directory 中。该过程相对简单,尽管有一些事项需要考虑。让我带您了解我们如何设置它的过程。

我们做的第一件事是在 Active Directory 中创建一个名为 proxyuser 的代理用户。这是我们的脚本用于通过 LDAP 进行身份验证的用户名。代理用户被授予在 ou=Domain Users 容器中读取和写入用户对象信息的权限。这就是在 Active Directory 中需要做的全部工作。我们使用 Perl 作为我们的 CGI,这意味着使用 Net::LDAP。以下是如何从 CGI 脚本中连接到 Active Directory

##: Active Directory connection
use Net::LDAP;

my $ldap=Net::LDAP->new('adserver.domain.com');
my $mesg=$ldap->bind('proxyuser@domain.com',
                      password=>'proxyuser'  );

请注意 Active Directory 对用户名字段的语法要求。这是 Active Directory 的 LDAP 接口的独特要求之一。现在我们已经连接到目录,我们执行一个查询以查找 ou=Domain Users 容器中的所有用户对象

##: Query LDAP to get a list of employees
my $basedn="ou=Domain Users,dc=domain,dc=com";
my $filter="(objectClass=user)";
$mesg=$ldap->search(
  base=> $basedn,
  filter=> $filter,
  attrs=> ['givenName','sn','mail',
           'telephoneNumber','streetAddress',
           'l','st','department','postalCode',
           'employeeNumber','homePhone',
           'title','sAMAccountName' ]
  );

这将返回该容器中的所有用户对象,以及您期望在公司目录中找到的所有相关属性。我们现在可以优化我们的搜索过滤器,将我们的搜索限制为仅限那些姓氏以 CGI 脚本在其 URL 中传递的字母开头的用户。这允许我们遵循地址簿格式,因此我们不必一次显示所有 70 个用户。如果没有在 URL 中请求字母,我们将回退到字母 a

##: Get letter requested in the URL
my $letter;
$letter=param('letter') || "a";
...
my $filter="(&(objectClass=user) (sn=$letter*))";

如果您不熟悉 LDAP 搜索过滤器使用的语法,我建议您查看 RFC-2254。此时,我们可以迭代我们的查询结果并根据需要对其进行美化。由于我们还查找了此用户的 SSC 信息,因此我们可以在遍历循环时检查每个员工的 sAMAccountName。当我们找到与 SSC 说是正在查看页面的员工对应的员工时,我们在员工姓名旁边添加一个链接,允许他或她转到某个区域来编辑目录信息。它看起来像这样

##: Display the directory
foreach my $entry ($mesg->sorted('sn')) {

  my $san=$entry->get_value('sAMAccountName');
  $empdir.="<div class='empcard'>";
  if(lc($cn) eq lc($san)) {
    ##: This is our man.  Add a button.
    $empdir.="<a href='empedit.cgi'>Edit</a>";
  }
  $empdir.="<span id='name'>";
  $empdir.=$entry->get_value('givenName')." ";
  $empdir.=$entry->get_value('sn');
  $empdir.="</span><br>";
  $empdir.="<span id='title'>";
  $empdir.=$entry->get_value('title').";
  $empdir.="</span><br>";
  ...
  $empdir.="</div>";

}
print STDOUT $empdir;
$mesg=$ldap->unbind();

SpamAssassin 和电子邮件集成

我在 2001 年为我们公司设计了一个电子邮件网关,它仍然是我们今天使用的系统。我在之前的 Linux Journal 2001 年 12 月刊的文章中对此进行了介绍。自那时以来,该系统已进行了大量修改,但它仍然以相同的基本方式运行。它只是一个存储、扫描和转发代理。由于这一切都发生在我们的 Linux 服务器上,我们的 Windows 用户无法查看或检索误报,也无法控制他们的 SpamAssassin 白名单。我们通过构建一组 CGI 脚本来解决这个问题,让我们的用户可以使用 intranet 作为界面,修改他们的 SpamAssassin 首选项文件并自行从垃圾邮件陷阱中释放他们的误报。

用户从我们主页上的“我的 Intranet”部分启动邮件管理脚本(图 2)。他们从下拉框中选择他们想要查看哪一天的邮件,然后单击按钮以激活 selfserv.cgi 脚本。没有用户身份信息传递给脚本,因为它将从 SSC 查找中获取该信息。在我们完成初始 SSC 查找后,我们再次调用 getempinfo.pl 脚本以获取当前用户的电子邮件地址,如下所示

##: Get this user's email address
open(GETEMPINFO,"-|","getempinfo.pl",$cn,"mail");
my $searchstring=<GETEMPINFO>;
close(GETEMPINFO);

$searchstring 变量然后成为我们用来在 /spam 目录中搜索属于此用户的垃圾邮件的正则表达式的基础。由于来自 Active Directory 的邮件属性是人为输入的,因此我们必须再次检查以确保我们没有成为拼写错误的受害者

##: Make sure this email address is valid
unless($searchstring=~/^[a-z]*\@domain\.com$/) {
  print STDOUT "Content-Type: text/plain\n\n";
  print STDOUT "Access Denied: Your identity on \
    the network can't be verified.\n";
  return(0);
}

如果这些检查成功,脚本将通过以表格格式显示用户请求的当天的垃圾邮件作为响应,并在每个项目的侧面显示选项链接列表(图 3)。然后,用户可以使用选项链接让脚本释放垃圾邮件、将发件人列入白名单、将发件人列入黑名单、生成 SpamAssasin 报告或仅将其显示为纯文本。脚本每次被调用以及在执行任何操作之前都会查找用户的 SSC 信息,以便它知道是否允许该操作。我不会在这里深入探讨更多细节,因为此脚本的功能主要包括响应用户的请求来移动文件。但我确实想提及白名单和黑名单选项。

Building and Integrating a Small Office Intranet

图 3. 处理捕获的垃圾邮件的选项

SpamAssassin 将其每用户配置数据保存在每个用户主目录中名为 .spamassassin/user_prefs.cf 的文件中。在正常设置中,Linux 是您的主要邮件服务器,这很好,但在我们的情况下,它不起作用。我们的 Linux 服务器只是一个扫描网关,用于中继进出邮件,因此它不知道我们的用户或他们的电子邮件帐户。为了解决这个问题,我们必须稍微作弊一下。SpamAssassin 的主配置文件名为 /etc/mail/spamassassin/local.cf,它在每次启动时都会读取此文件。但它不仅读取该文件。它实际上读取 /etc/mail/spamassasssin 目录中所有扩展名为 .cf 的文件。我们可以利用这一点,让我们的 CGI 脚本在此目录中为每个用户的白名单创建 $cn_prefs.cf 格式的文件。我们有一个 cron 作业,无论如何每小时重启一次 spamd 以释放内存,所以这效果很好。如果您使用此方法,要记住的最重要的事情是,您必须进行严格的语法检查,以确保用户没有将诸如 *@hotmail.com 之类的东西列入白名单,或者使用任何其他 SpamAssassin 指令。即使这些文件对用户来说具有私有首选项文件的外观,但它们实际上是 SpamAssassin 全局的,因为它们位于主配置目录中。

Microsoft SQL Server 集成

我们公司使用一个名为 CPAS 的时间和计费系统。此软件包保存了我们所有的客户和计费信息,以及我们的营销经理用于组装大规模邮件发送给客户的信息。我们希望让我们的用户访问此信息以进行一些基本的​​数据挖掘,而无需每次都联系管理部门。由于 CPAS 将其信息存储在 Microsoft SQL Server 数据库中,因此我们必须使用名为 FreeTDS 的软件和来自 CPAN 的 DBD::Sybase 包,以便从我们的 Perl CGI 脚本与之交互。

设置它涉及四个步骤。要做的第一件事是从 Internet 上获取最新的 FreeTDS 包并解压缩 tarball。接下来,cd进入解压缩的目录,并执行以下命令

> ./configure --prefix=/usr/local/freetds
> make
> su -c 'make install'

这会在其自己的目录中设置 FreeTDS,以便 Sybase 模块稍后更容易找到它。接下来,我们进入 CPAN 并获取 DBD::Sybase 包。成为 root 并执行以下命令

> perl -MCPAN -e shell
> install DBD::Sybase

如果某些测试失败,请随意强制安装——根据该软件包的作者的说法,这很常见。此时,软件已安装,但我们必须设置 FreeTDS 配置文件。此文件保存有关您将连接到的数据库的信息。配置文件有详细的文档记录,您应该能够轻松弄清楚语法。这是一个示例服务器条目

[JACKSON5]
    host = jackson5.domain.com
    port = 1433
    tds version = 4.2

配置 FreeTDS 后,您可以通过 Perl 中熟悉的 DBI 接口从您的 CGI 脚本访问您的数据库。以下是连接到名为 concerts 的数据库的示例,该数据库在名为 JACKSON5 的 Windows 服务器上运行

#!/usr/bin/perl -w
use DBI; $ENV{'SYBASE'} = '/usr/local/freetds';
$dbh = DBI->connect('dbi:Sybase:server=JACKSON5', 'username', 'password')
    or die 'connect';
$dbh->do("use concerts");

请注意,您必须在尝试连接之前将您的 FreeTDS 安装位置放在环境变量中。环境变量告诉 DBD::Sybase 在哪里找到 FreeTDS 库。之后,您只需像往常一样使用 DBI 执行查询即可。如果您习惯使用 MySQL,我建议您研究一下 Microsoft SQL Server 使用的语法。其中一些语法与您习惯的语法非常不同。

结论

我希望本文能为您提供一些关于如何更好地将您的 intranet 与小型企业中更常见的一些系统集成的想法和实用知识。Intranet 不应仅仅是新闻门户或电子公告板。它应该是用户的交互式工具和管理员的省时工具。用户在浏览器环境中感到一种舒适感,这是他们在文件系统中搜索或盯着命令行时所没有的。利用这一点,您的 intranet 将成为您业务的宝贵资产。

资源

FreeTDS: www.freetds.org

RFC-2254: ftp.rfc-editor.org/in-notes/rfc2254.txt

为您的网络构建电子邮件病毒检测系统: /article/4882

CPAS: www.cpasoftware.com

Pearce, Bevill, Leesburg & Moore P.C.: www.pearcebevill.com

Dave Jones 是阿拉巴马州伯明翰 Pearce, Bevill, Leesburg & Moore 的 IT 经理。他担任网络管理员已有八年。他将时间花在博客写作和在 www.sector62.com 编写软件上。

加载 Disqus 评论