oreilly.comSafari Books Online.Conferences.


Company-Wide Instant Messaging with Jabberd
Pages: 1, 2, 3, 4, 5

Postinstall Configuration

jabberd2 comes with extensive documentation, which should make getting a basic server up and running a fairly pain-free task. Because such documentation and other sources of quality installation how-tos exist, I will concentrate only on the differences from a plain Jabber server install.

MySQL storage back-end configuration

The storage mechanism for jabberd2 stores the user account information, queued messages, and roster lists. It can also store usernames and passwords to provide authentication, but the plans for this configuration are different. Configure the storage mechanism of MySQL as described in the jabberd2 documentation. The schema for the database is part of the jabberd2 distribution package and includes the definitions for the tables active, authreg, disco-items, logout, motd-message, motd-times, privacy-default, privacy-items, private, queue, roster-groups, roster-items, vacation-settings, and vcard.

Although LDAP already contains part of the necessary user information, including the users' full name and department, using LDAP as the storage mechanism is not very straightforward. The first problem is the LDAP schema. To function, Jabber needs some extra object parameters mapped on Active Directory. You could create a new schema, perhaps inheriting as much of the usable data as possible from Active Directory, or extending the existing Active Directory schema to be able to use LDAP for storage as well as authentication. However, not only is it much easier to use MySQL for the storage, but LDAP also is actually unsuited for operations that require fast and concurrent additions of data into the system. Its design primarily promotes central and infrequent modification of data and very fast read-only access, which makes it perfect for tasks such as centralized authentication.

Configuration for authentication via Active Directory

Authentication in this system will come from the Active Directory server using the LDAP authentication mechanism of jabberd2. That way, users will be able to keep using their Active Directory domain username and passwords to authenticate, while users who are no longer with the company will lose their access as soon as someone disables their account in Active Directory.

In order to set up jabberd2 for LDAP authentication, you must fill out the following section of the c2s.xml configuration file. If you installed jabberd2 in /opt/jabberd2, the configuration files will be in the /opt/jabberd2/etc/jabberd directory.

<!-- LDAP module configuration -->
  <!-- LDAP server host and port (default: 389) -->
  <binddn>YOUR LDAP DN</binddn>
  <bindpw>YOUR LDAP PASSWORD</bindpw>
  <!-- LDAP attribute that holds the user ID (default: uid) -->

  <!-- base DN of the tree. You should specify a DN for each
       authentication realm declared in the <local/> section above,
       by using the realm attribute. -->
  <basedn>OU=Company Name,DC=companyname,DC=com</basedn>

Here is a quick rundown of the variables you need to set in the XML file.

  • host--The hostname or IP address of your Active Directory server.
  • binddn--The distinguished name of the username you will use to connect to Active Directory via LDAP.
  • bindpw--The password for the user defined above.
  • uidattr--Unless you are using a directory server other than Active Directory, leave this as sAMAccountName.
  • basedn--The distinguished name for the root of your Active Directory hierarchy.

If you aren't sure what values to put in the LDAP configuration, ask your Active Directory administrator.

Also make sure that the <authreg> section of the same XML file has <module>ldap</module> configured and that all other module selections are commented out.

Creating and updating user accounts and populating rosters

Before users can log in and start using the system, their user account must be available in the system and they should have other users listed in their roster. Here's an external script that writes information directly to the database tables that jabberd2 uses for this purpose.

Although Active Directory comes with a default schema to take care of a corporation's directory needs, each corporation uses it differently. If your business rules require a different schema, modify the Perl script provided here to match your setup.

Sometimes directories include role accounts that are not those of actual employees. In the example environment, these entities do not have their mail field set in their active directory entry. The first assumption is that all real users who need a Jabber account have a mail entry.

The second assumption regards the levels in the organizational structure hierarchy. Most companies have levels within levels of department groups. For instance, a technical support group may have two groups for two separate office locations within it. In order to make the script simpler, I assumed that no subdepartments exist in the hierarchy.

Please keep in mind that this script is a tool to aid you in writing your own script. Although it worked in my environment, providing support for the script itself is outside the scope of this article. You can also download this script directly.

#!/usr/bin/perl -w
#   Oktay Altunergil -  Aug, 2nd 2005
#   This script is released in the hopes that it will be useful. Please make
#   sure you modify this script for your own purposes before running it. 
#   This script searches the windows domain for valid users and creates jabber
#   vcard/roster entries for them. It also adds each user to each other's
#   roster and bypasses the  invitation to add user steps.
#   assumptions:    - every legitimate user has a 'mail' entry
#           - the directory tree looks like:
#                       Root
#               ----------- 
#       Dept1:                  Dept2:          
#       ---------               ---------
#       Employee1               Employee2
#       Employee3               Employee4
use strict;
use Net::LDAP;
use Net::LDAP::Control;
use DBI;

my ($debug, $sth, $entry, $dept, $s, $query, $rosteritem, $rostergroup,
    @roster, $inneritem, $res);

my $basedn     = "YOUR BASE DN HERE";
my $ldapserver = "YOUR LDAP SERVER HERE";
my $disname    = "YOUR DN HERE";
my $ldappass   = "PASSWORD FOR THE ABOVE DN HERE";

my $dbuser     = "YOUR MYSQL USERNAME";
my $dbpass     = "YOUR MYSQL PASSWORD";
my $dbname     = "MYSQL DATABASE NAME";

my $dbh  = DBI->connect("DBI:mysql:database=$dbname;host=localhost",
                        $dbuser, $dbpass); 

my $ldap = Net::LDAP->new( $ldapserver, debug => 0 ) or die "$@";
my $mesg = $ldap->bind ($disname, password => $ldappass, version => 3 )
    or die "Cannot bind to ldap server. $!";

die $mesg->error if $mesg->code;

# query the department names
my $search = $ldap->search(
             base   => "$basedn",
             scope  => "one",
             filter => "(&(objectClass=organizationalUnit) )" );

# fetch the department names
foreach $entry ($search->entries) {
    $dept = $entry->get_value('name');
    print "\n\n Dept: $dept \n\n";

    fetchperson("ou=$dept,$basedn", "one", "$dept");

    # query the first department level names
    $s = $ldap->search(
        base   => "ou=$dept,$basedn",
        scope  => "one",
        filter => "( & (objectClass=organizationalUnit) )" );
    die $s->error if $s->code;


# delete everything in roster-items and roster-groups
# start with a clean page
$query = "DELETE FROM `roster-items`";
$dbh->do("$query") or die ("Cannot delete roster-items!\n");
$query = "DELETE FROM `roster-groups`";
$dbh->do("$query") or die ("Cannot delete roster-groups!\n");
# now that the @roster array is ready, populate the rosters
foreach $rosteritem (@roster)
    print STDERR "\n\n$rosteritem->{jid} is in $rosteritem->{role}" if $debug;
    foreach $inneritem (@roster) {
    if($debug) { print STDERR "\n|_____$inneritem->{jid}"
        unless $rosteritem->{'jid'} eq $inneritem->{'jid'} ; }
    # the following two statements add the roster-groups (Dept Names or Buddy
    # Groups etc) and also take care of adding each user to every users'
    # rosters without the need to authorize them manually
    $query = "INSERT INTO `roster-items` SET
            `collection-owner` = " . $dbh->quote($rosteritem->{'jid'}) . ",
            jid    = " . $dbh->quote($inneritem->{'jid'}) . ",
            name   = " . $dbh->quote($inneritem->{'name'}) . " ,
            `to`   = 1,
            `from` = 1,
            ask    = 0 ";
    $sth = $dbh->prepare($query);
    $sth->execute or die("Cannot execute query! ($!)");

    $query = "INSERT INTO `roster-groups` SET
            `collection-owner` = " . $dbh->quote($rosteritem->{'jid'}) . ",
            jid = " . $dbh->quote($inneritem->{'jid'}) . ",
            `group` = " . $dbh->quote($inneritem->{'rostergroup'}) ;
    $sth = $dbh->prepare($query);
    $sth->execute or warn("Cannot add roster group! ($!)");

#### SUBS ####

sub updatevcard {
    $res = shift;
    $rostergroup = shift;

    my $collection_owner = $res->get_value('sAMAccountName') . '@' .
    my $email            = $res->get_value('mail');
    my $org_orgname      = $res->get_value('l');
    my $org_orgunit      = $res->get_value('department');
    my $tel              = $res->get_value('telephoneNumber');
    my $title            = $res->get_value('title');
    my $role             = $res->get_value('description');
    my $nickname         = $res->get_value('givenName');
    my $adr_locality     = $res->get_value('physicalDeliveryOfficeName');
    my $adr_region       = $res->get_value('st');
    my $fn               = $res->get_value('displayName');

    $query               = "REPLACE INTO vcard SET 
            `collection-owner` = " . $dbh->quote($collection_owner) . ",
            fn                 = " . $dbh->quote($fn) . ",
            nickname           =  " . $dbh->quote($nickname) . ",
            email              = " . $dbh->quote($email) . ",
            title              = " . $dbh->quote($title) . ",
            role               = " . $dbh->quote($role) . ", 
            `adr-locality`     = " . $dbh->quote($adr_locality) . ",
            `adr-region`       = " . $dbh->quote($adr_region) . ",
            `org-orgname`      = " . $dbh->quote($org_orgname) . ",
            `org-orgunit`      = " . $dbh->quote($org_orgunit) . ",
            `desc`             = " . $dbh->quote($rostergroup) ; 

    $sth = $dbh->prepare($query) or die("Cannot prepare database query! ($!)"); 
    $sth->execute or die("Cannot execute database query! ($!) \n $query");
    print STDERR $collection_owner . "\n" if $debug;

    push @roster , {"jid" => $collection_owner, "name" => $fn,
                    "rostergroup" => $rostergroup}; 

sub fetchperson {
    my $searchdn    = shift;
    my $scope       = shift;
    my $rostergroup = shift;
    my ($pers, $person);

    # query the user names
    my $us = $ldap->search(
        base   => "$searchdn",
        scope  => "$scope",
        filter => "( & (objectClass=person) (mail=*) )" );
    die $us->error if $us->code;

    # fetch the usernames
    foreach $pers ($us->entries) {
        $person = $pers->get_value('cn');
        print "\t\t|___" . $person . "\n";

As you can see, the script completely removes all roster data before updating it with the latest information from LDAP. In some cases, such as when you want to be able to allow your users to make persistent changes to their vCards themselves, this might not be desirable. In that case, you would have to modify the script to make it smarter.

Pages: 1, 2, 3, 4, 5

Next Pagearrow

Sponsored by: