Building and Integrating a Small Office Intranet

This “how we did it” story includes valuable tips for building an intranet that integrates enterprise services in a user-friendly way.

Intranets have been around for a long time. They were one of the first alternate uses for World Wide Web technology back in the early 1990s. The idea of bringing a little bit of the Web experience in-house was very attractive, but integration with existing systems was difficult. Thus, a lot of intranets were nothing more than glorified bulletin boards with some user-publishing features thrown in. The landscape is different now, with open-source software ready to take most of the cost and some of the complexity away from a good intranet setup. The so-called LAMP stack provides the perfect neutral platform for integrating many different pieces of software into a single point of interaction for users. That's what we have tried to do at our company.

Our intranet started off in 1999 as a Web-based bulletin board and company calendar on a Red Hat 6.0 server running Apache. It was a static HTML site that was designed and kept current by our marketing manager. After she left the company in 2002, we needed to make the intranet more dynamic so that it didn't depend on one person to keep it up to date. As is usually the case, we added more and more features over the years and now have a very useful, user-friendly intranet site without a lot of unnecessary or static content that needs to be maintained. In this article, I use our intranet as an example of how to solve four of the more common integration tasks that small business admins may run into when setting up a LAMP-based intranet.

Technical Overview

Our intranet currently serves about 70 employees and runs on an IBM x335 server running Fedora Core 4. We use a normal LAMP stack (Linux, Apache 1.3x, MySQL and Perl) with mod_perl to improve performance. Apache currently shares the server with our e-mail scanner, internal DNS, Jabber, Samba and some other services. It's nice having all of this running on a single Linux server, because it reduces the need for NFS mounts and cuts down on network traffic. Some sites will be too large for this approach, but nothing in our design would preclude it from working in a multiserver setup. All of our users run Windows XP and authenticate through Active Directory. We use GroupWise as our e-mail software running on a NetWare 6 server, and all of its information is handled by Novell's eDirectory. We also have a time and billing system that runs on a Windows NT 4.0 server and stores its data in a Microsoft SQL Server database. You can see a layout of how everything links together in Figure 1.

Figure 1. How Our Enterprise Services Are Connected

Server-Side Credentialing

We decided early on that our users shouldn't have to authenticate in any way to our intranet. The site should automatically “know” who they are based on their IP address and information gleaned from the network about who is currently logged in from that address. We call this technique server-side credentialing (SSC). We accomplished this originally by using a piece of custom-written client-side software that was contacted by a CGI script any time the server needed to check a user's identity. This works, but it places too much trust on the client side. A sniffer and a Perl script could fake a user's identity nicely from any client computer. We now use Samba and winbindd for this task.

Because our intranet server resides on a trusted internal network, it is privy to the current state of affairs on the network, including who is logged in from where. Every computer in the office maps a drive letter to the Samba server during login, so any time the server needs the current user's identity, it simply looks up his or her IP address in the Samba connection list. The mapped drive is just a dummy drive explicitly for the SSC mechanism. I think this is an important feature, because it lowers the complexity of the site from a programming standpoint and allows users to browse freely without having to worry about registering or logging in. Users have enough user names and passwords to keep track of already without us adding to their burden.

The way you set up SSC depends on how your users authenticate on your network. We use Active Directory, so that is what I demonstrate here. Active Directory is annoying (surprise, surprise), because it doesn't store connection status information in its directory. You must use traditional RPC calls with Samba's net command to get reliable results. Our SSC script is called, and it looks like this:


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

Pretty simple, eh? Just remember to change DOMAIN to whatever your Active Directory's domain name is. This script returns the name of the user object that is logged in to Samba from the IP address we pass to it on the command line. The name it returns corresponds to Active Directory's sAMAccountName property. Armed with this information, we now can run an LDAP lookup to get the user's full name or any other data we might need. The script we use to do this is found in Listing 1. It will take the user's sAMAccountName as its first argument and an optional attribute whose value you want returned as the second argument. If you don't provide the optional attribute, the script returns the user's full name. You could do all of this in a custom mod_perl handler so that its information always would be available, but this seems like overkill for most sites. Our site has only a handful of restricted sections where this information comes into play, so we just let each CGI script run it as needed. Here is a typical SSC call from one of our CGI scripts:

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

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

open(GETEMPINFO," $cn |");
my $username=<GETEMPINFO>;
if($username eq "") {

This section of code leaves us with the user's sAMAccountName in the $cn variable and the user's full name in the $username variable. If the $username variable contains Guest, either the lookup failed or the computer accessing this CGI script doesn't have a logged-in user operating it. We now can use this critical information to decide whether the user has access to the information this CGI script is meant to provide. We also can use this information to return a page customized for this particular user. I demonstrate this with a section of code from the index.cgi file that serves up our home page:

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

You can see here that we check to see if the person viewing the home page is actually a credentialed user. If he or she is not, we serve up a random employee's picture and profile in this section of the home page. If the person is a credentialed user, we grab the appropriate personal information from the LDAP directory and proceed to assemble a My Intranet area in this section of the home page where the user can edit his or her employee profile, control mail preferences and so forth. The get_emp_card($cn) routine simply looks up the user's current info in Active Directory and returns a nicely formatted HTML section to display it (Figure 2).

Figure 2. A Sample User Profile on the Intranet