[Dovecot] calling dovecot exported auth from Java
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. ('m on a mainstream Linux distribution.)
Has anyone else confronted and solved this problem? Do tell us the details .... :-)
TIA!
On 4/15/2011 5:36 PM, WJCarpenter wrote:
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. ('m on a mainstream Linux distribution.)
BTW, one workaround I thought of this situation was to connect to the POP3 port and do a normal POP3 login there. That would be pretty easy, but it doesn't happen to work out for me. My SQL-based login config factors in the service name provided by dovecot, and some of the Java app users might not be authorized for POP3 and/or IMAP4. If I could figure out a way to run a second POP3 daemon with a different service name, that would probably work for me (but I don't see a way to do it ... at least with dovecot 1.1.11 ... some some later dovecot version allow that?)
On Fri, 2011-04-15 at 17:36 -0700, 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?
v2.0 supports TCP auth sockets.
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;
/** public class DovecotLogin
- This class is not threadsafe. */
{ 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; } }
participants (2)
-
Timo Sirainen
-
WJCarpenter