[Dovecot] calling dovecot exported auth from Java

WJCarpenter bill-dovecot at carpenter.org
Sat Apr 30 20:32:23 EEST 2011


On 4/15/2011 5:36 PM, WJCarpenter wrote:
> As far as I have been able to figure out, dovecot auth always works 
> over a Unix domain socket.  I believe it is not currently possible to 
> operate dovecot auth over an Internet domain (TCP) socket.  Am I correct?
>
> I want to call dovecot's exported authentication from a Java 
> application.  Java doesn't natively know how to talk to a Unix domain 
> socket, so there are inconveniences.  There are 3rd party JNI 
> libraries to allow Java to do it, but I'm not too wild about the idea 
> of using JNI.  My current thinking is to rig up some kind of 
> proxy/shuttle arrangement between a localhost TCP port and the dovecot 
> auth Unix domain socket in the filesystem.  I'm looking at using the 
> more or less standard tool "socat" to do that.  (I'm on a mainstream 
> Linux distribution.)

Here is the solution we came up with.  All of this is, uh, lightly 
tested at this point.  Bug reports welcome (directly or via this mailing 
list if you want.)

First, the "socat" command to connect a localhost TCP socket to the Unix 
domain socket.  This is suitable for somehow running just once since it 
makes its own children for handling each connection instance.

/usr/bin/socat -ly \
   TCP4-LISTEN:1649,bind=localhost,reuseaddr,fork \
   UNIX-CONNECT:/var/run/dovecot/auth-client

We chose port 1649 because it's allocated to the ancient kermit 
protocol, and we're confident we'll never use that here.

Here is the Java class that we use for speaking to the dovecot auth 
process.  It's basically a rework of the C++ code in the exim sources.  
Because we have a Tomcat environment for this, we used a Catalina 
utility for base64 encoding.  If you don't have that, you'll have to 
find one elsewhere (there is no standard base64 encoder thing in Java; 
grrr).  There are some things in here that I don't completely 
understand, but they are in the exim code and (apparently) do no harm.  
You can instantiate one of these objects and then call the doLogin() 
method arbitrarily many times.  As noted, the class is not threadsafe.


package aio.util;
import java.io.*;
import java.net.Socket;

import org.apache.catalina.util.Base64;

/**
  * This class is not threadsafe.
  */
public class DovecotLogin
{
     private boolean TRACE = true;
     private String host;
     private int port;
     private String service;
     private LineNumberReader lineReader;
     private OutputStream outputStream;

     public DovecotLogin()
     {
         this(null, 0, null);
     }

     public DovecotLogin(String host, int port, String service)
     {
         this.host = host != null ? host : "localhost";
         this.port = port > 0 ? port : 1649;
         this.service = service != null ? service : "aiologin";
         if (TRACE)
         {
             System.out.println("CONNECT " + this.host + ":" + this.port 
+ ", service=" + this.service);
         }
         initDovecotConnection();
     }

     private void initDovecotConnection()
     {
         try
         {
             socket = new Socket(host, port);
             int localport = socket.getLocalPort();
             InputStream inputStream = socket.getInputStream();
             lineReader = new LineNumberReader(new 
InputStreamReader(inputStream, "utf-8"));
             outputStream = socket.getOutputStream();
             initialDovecotListenTo();
             initialDovecotSpeakTo(localport);
         }
         catch (Exception e)
         {
             // TODO logme
             e.printStackTrace();
         }
     }

     public void close()
     {
         if (socket != null &&  socket.isConnected())
         {
             try
             {
                 socket.close();
                 socket = null;
             }
             catch (IOException e)
             {
                 // TODO logme
                 e.printStackTrace();
             }
         }
     }

     public boolean doLogin(String userid, String password)
     {
         try
         {
             sayThisLogin(userid, password);
             return readLoginResponse();
         }
         catch (Exception e)
         {
             // TODO logme
             e.printStackTrace();
             return false;
         }
     }

     private boolean readLoginResponse() throws IOException
     {
         String line = lineReader.readLine();
         if (TRACE)
         {
             System.out.println("S< " + line);
         }
         String[] splits = line.split("\t");
         String token1 = splits[0];
         if ("FAIL".equalsIgnoreCase(token1))
         {
             return false;
         }
         if ("OK".equalsIgnoreCase(token1))
         {
             return true;
         }
         throw new IOException("unexpected response received from 
dovecot auth: " + line);
     }

     private void initialDovecotListenTo() throws IOException
     {
         boolean done = false;
         while (!done)
         {
             String line = lineReader.readLine();
             if (TRACE)
             {
                 System.out.println("S< " + line);
             }
             // dovecot auth lines are tab-separated stuff
             String[] splits = line.split("\t");
             String token1 = splits[0];
             if ("DONE".equalsIgnoreCase(token1))
             {
                 done = true;
             }
             else if ("VERSION".equalsIgnoreCase(token1))
             {
                 String token2 = splits[1];
                 if (! "1".equals(token2))
                 {
                     throw new IOException("dovecot auth version 
mismatch; expected 1, saw " + token2);
                 }
             }
             // skip all other line types because ... we just don't care
         }
     }

     private void initialDovecotSpeakTo(int localport) throws 
UnsupportedEncodingException, IOException
     {
         String versionLine = "VERSION\t1\t0\n";
         String cpidLine = "CPID\t" + localport + "\n";

         if (TRACE)
         {
             System.out.print("C> " + versionLine);
         }
         outputStream.write(versionLine.getBytes("utf-8"));
         if (TRACE)
         {
             System.out.print("C> " + cpidLine);
         }
         outputStream.write(cpidLine.getBytes());
         outputStream.flush();
     }

     private void sayThisLogin(String userid, String password) throws 
UnsupportedEncodingException, IOException
     {
         String creds = "\0" + userid + "\0" + password;
         if (TRACE)
         {
             System.out.println("CREDS:" + creds);
         }
         String base64creds = new 
String(Base64.encode(creds.getBytes("utf-8")));
         String authLine = "AUTH\t" + getSeq() + 
"\tPLAIN\tsecured\tnologin\tservice=" + service + "\tresp=" + 
base64creds + "\n";

         if (TRACE)
         {
             System.out.print("C> " + authLine);
         }
         outputStream.write(authLine.getBytes("utf-8"));
         outputStream.flush();
     }

     private static long sequenceNumber = (long) (Math.random() * 1000000);
     private Socket socket;
     private synchronized static long getSeq()
     {
         return ++sequenceNumber;
     }
}



More information about the dovecot mailing list