Thursday, July 19, 2007

Zimbra 4.5.6 + Ubuntu 7.04

How to Install Zimbra (4.5.6) on Ubuntu Feisty


Zimbra Collaboration Suite (ZCS) is a groupware product created by Zimbra Inc, located in California, USA. It consists of both client and server components. There are two versions of Zimbra available: an open-source version, which is supported by the community, and a commercially supported version with closed-source components. In this article, I'll try to explain how to install the free (open-source) version of ZCS on an Ubuntu system Festy although Ubuntu Feisty is not supported at this time.

So what's ZCS more exactly? ZCS is a full-featured collaboration suite, which supports email and group calendars using an Ajax web interface that enables tool tips, draggable items and right click menus in the user interface. There are also some advanced searching capabilities included, as well as date relations, online document authoring and a full administration interface. The ZCS server works well with many open source projects such as Postfix, MySQL, OpenLDAP and it also acts as an IMAP and POP3 server.

That's why if you want install Zimbra you have to stop Postfix, MySQL, OpenLDAP, Apache 2, Tomcat and any other server application that could conflict with it.
You could also have some problem if there is a firewall installed, better disable it too. So first stop:

SHELL

sudo /etc/init.d/postifix stop
sudo /etc/init.d/mysql stop
sudo /etc/init.d/apache2 stop
sudo /etc/init.d/openldap stop

These and the other related services (like Tomcat or Spamassassin).

SHELL

sudo update-rc.d -f mysql remove
sudo update-rc.d -f apache2 remove
sudo update-rc.d -f postfix remove
sudo update-rc.d -f openldap remove

Following the Zimbra Installation Manual finally i could install the Collaboration Suite:

SHELL

tar xzvf zcs-4.5.6_GA_1044.UBUNTU6.tgz
cd zcd
sudo apt-get install curl fetchmail libpcre3 libgmp3c2 libexpat1 libxml2 libtie-ixhash-perl
sudo ./install.sh

You could experience some problem with dependencies:

OUTPUT

Checking for prerequisites...

NPTL...FOUND

sudo...MISSING
libidn...MISSING

curl...MISSING

fetchmail...MISSING

gmp...MISSING
/usr/lib/libstdc++.so.5...FOUND

###ERROR###
One or more prerequisite packages are missing.
Please install them before running this installer.
Installation cancelled.

You can go crazy searching the dependencies, but you hava the problem is not specific to dependencies.
The dependencies check can not be execute correctly, because the Zimbra Get Platform Tag script in the installation procedure.
The trick is modify the /etc/lsb-release:

SHELL

$ sudo vi /etc/lsb-release


inside you'll find something like this:

/etc/lsb-release

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=7.04

DISTRIB_CODENAME=feisty
DISTRIB_DESCRIPTION="Ubuntu 7.04"

and correct this:

DISTRIB_RELEASE=7.04


in this:

DISTRIB_RELEASE=6.04


now you can continue with the installation and the prerequisites check will return success:

OUTPUT
Checking for prerequisites...
NPTL...FOUND
sudo...FOUND sudo-1.6.8p12-4ubuntu5
libidn11...FOUND libidn11-0.6.5-1build1
curl...FOUND curl-7.15.5-1ubuntu2.1
fetchmail...FOUND fetchmail-6.3.6-1ubuntu2
libpcre3...FOUND libpcre3-6.7-1ubuntu2
libgmp3c2...FOUND libgmp3c2-2:4.2.1+dfsg-4build1
libexpat1...FOUND libexpat1-1.95.8-3.4build1
libxml2...FOUND libxml2-2.6.27.dfsg-1ubuntu3
libstdc++6...FOUND libstdc++6-4.1.2-0ubuntu4
libstdc++5...FOUND libstdc++5-1:3.3.6-15ubuntu1
openssl...FOUND openssl-0.9.8c-4build1


Anyway the installation problems I found wasn't terminated.
Starting the installation process, the manual show how to modify the hosts file.
ZCS Single Server Quick Start, Network Edition 4.5: "Make sure that FQDN entry in /etc/hosts appear before the hostnames. If this is missing, the creation of the Zimbra certificate fails. The FQDN entry should look like this example."

/etc/hosts
127.0.0.1 localhost.localdomain localhost
your.ip.address FQDN yourhostname


This is an important moment of installation. If you specify there the public ip address of your FQDN but the mail server don't bind directly this ip address, because it is behind a firewall, you could have a big problem after, when the openldap server starts and try to bind the 389 port on that address. In other words, the installation won't continue, because openldap can't start. And there isn't any damn error message that help you to understand that!
So I specified in the hosts file the Ip address of network interface on internet.
But another choice is to change the ip address used by openldap, and you have to do that using the zimbra user:

SHELL

zmlocalconfig -e ldap_url=ldap://0.0.0.0:389



Looking at installation log I found this error:

/opt/zimbra/bin/zmfixperms.sh: No such file or directory


This works:

sudo /opt/zimbra/libexec/zmfixperms

When everything seems up and running, finally you could try to connect at the administration panel. Well, I don't understood why, but the admin password won't work.
To specify a new password you have to submit this command as zimbra user:

SHELL

zmprov sp admin@domain.name password


I hope this can help to enjoy this wonderful suite.

Friday, July 13, 2007

Howto: Java and HTTPS with a self signed SSL Certificate

This code show how to connect in Java via HTTPS. The following code should works easily if you try to connect to a valid and verifiable source, like Google Service Authorization login.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
/**
* HTTPS Connection Sample
*
*
@author freedev
*
@version 0.1
*
*/

public class HttpsSample {
public static void main(String[] args) {
try {
String urlSite = "https://www.google.com/accounts/ServiceLoginAuth";
URL url = new URL(urlSite);
URLConnection conn = url.openConnection();
// Retrieve information from HTTPS: GET
InputStream istream = conn.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(istream));
String curline;
while ((curline = in.readLine()) != null) {
System.out.println(curline);
}
} catch (IOException e) {
System.out.println("IO exception = " + e);
}
}
}

But if you connect to an unverifiable source, you should receive this exception:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

or
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
This could happen easily, for example if you connect to a web server where SSL certificate is self signed. I read: There is no "workaround" for this, i.e. to disable the authentication step; the CA for the SSL cert must be trusted, and the common name must match, or the SSL connection will fail. Follow this link if you want know more about.
I'm not pretty sure about this, but I think you could extend the X509TrustManager creating a new one to avoid the problem, but heavily breaking the SSL protocol safety.

To complete the authentication step with success, you need to import the certificate and after you can proceed with a correct connection.
To import the certificate you can use the InstallCert.java (found here), the certificate will be stored locally in the jssecacerts file.

/*
* @(#)InstallCert.java 1.1 06/10/09
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved. Use is subject to
* license terms.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class InstallCert {
public static void main(String[] args) throws Exception {
String host;
int port;
char[] passphrase;
if ((args.length == 1) || (args.length == 2)) {
String[] c = args[0].split(":");
host = c[0];
port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
String p = (args.length == 1) ? "changeit" : args[1];
System.out.println("Using passphrase " + p);
passphrase = p.toCharArray();
} else {
System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
return;
}
File file = new File("jssecacerts");
if (file.isFile() == false) {
char SEP = File.separatorChar;
File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
file = new File(dir, "jssecacerts");
if (file.isFile() == false) {
file = new File(dir, "cacerts");
}
}
System.out.println("Loading KeyStore " + file.getAbsolutePath() + "...");
InputStream in = new FileInputStream(file);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
context.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory factory = context.getSocketFactory();
System.out.println("Opening connection to " + host + ":" + port + "...");
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.setSoTimeout(10000);
try {
System.out.println("Starting SSL handshake...");
socket.startHandshake();
socket.close();
System.out.println();
System.out.println("No errors, certificate is already trusted");
} catch (SSLException e) {
System.out.println();
e.printStackTrace(System.out);
}
X509Certificate[] chain = tm.chain;
if (chain == null) {
System.out.println("Could not obtain server certificate chain");
return;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println();
System.out.println("Server sent " + chain.length + " certificate(s):");
System.out.println();
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN());
System.out.println(" Issuer " + cert.getIssuerDN());
sha1.update(cert.getEncoded());
System.out.println(" sha1 " + toHexString(sha1.digest()));
md5.update(cert.getEncoded());
System.out.println(" md5 " + toHexString(md5.digest()));
System.out.println();
}
System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
String line = reader.readLine().trim();
int k;
try {
k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
} catch (NumberFormatException e) {
System.out.println("KeyStore not changed");
return;
}
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
System.out.println();
System.out.println(cert);
System.out.println();
System.out.println("Added certificate to keystore 'jssecacerts' using alias '" + alias + "'");
}
private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int b : bytes) {
b &= 0xff;
sb.append(HEXDIGITS[b >> 4]);
sb.append(HEXDIGITS[b & 15]);
sb.append(' ');
}
return sb.toString();
}
private static class SavingTrustManager implements X509TrustManager {
private final X509TrustManager tm;
private X509Certificate[] chain;
SavingTrustManager(X509TrustManager tm) {
this.tm = tm;
}
public X509Certificate[] getAcceptedIssuers() {
throw new UnsupportedOperationException();
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new UnsupportedOperationException();
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
this.chain = chain;
tm.checkServerTrusted(chain, authType);
}
}
}


Finally when you connect, you need to specify to java where is the file where the certificate is stored. And you can do this adding this parameter:
-Djavax.net.ssl.trustStore=jssecacerts

The last suggestion: pay attention about the hostname in the url and hostname stored inside the certificate: they have to be the same name. If they are different (even though they point to the same ip address), you'll receive the following error:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching www.bar.org found


In other words if you have a certificate for the www.foo.org site, you cannot connect to https://www.bar.org site.
You must connect to https://www.foo.org.