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;
    }
}

Reply via email to