Connecting your web applications to the mail services that support them is critical to any modern web application, and the ease of use of Mac OS X Leopard Server makes this possible. This article shows you how to use Leopard Server Open Directory server with PHP and Ruby to gain read and write access to mailing lists and contact information from Open Directory. Using Open Directory, which provides standards-based directory and network authentication, you can integrate user and group information with a web application and administer your directory service from a web application. This is useful, for example, on a corporate intranet, where directory information can be used to define access control and store settings for each user.
This article is designed for web developers interested in leveraging Mac OS X Open Directory within their Internet and intranet web applications. Basic knowledge of either PHP or Ruby is expected, as well as basic knowledge of how email works.
The Leopard Server Mail Services is an open standards compatible stack of components chosen for maximum interoperability with various email clients. At their core, they are built using popular open source components such as Postfix and OpenLDAP. Mac OS X Leopard Server provides an easy-to-use interface for configuration of these mail services. It also allows the manual configuration of these services for your advanced setup needs, which is a powerful feature for administrators that have a complex network.
Let's take a look at the various components that make up the mail services in Mac OS X Leopard Server, looking first at Open Directory, which is the central storage for users and groups of mail services.
Leopard Server Mail Services is driven by Open Directory, which stores the definitions for users and groups, as well as other devices on a network. Open Directory implements version 3 of the LDAP standard and is based upon OpenLDAP, an open source LDAP implementation. It also ties together an authentication module consisting of Apple Password Server and Kerberos, which allows users to authenticate against the directory service directly. Open Directory can be integrated with other LDAP servers, such as Active Directory or Network Information Service (NIS), or any other LDAPv3 compatible server.
Mail groups are a way to put users together based upon common attributes. For example, all of your accounting department can be in one group, HR in another, and users can be part of multiple groups. This article leverages how in Mac OS X Leopard each group is assigned an email address. This allows you to contact the group as a whole by one email, effectively creating a mailing list for a group of people. This is done by leveraging the built-in Leopard Server Mail Services, which are powered by the user and group definitions in Open Directory.
Mail Services is the base component that stores email in mailboxes, and allows users to connect to their mailboxes using either the IMAP or POP3 protocol. POP3 is an older protocol, which allows users to connect and download their email. However, one shortcoming with POP3 is that once you download the email from the server it is then deleted, which prohibits accessing email from more than one location easily. IMAP does not have this limitation, which makes it advantageous in business situations, allowing business users to access their email on their primary computer as well as a secondary computer or an iPhone.
Simple Mail Transport Protocol (SMTP) is used to deliver email to an intended recipient. It uses Postfix, which is a very modern SMTP server supporting things such as mail relay restrictions (to prevent unauthorized relaying of email messages through the server) and secure access via SSL. SMTP delivers the example mailing list messages to various users in an Open Directory group.
The definitions of mail accounts and mail groups comes from Open Directory, as each mail user and mail group in Leopard Server has an entry in the directory service. This allows network users to have one account to access all network functions, from logging in to file server access to email. It also simplifies maintenance for administrators to control user and groups accounts in one place for an entire network. For this article, it allows you to use the LDAP protocol to get user and group account information. You can also perform other administrative tasks, such as adding and modifying users and groups.
Mailman, which comes with Leopard Server, is free software for managing email discussion lists and newsletters delivered via email. It is the standard in managing email lists, and comes complete with a webmail interface so users can manage their accounts. In previous versions of Mac OS X Server, you used Mailman to handle mailing lists and you weren't able to manage them through Open Directory. Now, while Mailman still exists for legacy reasons, mail groups provide much of the same functionality.
With the use of various open source components that use an open standards API in LDAP, you can connect to Leopard Server Mail Services from many different programming languages. In PHP, you use the LDAP extension, which is built in by default with the PHP on Mac OS X Leopard. If you are compiling PHP yourself, you can follow the instructions at http://www.php.net/manual/en/ldap.installation.php to see how to have LDAP compiled for PHP. For Ruby, you use the Ruby/LDAP extension at http://sourceforge.net/projects/ruby-ldap/.
Using the LDAP extensions of PHP or Ruby allows you to extend your intranet web application to connect to Open Directory. For example, you could have an HR web application to create company hierarchy and department groupings, which in turn can write these changes out to the Open Directory server directly. This can be done easily when using Open Directory to manage your company's network users.
Connecting to the Leopard Server Open Directory is a two part process. You must first connect to the server, and then bind against a specific LDAP directory, such as user account information, group definitions, or a list of devices on a network:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); ?>
As you can see, you first define the directory name you are authenticating against ($dn
). It consists of the username (username, defined as uid=username
), the directory section you are looking in (users, defined as cn=users
), and the domain you are looking in (server.com; when doing LDAP queries separate each part of the domain into a seperate entity). You use this directory name along with a password to bind against that directory.
Now that you have seen how to connect to Open Directory, let's look at doing a query against a directory. Let's begin with a simple example that searches for user information. The ldap_search()
function allows you to do this. It takes an argument of the directory you should be searching in, and filters the results that come back, allowing you to look for a specific attribute. In this example, you search for the uid, and return back the uid attribute:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); $res = ldap_search($conn,$dn,"uid=username",array('uid')); $info = ldap_get_entries($conn, $res); echo $info["count"]." entries returned\n"; ?>
An Open Directory entry for users contains the standard things you would expect to be returned, such as the Full User name. Open Directory also adds other attributes specific to Leopard Server, such as Chat and Mail Settings, which are used by other Leopard Server services. Later in this article you use one attribute, gidnumber (the group id number), to help you find users in a specific group.
Now that the basics are covered, let's look at some specific examples using PHP to manipulate users and groups in Leopard Server Mail Services.
Every group defined in Open Directory is also assigned an email address, so you can send messages to everyone in a group and have it work like a mailing list. This is managed in Open Directory and is accessible to PHP via LDAP, which makes the tasks of viewing members of the mail groups and manipulating them much easier. As shown previously, you can use the PHP LDAP extension to interact with Open Directory to handle the management of the mail groups. This can be useful for intranet web applications, where users can join different groups and manage their own user information from a web-based interface.
In order to view the list of users in a mail group, you can query the group via LDAP. The group id for the group you are looking for in this example is 20, as defined by Open Directory, so you need to pass that in the LDAP query string. You must also restrict the result set of nodes to just provide the username, as you can see here:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); $res = ldap_search($conn,$dn,"(gidnumber=20)",array("uid")); $info = ldap_get_entries($conn, $res); echo $info["count"]." entries returned\n"; var_dump($info); ?>
Now that you see what users are in a mail group, let's explore how to add new users to a mail group.
To add a new user to the mail group, you first add that user into Open Directory. New contacts can be easily added to an LDAP directory service via the ldap_add()
function. Specify a list of attributes for the user, such as name and email address, in the form of an array. Then add the entry into the directory, specifying the directory name to add the entry to:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); // Add user $info["cn"] = "John Jones"; $info["sn"] = "Jones"; $info["mail"] = "jonj@example.com"; $info["objectclass"] = array("person"); $info["uid"] = "jonj"; // add data to directory $r = ldap_add($conn, "cn=John Jones,dc=server,dc=com", $info); ?>
Now that you have added the user into the Open Directory, you can put them into a mail group, which adds them to a mailing list.
Adding the newly created user to the mailing list is as simple as adding the user LDAP query string as an attribute to the mail group in question. Here you use the ldap_mod_add()
function, which allows you to add any attributes to an existing entry in an LDAP directory:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); // now assign to a group $info['member'] = 'uid=jonj,cn=users,dc=server,dc=com'; $res = ldap_mod_add($conn,"cn=groupname,ou=groups,dc=server,dc=com",$info); ?>
Every entry in the mail group has its own entry in the Open Directory server, and you can query them for additional information, such as their real name, department, or any other attribute stored in the directory service.
Given a username, you can query any user in the Open Directory LDAP directory service. Again, you use the ldap_search
functionality to do this, and pass the username of the user you are looking for as a search parameter to ldap_search()
. You can also search against the first name, last name, email address, or full name of the user, as well as by changing the attributes you are searching against:
<?php $dn = 'uid=username,cn=users,dc=server,dc=com'; $password = 'password'; $conn = ldap_connect('localhost') or die("Could not connect.\n"); ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); $ldap_rs = ldap_bind($conn, $dn, $password); $res = ldap_search($conn,$dn,"uid=username"); $info = ldap_get_entries($conn, $res); var_dump($info); ?>
As you can see, working with an LDAP directory service in PHP is very straightforward and easy to do. The LDAP extension is a very powerful tool for interacting with Open Directory directory services, and provides links to leverage already existing user and group information in an application.
Now that you have looked at examples using PHP, let's shift the focus to Ruby. As mentioned earlier, Ruby on Rails is becoming very popular among web developers, and it also provides you with several LDAP libraries. With the ease of building web applications with the Ruby on Rails framework, using LDAP for directory information can help you build web based intranet applications quickly. For this article use the Ruby/LDAP extension to interface with Open Directory. This extension is not included with the Ruby installation in Mac OS X Leopard, so you need to install it separately from http://sourceforge.net/projects/ruby-ldap/. The Ruby/LDAP extension is included with the Ruby One-Click Installer for OS X (http://rubyosx.rubyforge.org/) if you are looking for a simple all-in-one solution.
Keep in mind that the Ruby/LDAP extension uses an object-oriented interface instead of the procedural style interface that the LDAP extension uses in PHP. This has the advantage of making the syntax much cleaner, but doesn't change the basic functionality of dealing with an LDAP directory server very much, which makes the examples in this article very similar to the ones in the previous section. For Ruby/LDAP documentation see http://ruby-ldap.sourceforge.net/rdoc/. So let's take a look at connecting to an LDAP server using Ruby/LDAP:
require 'ldap' dn = 'uid=username,cn=users,dc=server,dc=com' password = 'password' conn = LDAP::Conn.new('localhost', 389) conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) conn.bind(dn, password)
Here you connect to the directory server and then bind against a particular directory that you define.
Viewing a mailing list in Ruby is very similar to how it was done earlier in this article. The search2 method allows you to search the LDAP directory. One difference from PHP is the specification of a scope to search in; for your examples you can keep it in the subtree under the root of the directory service. In addition, Ruby/LDAP will throw an error if too much data is returned, so watch for this and deal with it accordingly. Here's how you view a group list:
require 'ldap' dn = 'uid=username,cn=users,dc=server,dc=com' password = 'password' conn = LDAP::Conn.new('localhost', 389) conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) conn.bind(dn, password) results = conn.search2('dc=server,dc=com', LDAP::LDAP_SCOPE_SUBTREE, "gidnumber=20") if results == LDAP::LDAP_SIZELIMIT_EXCEEDED printf "To Much Data\n" end pp results pp results.controls pp results.referrals results.each { |entry| puts entry }
Results are returned in a hash, similar to how they were returned in PHP with an array. Now let's see how to add new users to the directory.
The Ruby/LDAP extension contains the add
method to add entries to an LDAP directory. This is identical to the PHP example; you define an array of entries similar to the array that was defined before. Then add the new entry via the add method to the LDAP directory:
require 'ldap' dn = 'uid=username,cn=users,dc=server,dc=com' password = 'password' conn = LDAP::Conn.new('localhost', 389) conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) conn.bind(dn, password) newentry = {:cn => "John Jones, :sn => "Jones", :mail => "jonj@example.com", :objectclass => "person", :uid => "jonj"} conn.add(dn,newentry)
Much like with PHP, you can also add the newly created user to a mail group, as shown next.
To add entries with Ruby/LDAP use the LDAP::Mod
class. This class handles all attribute modification, whether you are looking to add, change or delete an entry. Use the modify method here, which takes an array of changes that are LDAP::Mod
instances to make as the second argument:
require 'ldap' dn = 'uid=username,cn=users,dc=server,dc=com' password = 'password' conn = LDAP::Conn.new('localhost', 389) conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) conn.bind(dn, password) reset = [ LDAP.mod(LDAP::LDAP_MOD_ADD, "member", 'uid=jonj,cn=users,dc=server,dc=com')] conn.modify("cn=groupname,ou=groups,dc=server,dc=com", reset)
Now that the user is added to the group, let's look at how to query user information.
To look at the contact information, use the search2 method to query the LDAP directory. You can query by any contact attribute here, such as username, real name, email address, first name or last name:
require 'ldap' dn = 'uid=username,cn=users,dc=server,dc=com' password = 'password' conn = LDAP::Conn.new('localhost', 389) conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) conn.bind(dn, password) results = conn.search2('dc=server,dc=com', LDAP::LDAP_SCOPE_SUBTREE, "uid=username") if results == LDAP::LDAP_SIZELIMIT_EXCEEDED printf "To Much Data\n" end pp results pp results.controls pp results.referrals results.each { |entry| puts entry }
As you can see, you can interact with an LDAP directory as easily with Ruby as you did with PHP. This allows you, the developer, to build web applications in Ruby on Rails that connect to Open Directory with ease.
Leopard Mail Services' use of Open Directory allows you to leverage existing user and group accounts in your web applications, which allows integration with existing directory services. The key to this is the use of the LDAP protocol, which opens the directory using an open standards API to query, add, and modify user and group account information with libraries built into PHP and Ruby.
In this article you saw in detail how to create and manipulate mailing lists under Mac OS X Leopard, which allows you to have web applications that can use mail groups to extend their functionality. While Mailman exists to handle the needs of a mailing list with external users, each group defined in Open Directory has the ability to be used as a mailing list as well. This powerful addition to the groups functionality allows the creation of internal mailing lists to be handled through the LDAP directory service, which both PHP and Ruby have powerful bindings to use.
Posted: 2008-11-07