Setting up Horde Imp

Note: The following is part of a series of steps to setup an email server using Exim 4.x, with imap and webmail access. It will use winbind to get user information from an NT server. If you found this page via a search engine it may not cover what you need or you may need to start at the beginning to understand everything I have done.

(Note: These instructions reference software that is now possibly much newer with many new or different configuration options. This page is being left up for reference.)

See http://www.horde.org/imp/ for much more information. RedHat users may also want to look at this how-to: http://www.geocities.com/oliversl/imp/

Quick steps:

  1. Download and unpack
  2. Edit php.ini.
  3. Install and configure Horde.
  4. Install and configure IMP.
  5. Install and configure Turba.
  6. Possible problems with Turba.
  7. Setup apache so http://your_web_server/webmail/ goes to imp.
  8. Change the "Welcome to Horde".
  9. Customizing
  10. Thoughts.

While I've somewhat decided not to use Horde (jscript, ldap slowness) I do think it is a great package. I'm leaving this up for reference.

Note I am using Red Hat 8.0 with apache 2.0.4 (httpd-*.rpm) and php 4.2.2. I assume you have apache at least minimally configured. I did have trouble with logins (got reason=session, would reload page and logs showed login & out of IMAP), but I had been playing with a lot of things on my test system. After reinstalling it everything works.

1) Download and unpack

Get the tar.gz from here: http://www.horde.org/imp/ (I'm using 3.1)
You will also need the Horde Application Framework, which can be found here: http://www.horde.org/horde/ (I'm using 2.1)
For contacts you need Turba (SQL or LDAP). It is here: http://www.horde.org/turba/ (I'm using 1.1)
Note there are RPMs available for RH 6 and 7 at the above site. I am not using those.
And you will need PEAR, which comes with the PHP rpm. The how-to listed at the top has how to update if needed.
For spell checking you will need Ispell or Aspell, which are available from the RH rpms.

Unpacking the files:
gunzip name.tar.gz
tar -xvf name.tar

Don't forget you can remove the tar file (rm *.tar) when you are done or place it elsewhere for backup. And read the README files in the name/docs directory.

2) Edit php.ini

These settings are needed:
file_uploads = On
short_open_tag = On

extension=imap.so

More may be needed, but those are the ones I know of for my setup. Restart apache:
service httpd restart

3) Install and configure Horde

If you unpacked the tar in the /tmp directory, like I did, or elsewhere you now need to move it into the web server directory. Example:
mv /tmp/horde-2.1 /var/www/html/horde

See the INSTALL file in the horde/docs directory for the note about databases.

Cd into the path/horde/config directory and make copies of all the *.dist files without the .dist extension. Example:
for foo in *.dist; do cp $foo `basename $foo .dist`; done

Now, edit the horde.php file. Here are some of my settings, though not all available are shown:

In the "General Horde Settings" section I set:
$conf['use_ssl'] = 1;
but this was after I got a working config with http. This force https.

In the "Horde Authentication" section I set:
$conf['auth']['driver'] = 'imap';
$conf['auth']['params']['dsn'] = '{localhost:143/imap}INBOX.';

note the period after INBOX. This is for Courier.

In the "Preference System Settings" section I set:
$conf['prefs']['driver'] = 'none';
For the first bit of testing. Once things were working I set it to:
$conf['prefs']['driver'] = 'session';
so I could explore the options that would be available to users. I will use mySQL eventually. (note: session caused a problem with turba, see below)

In the "Mailer" section I set:
$conf['mailer']['type'] = 'smtp';
$conf['mailer']['params'] = array('host' => 'localhost');

The reason for this is some mailscanners can only scan smtp sessions. This works nicely with Exim too.

Note: if you are going to use SMTP, make sure the Pear modules Net_Socket and Net_SMTP are installed. Net_Socket, which should be installed first, is newer than what Net_SMTP expects. So to install Net_SMTP use:
pear install -n Net_SMTP-version.tgz

Next I edited the registry.php. Here are some of my settings, though not all available are shown:

In the "Handlers" section:
$this->registry['auth']['login'] = 'imp';
$this->registry['auth']['logout'] = 'imp';

In the "Application registry" section I uncommented the lines for IMP. I uncommented the lines for turba once it was installed.

There are other files in the config directory that you may need to change.

Now protect those files by setting the owner to root, the group to the web server's group:
chown -R root /var/www/html/horde
chgrp -R apache /var/www/html/horde

and making the config files read only:
chmod 0440 path/horde/config/*
Note: I used 0640 so root could still edit them.

Now test by going to:
http://your_web_server/horde/test.php
Look at what the page says and pay attention to any warnings. I needed the Log module of Pear, as noted by the horde test page. Go here: http://pear.php.net/ and in the search for packages type in what you need. Download it to your machine in TGZ format. Now run:
pear install name.tgz

4) Install and configure IMP

If you unpacked the tar in the /tmp directory, like I did, or elsewhere you now need to move it into the web server directory. Example:
mv /tmp/imp-3.1 /var/www/html/horde/imp

Cd into the path/horde/imp/config directory and make copies of all the *.dist files without the .dist extension. Example:
for foo in *.dist; do cp $foo `basename $foo .dist`; done

Now, edit the conf.php file. Here are some of my settings, though not all available are shown:

In the "External Utilities" section I set:
$conf['utils']['spellchecker'] = 'aspell';
once aspell was installed.

In the "Menu Settings" section I set:
$conf['menu']['apps'] = array('turba');
after turba was installed.

In the "User Capabilities and Constraints" section I set:
$conf['user']['allow_resume_all_in_drafts'] = true;
$conf['user']['select_sentmail_folder'] = true;

In the "Mail Server Settings" section I set:
For testing (which allows you to play with what is needed in servers.php):
$conf['server']['server_list'] = 'none';
$conf['server']['change_server'] = true;
$conf['server']['change_port'] = true;
$conf['server']['change_protocol'] = true;
$conf['server']['change_folders'] = true;

For working (because I only have one server):
$conf['server']['server_list'] = 'hidden';

In the "Mailbox Settings" section:
$conf['mailbox']['from_link'] = 'compose';
$conf['mailbox']['show_attachments'] = true;

In the "Message Settings" section:
$conf['msg']['append_trailer'] = false;

Next, edit the servers.php file or play first with the test settings in conf.php, "Mail Server Settings" show above. Here are some of my settings for Courier, though not all available are shown:

$servers['imap'] = array(
    'name' => 'IMAP Server',
    'server' => 'localhost',
    'protocol' => 'imap/notls',
    'port' => 143,
    'folders' => '',
    'namespace' => 'INBOX.',
    'maildomain' => 'example.com',
    'smtphost' => 'localhost',
    'realm' => 'example.com',
    'preferred' => ''
);

There are other files in the config directory that you may need to change.

Now protect those files by setting the owner to root, the group to the web server's group:
chown -R root /var/www/html/horde/imp
chgrp -R apache /var/www/html/horde/imp

and making the config files read only:
chmod 0440 path/horde/imp/config/*
Note: I used 0640 so root could still edit them.

Now test by going to:
http://your_web_server/horde/imp/test.php
Note: I never got this page to work with Courier. I believe it is because it uses INBOX without the period. I used the testing configuration specified above to play with what should go in the server settings and just went directly to:
http://your_web_server/horde/imp/

5) Install and configure Turba

If you unpacked the tar in the /tmp directory, like I did, or elsewhere you now need to move it into the web server directory. Example:
mv /tmp/turba-1.1 /var/www/html/horde/turba

Cd into the path/horde/imp/config directory and make copies of all the *.dist files without the .dist extension. Example:
for foo in *.dist; do cp $foo `basename $foo .dist`; done

Now, edit the conf.php file. Not much there, eh?

Next, edit the sources.php. I'm only using LDAP on the localhost at the moment with no password and no changing allowed:

$cfgSources['localldap'] = array(
    'title' => 'Shared Directory',
    'type' => 'ldap',
    'params' => array(
        'server' => 'localhost',
        'port' => 389,
        'root' => 'dc=example,dc=com,ou=example',
        'bind_dn' => '',
        'bind_password' => '',
        'dn' => array('cn'),
        'objectclass' => 'person',
        'version' => 3
    ),
    'map' => array(
        '__key' => 'dn',
        'name' => 'cn',
        'email' => 'mail',
        'company' => 'o',
        'workPhone' => 'telephonenumber'
    ),
    'search' => array(
        'name',
        'company'
    ),
    'strict' => array(
        'dn'
    ),
    'public' => true,
    'readonly' => true,
    'admin' => array(),
    'export' => false
);

The line 'export' does not relate back to the options of letting users import & export address books. What it means is allow it to be browseable.

If you use any types in the 'map' section that are not in attributes.php, you must add them. Example: If I had called 'workPhone' as 'phone' I would have had to edit attributes.php and add something like:

$attributes['phone'] = array(
    'type' => 'phone',
    'desc' => _("Phone")
);

Here are some of my additions to attributes.php (note the order they are in this file is the order they appear, not the order they are called in sources.php):

$attributes['poBox'] = array(
    'type' => 'multiline',
    'desc' => _("PO Box")
);
 
$attributes['city'] = array(
    'type' => 'text',
    'desc' => _("City")
);
 
$attributes['state'] = array(
    'type' => 'text',
    'desc' => _("State")
);
 
$attributes['zip'] = array(
    'type' => 'text',
    'desc' => _("Zip")
);
 
$attributes['country'] = array(
    'type' => 'text',
    'desc' => _("Country")
);
 
$attributes['category'] = array(
   'type' => 'text',
   'desc' => _("Category")
);

To control what shows for the return results, edit prefs.php in the section "columns to show". For mine, to show company and email from the results returned from localldap:

// columns to be displayed
$_prefs['columns'] = array(
    'value' => "localldap\tcompany\temail",
    'locked' => false,
    'shared' => false,
    'type' => 'implicit'
);

The "\t" starts a new field. If you have multiple address sources, list the next one with a "\n". Like:

'value' => "source1\tfield1\tfield2\nsource2\tfield1",
Also, displaying the name field is a given.

Note that there is a different set of controls for fields to search for the address book you get by accessing from the compose window of Imp. These settings are located in imp/config/prefs.php under the "Address book preferences" section at the end. Same format as above and again name is a given.

There are other files in the config directory that you may need to change.

If you are using imp, go back to it's config directory and edit the conf.php:

In the "Menu Settings" section set:
$conf['menu']['apps'] = array('turba');

Don't forget to uncomment turba in horde/config/registry.php, in the "Application registry" section.

Now protect those files by setting the owner to root, the group to the web server's group:
chown -R root /var/www/html/horde/turba
chgrp -R apache /var/www/html/horde/turba

and making the config files read only:
chmod 0440 path/horde/turba/config/*
Note: I used 0640 when I needed root to edit them.

Now give it a whirl by going to:
http://your_web_server/horde/turba/

6) Possible problems with Turba

If you have problems occasionally or often, check your apache error logs (mine is in /var/log/httpd/error_log). If you show some seg faults, see this bug report (says fixed with latest snapshot at the end): http://bugs.php.net/bug.php?id=19482

Warning: What follows is from someone who knows just enough to get in and sometimes out of trouble, but not enough to fix things themselves.

My symptom was IE just sat there, the httpd process was hogging the cpu, and there was nothing in the error_log. Yet I was able to use this in a php file to successfully test LDAP via php (ie I saved this as test.php in the turba directory and edited it as required):

<?php
$ds = ldap_connect("ldap://example.com_or_localhost/");
echo "connect result is ".$ds."<p>";
if($ds) {
 $r=ldap_bind($ds);
 echo "Bind result is ".$r."<p>";
 $sr=ldap_search($ds, "dc=example,dc=com,ou=example", "sn=surname_of_a_person_in_database");
 echo "Search result is ".$sr."<p>";
 echo "Number of entires returned is ".ldap_count_entries($ds,$sr)."<p>";
 echo "Getting entries ...<p>";
 $info = ldap_get_entries($ds, $sr);
 echo "Data for ".$info["count"]." items returned:<p>";
 for ($i=0; $i<$info["count"]; $i++) {
  echo "dn is: ". $info[$i]["dn"] ."<br>";
  echo "first cn entry is: ". $info[$i]["cn"][0] ."<br>";
  echo "first mail entry is: ". $info[$i]["mail"][0]."<p>";
 }
}
echo "Closing connection";
ldap_close($ds);
?>

I was also able to successfully go directly to search.php or browse.php (ie http://your_web_server/horde/turba/seach.php). So I got to playiing with index.php by rem'ing out the line:
header('Location: ' . Horde::applicationUrl($uri, true));
and adding:
echo "uri=".$uri;

Which showed me that the part above was not working correctly. Having exhausted my knowledge of php I changed the section as follows:

...
if ($turba_configured) {
include_once TURBA_BASE . '/lib/base.php';
 
// $uri = $prefs->getValue('initial_page');
// if (!empty($_SERVER['QUERY_STRING'])) {
// $uri .= '?' . $_SERVER['QUERY_STRING'];
// }
 
$uri = "search.php";
header('Location: ' . Horde::applicationUrl($uri, true));
echo "\n";
exit;
...

It is probably something in my setup files somewhere, but until I figure it out this works for me.
Update: When I set $conf['prefs']['driver'] = 'none'; in horde.php it worked fine, so it must have something to do with session preferences?

I've also updated to the latest CVS of turba (with tag RELENG_1) too. See http://www.horde.org/source/

Another odd thing I've found with turba, and posted to the list (S. Yoder), is the search queries passed. I turned on LDAP logging of connections, operations, and results (level 256) and here is what I see:
- bind
- search for the requested name using the sources.php base (scope=2)
- search for the sources.php filter using the dn of the result from the previous search (scope=0)
- unbind
- bind
- search for the sources.php filter using the sources.php base (scope=2)
- same search (scope=0)
- a search on each dn returned from above for the sources.php filter (scope=0) [That's 2500 searches for my data!]
- a search for the sources.php filter using the dn of the result returned from the first bind
- unbind

Not having gotten any response from the list yet I started trying to read some of the code and figure out what is going on. As far as I can tell something is calling turba/lib/Driver/ldap.php twice (note the two binds), what it is I can't find, which is triggering the 'else' case (line 144 in r1.11.2.6) when turba/config/sources.php does not define 'filter' or defines it as null:

099|function search($criteria, $fields, $strict_fields = array(), $match = TURBA_SEARCH_AND)
    ...
107|  if (count($criteria) > 0) {
08|    $filter = $glue;
       ...
144|  } else {
145|    $filter = '(objectclass=*)';
146|  }
      ...

If 'filter' is not null then lines 149-151 are used. Shown for reference:

148|  /* Add source-wide filters, which are _always_ AND-ed. */
149|  if (!empty($this->params['filter'])) {
150|    $filter = '(&' . '(' . $this->params['filter'] . ')' .
$filter . ')'
151|  }

By rem'ing out line 144 and 145 (and the debuging on lines 163-165), if 'filter' is null, that particular first search fails in the second bind, but the second search does not. The two binds still happen, but now instead of iterating all of my entries it returns the results very quickly. Speed was the only reason I bothered to dig into what was happening, as it was taking 6 minutes (with ldap logging at 256, ~2 minutes with it off) to return from a search I knew only had one result. When searching from Imp's "Address Book", there is no problem and only one bind happening.

The bad part about this 'solution' is browsing ldap address books no longer works. However, since Turba merely iterates everything at this time, rather than say 25 at a time, allowing folks to browse 2500+ contacts is not something I want. Therefore I can live with this work around, but others may not be able to. Browsing mySQL address books should still be fine, but I don't have any up at the moment to test. If the horde team fixes this or I find time to dig deeper and fix it myself I will update this information.

7) Setup apache so http://your_web_server/webmail/ shows imp

In httpd.conf add (I've also included remapping turba too):

Alias /horde/ /path/horde/
Alias /webmail/ /path/horde/imp/
Alias /contacts/ /path/horde/turba/
# Still deciding if I need this
# I think I will have to do a virtual
# server if so
#DocumentRoot /var/www/horde/

Then in horde/config/registry.php, in the "Application registry" section:

$this->applications['horde'] = array(
    'fileroot' => dirname(__FILE__) . '/..',
    'webroot' => '',
    'initial_page' => 'login.php',
    'icon' => '/horde/graphics/home.gif',
    'name' => _("Horde"),
    'allow_guests' => true,
    'show' => true,
    'templates' => dirname(__FILE__) . '/../templates',
    'cookie_domain' => $GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME'],
    'cookie_path' => '/',
    'server_name' => $GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME'],
    'server_port' => $GLOBALS['HTTP_SERVER_VARS']['SERVER_PORT']
);
 
$this->applications['imp'] = array(
    'fileroot' => dirname(__FILE__) . '/../imp',
    'webroot' => '/webmail',
    'icon' => $this->applications['horde']['webroot'] .
    '/imp/graphics/imp.gif',
    'name' => _("Mail"),
    'allow_guests' => false,
    'show' => true
);
 
$this->applications['turba'] = array(
    'fileroot' => dirname(__FILE__) . '/../turba',
    'webroot' => '/contacts',
    'icon' => $this->applications['horde']['webroot'] .
    '/turba/graphics/turba.gif',
    'name' => _("Addressbook"),
    'allow_guests' => false,
    'show' => true
);

And finally restart your web server:
service httpd restart

8) Change the "Welcome to Horde"

Believe it or not, but this eluded me and I thought I would have to create a custom en_US language file. Instead it is buried in horde/config/registry.php in the "Application registry" section:

$this->applications['horde'] = array(
   'fileroot' => dirname(__FILE__) . '/..',
   'webroot' => '/horde',
   'initial_page' => 'login.php',
   'icon' => '/horde/graphics/home.gif',
   'name' => _("Change Me"),
   ...

9) Customizing

All I've done so far is to change some colors. For that you need to edit the html.php files. The main one is in horde/config. Colors can be in hex:
#nnnnnn
rgb:
RGB(n,n,n)
or literal:
red

Tip: Do a web search for "html colors" and you'll turn up lots of resources on this.

Tip: If you have changed colors and a reload of the web page does not display them, you may need to clean out your web browser's cache (or just remove the css.* files from it). The method for doing this depends on your browser.

10) Thoughts

While Horde/Imp/Turba does take more configuration than Squirrelmail, I do rather like it. Yet, if you were new to *nix I'd recommend Squirremail, just because it has a configuration interface and can store preferences without setting up a SQL database or LDAP server.

However, I do wish that the address book was consistent between accessing it direct from path/turba/ or the "Address book" link in Imp and the "Address Book" link in the Imp compose window.