/*
   D-Bus Java Implementation
   Copyright (c) 2005-2006 Matthew Johnson

   This program is free software; you can redistribute it and/or modify it
   under the terms of either the GNU Lesser General Public License Version 2 or the
   Academic Free Licence Version 2.1.

   Full licence texts are included in the COPYING file with this program.
*/
package org.freedesktop.dbus;

import static org.freedesktop.dbus.Gettext._;

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.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.ParseException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.Collator;
import java.util.Arrays;
import java.util.Random;
import java.util.Vector;
import cx.ath.matthew.unix.UnixSocket;
import cx.ath.matthew.unix.UnixServerSocket;
import cx.ath.matthew.unix.UnixSocketAddress;
import cx.ath.matthew.utils.Hexdump;
import cx.ath.matthew.debug.Debug;

public class Transport
{
   public static class SASL
   {
      public static class Command
      {
         private int command;
         private int mechs;
         private String data;
         private String response;
         public Command()
         {
         }
         public Command(String s) throws IOException
         {
            String[] ss = s.split(" ");
            if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: "+Arrays.toString(ss));
            if (0 == col.compare(ss[0], "OK")) {
               command = COMMAND_OK;
               data = ss[1];
            } else if (0 == col.compare(ss[0], "AUTH")) {
               command = COMMAND_AUTH;
               if (ss.length > 1) {
                  if (0 == col.compare(ss[1], "EXTERNAL"))
                     mechs = AUTH_EXTERNAL;
                  else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1"))
                     mechs = AUTH_SHA;
                  else if (0 == col.compare(ss[1], "ANONYMOUS"))
                     mechs = AUTH_ANON;
               }
               if (ss.length > 2)
                  data = ss[2];
            } else if (0 == col.compare(ss[0], "DATA")) {
               command = COMMAND_DATA;
               data = ss[1];
            } else if (0 == col.compare(ss[0], "REJECTED")) {
               command = COMMAND_REJECTED;
               for (int i = 1; i < ss.length; i++)
                  if (0 == col.compare(ss[i], "EXTERNAL"))
                     mechs |= AUTH_EXTERNAL;
                  else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1"))
                     mechs |= AUTH_SHA;
                  else if (0 == col.compare(ss[i], "ANONYMOUS"))
                     mechs |= AUTH_ANON;
            } else if (0 == col.compare(ss[0], "BEGIN")) {
               command = COMMAND_BEGIN;
            } else if (0 == col.compare(ss[0], "CANCEL")) {
               command = COMMAND_CANCEL;
            } else if (0 == col.compare(ss[0], "ERROR")) {
               command = COMMAND_ERROR;
               data = ss[1];
            } else {
               throw new IOException(_("Invalid Command ")+ss[0]);
            }
            if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: "+this);
         }
         public int getCommand() { return command; }
         public int getMechs() { return mechs; }
         public String getData() { return data; }
         public String getResponse() { return response; }
         public void setResponse(String s) { response = s; }
         public String toString() 
         {
            return "Command("+command+", "+mechs+", "+data+", "+null+")";
         }
      }
      private static Collator col = Collator.getInstance();
      static {
         col.setDecomposition(Collator.FULL_DECOMPOSITION);
         col.setStrength(Collator.PRIMARY);
      }
		public static final int LOCK_TIMEOUT = 1000;
		public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5;
		public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2);
		public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5;
		public static final int COOKIE_TIMEOUT = 240;
      public static final String COOKIE_CONTEXT = "org_freedesktop_java";

      private String findCookie(String context, String ID) throws IOException
      {
         String homedir = System.getProperty("user.home");
         File f = new File(homedir+"/.dbus-keyrings/"+context);
         BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
         String s = null;
         String cookie = null;
			long now = System.currentTimeMillis()/1000;
			while (null != (s = r.readLine())) {
				String[] line = s.split(" ");
				long timestamp = Long.parseLong(line[1]);
				if (line[0].equals(ID) && (! (timestamp < 0 ||
								(now + MAX_TIME_TRAVEL_SECONDS) < timestamp ||
								(now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) {
					cookie = line[2];
					break;
				}
			}
			r.close();
         return cookie;
      }
      private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException
      {
         String homedir = System.getProperty("user.home");
         File keydir = new File(homedir+"/.dbus-keyrings/");
         File cookiefile = new File(homedir+"/.dbus-keyrings/"+context);
         File lock = new File(homedir+"/.dbus-keyrings/"+context+".lock");
         File temp = new File(homedir+"/.dbus-keyrings/"+context+".temp");

         // ensure directory exists
         if (!keydir.exists()) keydir.mkdirs();

         // acquire lock
         long start = System.currentTimeMillis();
         while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis()-start));

         // read old file
         Vector<String> lines = new Vector<String>();
         if (cookiefile.exists()) {
            BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile)));
            String s = null;
            while (null != (s = r.readLine())) {
               String[] line = s.split(" ");
               long time = Long.parseLong(line[1]);
               // expire stale cookies
               if ((timestamp - time) < COOKIE_TIMEOUT)
                  lines.add(s);
            }
            r.close();
         }

         // add cookie
         lines.add(ID+" "+timestamp+" "+cookie);

         // write temp file
         PrintWriter w = new PrintWriter(new FileOutputStream(temp));
         for (String l: lines)
            w.println(l);
         w.close();

         // atomically move to old file
         if (!temp.renameTo(cookiefile)) {
            cookiefile.delete();
            temp.renameTo(cookiefile);
         }

         // remove lock
         lock.delete();
      }
      /**
       * Takes the string, encodes it as hex and then turns it into a string again.
       * No, I don't know why either.
       */
      private String stupidlyEncode(String data)
      {
         return Hexdump.toHex(data.getBytes()).replaceAll(" ","");
      }
      private String stupidlyEncode(byte[] data)
      {
         return Hexdump.toHex(data).replaceAll(" ","");
      }
      private byte getNibble(char c)
      {
         switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
               return (byte) (c-'0');
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
               return (byte) (c-'A'+10);
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
               return (byte) (c-'a'+10);
            default:
               return 0;
         }
      }
      private String stupidlyDecode(String data)
      {
         char[] cs = new char[data.length()];
         char[] res = new char[cs.length/2];
         data.getChars(0, data.length(), cs, 0);
         for (int i = 0, j = 0; j < res.length; i += 2, j++) {
            int b = 0;
            b |= getNibble(cs[i])<<4;
            b |= getNibble(cs[i+1]);
            res[j] = (char) b;
         }
         return new String(res);
      }
      
      public static final int MODE_SERVER=1;
      public static final int MODE_CLIENT=2;
      
      public static final int AUTH_NONE=0;
      public static final int AUTH_EXTERNAL=1;
      public static final int AUTH_SHA=2;
      public static final int AUTH_ANON=4;
      
      public static final int COMMAND_AUTH=1;
      public static final int COMMAND_DATA=2;
      public static final int COMMAND_REJECTED=3;
      public static final int COMMAND_OK=4;
      public static final int COMMAND_BEGIN=5;
      public static final int COMMAND_CANCEL=6;
      public static final int COMMAND_ERROR=7;
      
      public static final int INITIAL_STATE=0;
      public static final int WAIT_DATA=1;
      public static final int WAIT_OK=2;
      public static final int WAIT_REJECT=3;
      public static final int WAIT_AUTH=4;
      public static final int WAIT_BEGIN=5;
      public static final int AUTHENTICATED=6;
      public static final int FAILED=7;

      public static final int OK=1;
      public static final int CONTINUE=2;
      public static final int ERROR=3;
      public static final int REJECT=4;

      public Command receive(InputStream s) throws IOException
      {
         StringBuffer sb = new StringBuffer();
         top: while (true) {
            int c = s.read();
            switch (c) {
               case -1:
                 throw new IOException("Stream unexpectedly short (broken pipe)");
               case 0:
               case '\r':
                  continue;
               case '\n':
                  break top;
               default:
                  sb.append((char) c);
            }
         }
         if (Debug.debug) Debug.print(Debug.VERBOSE, "received: "+sb);
         try {
            return new Command(sb.toString());         
         } catch (Exception e) {
            if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e);
            return new Command();
         }
      }
      public void send(OutputStream out, int command, String... data) throws IOException
      {
         StringBuffer sb = new StringBuffer();
         switch (command) {
            case COMMAND_AUTH:
               sb.append("AUTH");
               break;
            case COMMAND_DATA:
               sb.append("DATA");
               break;
            case COMMAND_REJECTED:
               sb.append("REJECTED");
               break;
            case COMMAND_OK:
               sb.append("OK");
               break;
            case COMMAND_BEGIN:
               sb.append("BEGIN");
               break;
            case COMMAND_CANCEL:
               sb.append("CANCEL");
               break;
            case COMMAND_ERROR:
               sb.append("ERROR");
               break;
            default:
               return;
         }
         for (String s: data) {
            sb.append(' ');
            sb.append(s);
         }
         sb.append('\r');
         sb.append('\n');
         if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: "+sb);
         out.write(sb.toString().getBytes());
      }
      public int do_challenge(int auth, Command c) throws IOException
      {
         switch (auth) {
            case AUTH_SHA:
               String[] reply=stupidlyDecode(c.getData()).split(" ");
               if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply));
               if (3 != reply.length) {
                  if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3");
                  return ERROR;
               }
               String context = reply[0];
               String ID = reply[1];
               String serverchallenge = reply[2];
               MessageDigest md = null;
               try {
                  md = MessageDigest.getInstance("SHA");
               } catch (NoSuchAlgorithmException NSAe) {
                  if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe);
                  return ERROR; 
               }
               byte[] buf = new byte[8];
               Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8);
               String clientchallenge = stupidlyEncode(md.digest(buf));
               md.reset();
               long start = System.currentTimeMillis();
               String cookie = null;
               while (null == cookie && (System.currentTimeMillis()-start) < LOCK_TIMEOUT)
                  cookie = findCookie(context, ID);
               if (null == cookie) {
                  if (Debug.debug) Debug.print(Debug.DEBUG, "Did not find a cookie in context "+context+" with ID "+ID);
                  return ERROR;
               }
               String response = serverchallenge+":"+clientchallenge+":"+cookie;
               buf = md.digest(response.getBytes());
               if (Debug.debug) Debug.print(Debug.VERBOSE, "Response: "+response+" hash: "+Hexdump.format(buf));
               response = stupidlyEncode(buf);
               c.setResponse(stupidlyEncode(clientchallenge+" "+response));
               return OK;
            default:
               if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype.");
               return ERROR;
         }
      }
      public String challenge = "";
      public String cookie = "";
      public int do_response(int auth, String Uid, String kernelUid, Command c)
      {
         MessageDigest md = null;
         try {
            md = MessageDigest.getInstance("SHA");
         } catch (NoSuchAlgorithmException NSAe) {
            if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe);
            return ERROR; 
         }
         switch (auth) {
            case AUTH_NONE:
               switch (c.getMechs()) {
                  case AUTH_ANON:
                     return OK;
                  case AUTH_EXTERNAL:
                     if (0 == col.compare(Uid, c.getData()) &&
                        (null == kernelUid || 0 == col.compare(Uid, kernelUid)))
                        return OK;
                     else
                        return ERROR;
                  case AUTH_SHA:
                     String context = COOKIE_CONTEXT;
                     long id = System.currentTimeMillis();
                     byte[] buf = new byte[8];
                     Message.marshallintBig(id, buf, 0, 8);
                     challenge = stupidlyEncode(md.digest(buf));
                     Random r = new Random();
                     r.nextBytes(buf);
                     cookie = stupidlyEncode(md.digest(buf));
                     try { addCookie(context, ""+id, id/1000, cookie);
                     } catch (IOException IOe) {
                        if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe);
                     }
                     if (Debug.debug) Debug.print(Debug.DEBUG, "Sending challenge: "+context+' '+id+' '+challenge);
                     c.setResponse(stupidlyEncode(context+' '+id+' '+challenge));
                     return CONTINUE;
                  default:
                     return ERROR;
               }
            case AUTH_SHA:
               String[] response = stupidlyDecode(c.getData()).split(" ");
               if (response.length < 2) return ERROR;
               String cchal = response[0];
               String hash = response[1];
               String prehash = challenge+":"+cchal+":"+cookie;
               byte[] buf = md.digest(prehash.getBytes());
               String posthash = stupidlyEncode(buf);
               if (Debug.debug) Debug.print(Debug.DEBUG, "Authenticating Hash; data="+prehash+" remote hash="+hash+" local hash="+posthash);
               if (0 == col.compare(posthash, hash))
                  return OK;
               else
                  return ERROR;
            default:
               return ERROR;
         }
      }
      public String[] getTypes(int types)
      {
         switch (types) {
            case AUTH_EXTERNAL:
               return new String[] { "EXTERNAL" };
            case AUTH_SHA:
               return new String[] { "DBUS_COOKIE_SHA1" };
            case AUTH_ANON:
               return new String[] { "ANONYMOUS" };
            case AUTH_SHA+AUTH_EXTERNAL:
               return new String[] { "EXTERNAL", "DBUS_COOKIE_SHA1" };
            case AUTH_SHA+AUTH_ANON:
               return new String[] { "ANONYMOUS", "DBUS_COOKIE_SHA1" };
            case AUTH_EXTERNAL+AUTH_ANON:
               return new String[] { "ANONYMOUS", "EXTERNAL" };
            case AUTH_EXTERNAL+AUTH_ANON+AUTH_SHA:
               return new String[] { "ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1" };
            default:
               return new String[] { };
         }
      }
      /**
       * performs SASL auth on the given streams.
       * Mode selects whether to run as a SASL server or client.
       * Types is a bitmask of the available auth types.
       * Returns true if the auth was successful and false if it failed.
       */
      @SuppressWarnings("unchecked")
      public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException
      {
         String username = System.getProperty("user.name");
         String Uid = null;
         String kernelUid = null;
         try {
            Class c = Class.forName("com.sun.security.auth.module.UnixSystem");
            Method m = c.getMethod("getUid");
            Object o = c.newInstance();
            long uid = (Long) m.invoke(o);
            Uid = stupidlyEncode(""+uid);
         } catch (Exception e) {
            Uid = stupidlyEncode(username);
         }
         Command c;
         int failed = 0;
         int current = 0;
         int state = INITIAL_STATE;

         while (state != AUTHENTICATED && state != FAILED) {
            if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: "+state);
            switch (mode) {
               case MODE_CLIENT:
                  switch (state) {
                     case INITIAL_STATE:
                        if (null == us)
                           out.write(new byte[] { 0 });
                        else 
                           us.sendCredentialByte((byte) 0);
                        send(out, COMMAND_AUTH);
                        state = WAIT_DATA;
                        break;
                     case WAIT_DATA:
                     c = receive(in);
                     switch (c.getCommand()) {
                        case COMMAND_DATA:
                           switch (do_challenge(current, c)) {
                              case CONTINUE:
                                 send(out, COMMAND_DATA, c.getResponse());
                                 break;
                              case OK:
                                 send(out, COMMAND_DATA, c.getResponse());
                                 state = WAIT_OK;
                                 break;
                              case ERROR:
                                 send(out, COMMAND_ERROR, c.getResponse());
                                 break;
                           }
                           break;
                        case COMMAND_REJECTED:
                           failed |= current;
                           int available = c.getMechs() & (~failed);
                           if (0 != (available & AUTH_EXTERNAL)){
                              send(out, COMMAND_AUTH, "EXTERNAL", Uid);
                              current = AUTH_EXTERNAL;
                           } else if (0 != (available & AUTH_SHA)) {
                              send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
                              current = AUTH_SHA;
                           } else if (0 != (available & AUTH_ANON)) {
                              send(out, COMMAND_AUTH, "ANONYMOUS");
                              current = AUTH_ANON;
                           }
                           else state = FAILED;
                           break;
                        case COMMAND_ERROR:
                           send(out, COMMAND_CANCEL);
                           state = WAIT_REJECT;
                           break;
                        case COMMAND_OK:
                           send(out, COMMAND_BEGIN);
                           state = AUTHENTICATED;
                           break;
                        default:
                           send(out, COMMAND_ERROR, "Got invalid command");
                           break;
                     }
                     break;
                  case WAIT_OK:
                     c = receive(in);
                        switch (c.getCommand()) {
                           case COMMAND_OK:
                              send(out, COMMAND_BEGIN);
                              state = AUTHENTICATED;
                              break;
                           case COMMAND_ERROR:
                           case COMMAND_DATA:
                              send(out, COMMAND_CANCEL);
                              state = WAIT_REJECT;
                              break;
                           case COMMAND_REJECTED:
                              failed |= current;
                              int available = c.getMechs() & (~failed);
                              state = WAIT_DATA;
                              if (0 != (available & AUTH_EXTERNAL)) {
                                 send(out, COMMAND_AUTH, "EXTERNAL", Uid);
                                 current = AUTH_EXTERNAL;
                              } else if (0 != (available & AUTH_SHA)) {
                                 send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
                                 current = AUTH_SHA;
                              } else if (0 != (available & AUTH_ANON)) {
                                 send(out, COMMAND_AUTH, "ANONYMOUS");
                                 current = AUTH_ANON;
                              }
                              else state = FAILED;
                              break;
                           default:
                              send(out, COMMAND_ERROR, "Got invalid command");
                              break;
                        }
                        break;
                     case WAIT_REJECT:
                        c = receive(in);
                        switch (c.getCommand()) {
                           case COMMAND_REJECTED:
                              failed |= current;
                              int available = c.getMechs() & (~failed);
                              if (0 != (available & AUTH_EXTERNAL)) {
                                 send(out, COMMAND_AUTH, "EXTERNAL", Uid);
                                 current = AUTH_EXTERNAL;
                              } else if (0 != (available & AUTH_SHA)) {
                                 send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
                                 current = AUTH_SHA;
                              } else if (0 != (available & AUTH_ANON)) {
                                 send(out, COMMAND_AUTH, "ANONYMOUS");
                                 current = AUTH_ANON;
                              }
                              else state = FAILED;
                              break;
                           default:
                              state = FAILED;
                              break;
                        }
                        break;
                     default:
                        state = FAILED;
                  }
                  break;
               case MODE_SERVER:
                  switch (state) {
                     case INITIAL_STATE:
                        byte[] buf = new byte[1];
                        if (null == us) {
                           in.read(buf);
                        } else {
                           buf[0] = us.recvCredentialByte();
                           int kuid = us.getPeerUID();
                           if (kuid >= 0)
                              kernelUid = stupidlyEncode(""+kuid);
                        }
                        if (0 != buf[0]) state = FAILED;
                        else state = WAIT_AUTH;
                        break;
                     case WAIT_AUTH:
                        c = receive(in);
                        switch (c.getCommand()) {
                           case COMMAND_AUTH:
                              if (null == c.getData()) {
                                 send(out, COMMAND_REJECTED, getTypes(types));
                              } else {
                                 switch (do_response(current, Uid, kernelUid, c)) {
                                    case CONTINUE:
                                       send(out, COMMAND_DATA, c.getResponse());
                                       current = c.getMechs();
                                       state = WAIT_DATA;
                                       break;
                                    case OK:
                                       send(out, COMMAND_OK, guid);
                                       state = WAIT_BEGIN;
                                       current = 0;
                                       break;
                                    case REJECT:
                                       send(out, COMMAND_REJECTED, getTypes(types));
                                       current = 0;
                                       break;
                                 }
                              }
                              break;
                           case COMMAND_ERROR:
                              send(out, COMMAND_REJECTED, getTypes(types));
                              break;
                           case COMMAND_BEGIN:
                              state = FAILED;
                              break;
                           default:
                              send(out, COMMAND_ERROR, "Got invalid command");
                              break;
                        }
                        break;
                     case WAIT_DATA:
                        c = receive(in);
                        switch (c.getCommand()) {
                           case COMMAND_DATA:
                              switch (do_response(current, Uid, kernelUid, c)) {
                                 case CONTINUE:
                                    send(out, COMMAND_DATA, c.getResponse());
                                    state = WAIT_DATA;
                                    break;
                                 case OK:
                                    send(out, COMMAND_OK, guid);
                                    state = WAIT_BEGIN;
                                    current = 0;
                                    break;
                                 case REJECT:
                                    send(out, COMMAND_REJECTED, getTypes(types));
                                    current = 0;
                                    break;
                              }
                              break;
                           case COMMAND_ERROR:
                           case COMMAND_CANCEL:
                              send(out, COMMAND_REJECTED, getTypes(types));
                              state = WAIT_AUTH;
                              break;
                           case COMMAND_BEGIN:
                              state = FAILED;
                              break;
                           default:
                              send(out, COMMAND_ERROR, "Got invalid command");
                              break;
                        }
                        break;
                     case WAIT_BEGIN:
                        c = receive(in);
                        switch (c.getCommand()) {
                           case COMMAND_ERROR:
                           case COMMAND_CANCEL:
                              send(out, COMMAND_REJECTED, getTypes(types));
                              state = WAIT_AUTH;
                              break;
                           case COMMAND_BEGIN:
                              state = AUTHENTICATED;
                              break;
                           default:
                              send(out, COMMAND_ERROR, "Got invalid command");
                              break;
                        }
                        break;
                     default:
                        state = FAILED;
                  }
                  break;
               default:
                  return false;
            }
         }

         return state == AUTHENTICATED;
      }
   }
   public MessageReader min;
   public MessageWriter mout;
   public Transport() {}
   public static String genGUID()
   {
      Random r = new Random();
      byte[] buf = new byte[16];
      r.nextBytes(buf);
      String guid = Hexdump.toHex(buf);
      return guid.replaceAll(" ", "");
   }
   public Transport(BusAddress address) throws IOException
   {
      connect(address);
   }
   public Transport(String address) throws IOException, ParseException
   {
      connect(new BusAddress(address));
   }
   public Transport(String address, int timeout) throws IOException, ParseException
   {
      connect(new BusAddress(address), timeout);
   }
   public void connect(String address) throws IOException, ParseException
   {
      connect(new BusAddress(address), 0);
   }
   public void connect(String address, int timeout) throws IOException, ParseException
   {
      connect(new BusAddress(address), timeout);
   }
   public void connect(BusAddress address) throws IOException
   {
      connect(address, 0);
   }
   public void connect(BusAddress address, int timeout) throws IOException
   {
      if (Debug.debug) Debug.print(Debug.INFO, "Connecting to "+address);
      OutputStream out = null;
      InputStream in = null;
      UnixSocket us = null;
      Socket s = null;
      int mode = 0;
      int types = 0;
      if ("unix".equals(address.getType())) {
         types = SASL.AUTH_EXTERNAL;
         if (null != address.getParameter("listen")) {
            mode = SASL.MODE_SERVER;
            UnixServerSocket uss = new UnixServerSocket();
            if (null != address.getParameter("abstract"))
               uss.bind(new UnixSocketAddress(address.getParameter("abstract"), true));
            else if (null != address.getParameter("path"))
               uss.bind(new UnixSocketAddress(address.getParameter("path"), false));
            us = uss.accept();
         } else {
            mode = SASL.MODE_CLIENT;
            us = new UnixSocket();
            if (null != address.getParameter("abstract"))
               us.connect(new UnixSocketAddress(address.getParameter("abstract"), true));
            else if (null != address.getParameter("path"))
               us.connect(new UnixSocketAddress(address.getParameter("path"), false));
         }
         us.setPassCred(true);
         in = us.getInputStream();
         out = us.getOutputStream();
      } else if ("tcp".equals(address.getType())) {
         types = SASL.AUTH_SHA;
         if (null != address.getParameter("listen")) {
            mode = SASL.MODE_SERVER;
            ServerSocket ss = new ServerSocket();
            ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
            s = ss.accept();
         } else {
            mode = SASL.MODE_CLIENT;
            s = new Socket();
            s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
         }
         in = s.getInputStream();
         out = s.getOutputStream();
      } else {
         throw new IOException(_("unknown address type ")+address.getType());
      }
      
      if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) {
         out.close();
         throw new IOException(_("Failed to auth"));
      }
      if (null != us) {
         if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket");
         if (timeout == 1)
            us.setBlocking(false);
         else
            us.setSoTimeout(timeout);
      }
      if (null != s) {
         if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket");
         s.setSoTimeout(timeout);
      }
      mout = new MessageWriter(out);
      min = new MessageReader(in);
   }
   public void disconnect() throws IOException
   {
      if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport");
      min.close();
      mout.close();
   }
}


