qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From astitc...@apache.org
Subject [1/4] qpid-proton git commit: PROTON-334: SASL Implementation for Proton-C using Cyrus SASL
Date Tue, 21 Apr 2015 07:10:51 GMT
Repository: qpid-proton
Updated Branches:
  refs/heads/master 1e4b121d6 -> b3bf328fd


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-c/src/transport/transport.c
----------------------------------------------------------------------
diff --git a/proton-c/src/transport/transport.c b/proton-c/src/transport/transport.c
index 62d4742..b96d2a6 100644
--- a/proton-c/src/transport/transport.c
+++ b/proton-c/src/transport/transport.c
@@ -213,9 +213,13 @@ ssize_t pn_io_layer_output_setup(pn_transport_t *transport, unsigned
int layer,
   return transport->io_layers[layer]->process_output(transport, layer, bytes, available);
 }
 
-static void pni_set_error_layer(pn_transport_t *transport)
+void pn_set_error_layer(pn_transport_t *transport)
 {
-    transport->io_layers[0] = &pni_error_layer;
+  // Set every layer to the error layer in case we manually
+  // pass through (happens from SASL to AMQP)
+  for (int layer=0; layer<PN_IO_LAYER_CT; ++layer) {
+    transport->io_layers[layer] = &pni_error_layer;
+  }
 }
 
 // Autodetect the layer by reading the protocol header
@@ -225,7 +229,7 @@ ssize_t pn_io_layer_input_autodetect(pn_transport_t *transport, unsigned
int lay
   bool eos = pn_transport_capacity(transport)==PN_EOS;
   if (eos && available==0) {
     pn_do_error(transport, "amqp:connection:framing-error", "No valid protocol header found");
-    pni_set_error_layer(transport);
+    pn_set_error_layer(transport);
     return PN_EOS;
   }
   pni_protocol_type_t protocol = pni_sniff_header(bytes, available);
@@ -256,16 +260,21 @@ ssize_t pn_io_layer_input_autodetect(pn_transport_t *transport, unsigned
int lay
         pn_transport_logf(transport, "  <- %s", "SASL");
     return 8;
   case PNI_PROTOCOL_AMQP1:
-    if (transport->sasl && pn_sasl_state((pn_sasl_t *)transport)==PN_SASL_IDLE)
{
-      if (pn_sasl_skipping_allowed(transport)) {
-        pn_sasl_done((pn_sasl_t *)transport, PN_SASL_SKIPPED);
-      } else {
-        pn_do_error(transport, "amqp:connection:policy-error",
-                    "Client skipped SASL exchange - forbidden");
-        pni_set_error_layer(transport);
-        return 8;
-      }
+    if (!transport->authenticated && transport->auth_required) {
+      pn_do_error(transport, "amqp:connection:policy-error",
+                  "Client skipped authentication - forbidden");
+      pn_set_error_layer(transport);
+      return 8;
+    }
+// TODO: Encrypted connection detection not implemented yet
+#if 0
+    if (!transport->encrypted && transport->encryption_required) {
+      pn_do_error(transport, "amqp:connection:policy-error",
+                  "Client connection unencryted - forbidden");
+      pn_set_error_layer(transport);
+      return 8;
     }
+#endif
     transport->io_layers[layer] = &amqp_write_header_layer;
     if (transport->trace & PN_TRACE_FRM)
         pn_transport_logf(transport, "  <- %s", "AMQP");
@@ -287,7 +296,7 @@ ssize_t pn_io_layer_input_autodetect(pn_transport_t *transport, unsigned
int lay
   pn_do_error(transport, "amqp:connection:framing-error",
               "%s: '%s'%s", error, quoted,
               !eos ? "" : " (connection aborted)");
-  pni_set_error_layer(transport);
+  pn_set_error_layer(transport);
   return 0;
 }
 
@@ -395,9 +404,16 @@ static void pn_transport_initialize(void *object)
 
   transport->server = false;
   transport->halt = false;
+  transport->auth_required = false;
+  transport->authenticated = false;
+  transport->encryption_required = false;
+  transport->encrypted = false;
 
   transport->referenced = true;
 
+  transport->sasl_input_bypass = false;
+  transport->sasl_output_bypass = false;
+
   transport->trace = (pn_env_bool("PN_TRACE_RAW") ? PN_TRACE_RAW : PN_TRACE_OFF) |
     (pn_env_bool("PN_TRACE_FRM") ? PN_TRACE_FRM : PN_TRACE_OFF) |
     (pn_env_bool("PN_TRACE_DRV") ? PN_TRACE_DRV : PN_TRACE_OFF);
@@ -492,9 +508,42 @@ pn_transport_t *pn_transport(void)
 
 void pn_transport_set_server(pn_transport_t *transport)
 {
+  assert(transport);
   transport->server = true;
 }
 
+const char *pn_transport_get_user(pn_transport_t *transport)
+{
+  assert(transport);
+  if (!transport->sasl) return "anonymous";
+
+  return pn_sasl_get_user((pn_sasl_t *)transport);
+}
+
+void pn_transport_require_auth(pn_transport_t *transport, bool required)
+{
+  assert(transport);
+  transport->auth_required = required;
+}
+
+bool pn_transport_is_authenticated(pn_transport_t *transport)
+{
+  assert(transport);
+  return transport->authenticated;
+}
+
+void pn_transport_require_encryption(pn_transport_t *transport, bool required)
+{
+  assert(transport);
+  transport->encryption_required = required;
+}
+
+bool pn_transport_is_encrypted(pn_transport_t *transport)
+{
+    assert(transport);
+    return transport->encrypted;
+}
+
 void pn_transport_free(pn_transport_t *transport)
 {
   if (!transport) return;
@@ -565,6 +614,18 @@ int pn_transport_bind(pn_transport_t *transport, pn_connection_t *connection)
 
   pn_connection_bound(connection);
 
+  // set the hostname/user/password
+  if (pn_string_size(connection->auth_user)) {
+    pn_sasl(transport);
+    pni_sasl_set_user_password(transport, pn_string_get(connection->auth_user), pn_string_get(connection->auth_password));
+  }
+  if (transport->sasl) {
+    pni_sasl_set_remote_hostname(transport, pn_string_get(connection->hostname));
+  }
+  if (transport->ssl) {
+    pn_ssl_set_peer_hostname((pn_ssl_t*) transport, pn_string_get(connection->hostname));
+  }
+
   if (transport->open_rcvd) {
     PN_SET_REMOTE(connection->endpoint.state, PN_REMOTE_ACTIVE);
     pni_post_remote_open_events(transport, connection);
@@ -2374,6 +2435,7 @@ static ssize_t pn_output_write_amqp(pn_transport_t* transport, unsigned
int laye
   return pn_dispatcher_output(transport, bytes, available);
 }
 
+// Mark transport output as closed and send event
 static void pni_close_head(pn_transport_t *transport)
 {
   if (!transport->head_closed) {
@@ -2422,9 +2484,7 @@ static ssize_t transport_produce(pn_transport_t *transport)
       if (transport->output_pending)
         break;   // return what is available
       if (transport->trace & (PN_TRACE_RAW | PN_TRACE_FRM)) {
-        if (n < 0) {
-          pn_transport_log(transport, "  -> EOS");
-        }
+        pn_transport_log(transport, "  -> EOS");
       }
       pni_close_head(transport);
       return n;
@@ -2712,7 +2772,9 @@ void pn_transport_pop(pn_transport_t *transport, size_t size)
                transport->output_pending );
     }
 
-    if (!transport->output_pending && pn_transport_pending(transport) < 0)
{
+    if (transport->output_pending==0 && pn_transport_pending(transport) < 0)
{
+      // TODO: It looks to me that this is a NOP as iff we ever get here
+      // TODO: pni_close_head() will always have been already called before leaving pn_transport_pending()
       pni_close_head(transport);
     }
   }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
----------------------------------------------------------------------
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
index d1b1bb2..ed8891e 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
@@ -352,12 +352,7 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
     @Override
     public void done(SaslOutcome outcome)
     {
-        // Support current hack in C code to allow producing sasl frames for
-        // ANONYMOUS in a single chunk
-        if(_role == Role.CLIENT)
-        {
-            return;
-        }
+        checkRole(Role.SERVER);
         _outcome = outcome;
         _done = true;
         _state = classifyStateFromOutcome(outcome);

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/proton-j/src/main/resources/csasl.py
----------------------------------------------------------------------
diff --git a/proton-j/src/main/resources/csasl.py b/proton-j/src/main/resources/csasl.py
index 93df589..ea5e489 100644
--- a/proton-j/src/main/resources/csasl.py
+++ b/proton-j/src/main/resources/csasl.py
@@ -29,13 +29,6 @@ PN_SASL_AUTH=1
 PN_SASL_SYS=2
 PN_SASL_PERM=3
 PN_SASL_TEMP=4
-PN_SASL_SKIPPED=5
-
-PN_SASL_CONF = 0
-PN_SASL_IDLE = 1
-PN_SASL_STEP = 2
-PN_SASL_PASS = 3
-PN_SASL_FAIL = 4
 
 def pn_sasl(tp):
   sasl = tp.impl.sasl()
@@ -45,13 +38,6 @@ def pn_sasl(tp):
     sasl.client()
   return sasl
 
-SASL_STATES = {
-  Sasl.SaslState.PN_SASL_IDLE: PN_SASL_IDLE,
-  Sasl.SaslState.PN_SASL_STEP: PN_SASL_STEP,
-  Sasl.SaslState.PN_SASL_PASS: PN_SASL_PASS,
-  Sasl.SaslState.PN_SASL_FAIL: PN_SASL_FAIL
-  }
-
 SASL_OUTCOMES_P2J = {
   PN_SASL_NONE: Sasl.PN_SASL_NONE,
   PN_SASL_OK: Sasl.PN_SASL_OK,
@@ -59,7 +45,6 @@ SASL_OUTCOMES_P2J = {
   PN_SASL_SYS: Sasl.PN_SASL_SYS,
   PN_SASL_PERM: Sasl.PN_SASL_PERM,
   PN_SASL_TEMP: Sasl.PN_SASL_TEMP,
-  PN_SASL_SKIPPED: Sasl.PN_SASL_SKIPPED
 }
 
 SASL_OUTCOMES_J2P = {
@@ -69,43 +54,16 @@ SASL_OUTCOMES_J2P = {
   Sasl.PN_SASL_SYS: PN_SASL_SYS,
   Sasl.PN_SASL_PERM: PN_SASL_PERM,
   Sasl.PN_SASL_TEMP: PN_SASL_TEMP,
-  Sasl.PN_SASL_SKIPPED: PN_SASL_SKIPPED
 }
 
-def pn_sasl_client(sasl):
-  sasl.client()
-
-def pn_sasl_server(sasl):
-  sasl.server()
+def pn_transport_require_auth(transport, require):
+  transport.impl.sasl().allowSkip(not require)
 
-def pn_sasl_state(sasl):
-  return SASL_STATES[sasl.getState()]
-
-def pn_sasl_mechanisms(sasl, mechs):
+def pn_sasl_allowed_mechs(sasl, mechs):
   sasl.setMechanisms(*mechs.split())
 
-def pn_sasl_allow_skip(sasl, allow):
-  sasl.allowSkip(allow)
-
 def pn_sasl_done(sasl, outcome):
   sasl.done(SASL_OUTCOMES_P2J[outcome])
 
 def pn_sasl_outcome(sasl):
   return SASL_OUTCOMES_J2P[sasl.getOutcome()]
-
-def pn_sasl_plain(sasl, user, password):
-  sasl.plain(user, password)
-
-def pn_sasl_recv(sasl, size):
-  if size < sasl.pending():
-    return PN_OVERFLOW, None
-  else:
-    ba = zeros(size, 'b')
-    n = sasl.recv(ba, 0, size)
-    if n >= 0:
-      return n, ba[:n].tostring()
-    else:
-      return n, None
-
-def pn_sasl_send(sasl, data, size):
-  return sasl.send(array(data, 'b'), 0, size)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/tests/python/proton_tests/sasl.py
----------------------------------------------------------------------
diff --git a/tests/python/proton_tests/sasl.py b/tests/python/proton_tests/sasl.py
index a14a0db..68fb6e3 100644
--- a/tests/python/proton_tests/sasl.py
+++ b/tests/python/proton_tests/sasl.py
@@ -17,9 +17,9 @@
 # under the License.
 #
 
-import os, common
+import sys, os, common
 from proton import *
-from common import pump
+from common import pump, Skipped
 
 class Test(common.Test):
   pass
@@ -35,33 +35,72 @@ class SaslTest(Test):
   def pump(self):
     pump(self.t1, self.t2, 1024)
 
-  def testPipelined(self):
-    self.s1.mechanisms("ANONYMOUS")
+  # Note that due to server protocol autodetect, there can be no "pipelining"
+  # of protocol frames from the server end only from the client end.
+  #
+  # This is because the server cannot know which protocol layers are active
+  # and therefore which headers need to be sent,
+  # until it sees the respective protocol headers from the client.
+  def testPipelinedClient(self):
+    if "java" in sys.platform:
+      raise Skipped("Proton-J does not support client pipelining")
+
+    # Client
+    self.s1.allowed_mechs('ANONYMOUS')
+    # Server
+    self.s2.allowed_mechs('ANONYMOUS')
 
     assert self.s1.outcome is None
+    assert self.s2.outcome is None
 
-    self.s2.mechanisms("ANONYMOUS")
-    self.s2.done(SASL.OK)
-
+    # Push client bytes into server
     out1 = self.t1.peek(1024)
     self.t1.pop(len(out1))
+    self.t2.push(out1)
+
     out2 = self.t2.peek(1024)
     self.t2.pop(len(out2))
 
+    assert self.s1.outcome is None
+
+    self.t1.push(out2)
+
+    assert self.s1.outcome == SASL.OK
+    assert self.s2.outcome == SASL.OK
+
+  def testPipelinedClientFail(self):
+    if "java" in sys.platform:
+      raise Skipped("Proton-J does not support client pipelining")
+
+    # Client
+    self.s1.allowed_mechs('ANONYMOUS')
+    # Server
+    self.s2.allowed_mechs('PLAIN DIGEST-MD5 SCRAM-SHA-1')
+
+    assert self.s1.outcome is None
+    assert self.s2.outcome is None
+
+    # Push client bytes into server
+    out1 = self.t1.peek(1024)
+    self.t1.pop(len(out1))
     self.t2.push(out1)
 
+    out2 = self.t2.peek(1024)
+    self.t2.pop(len(out2))
+
     assert self.s1.outcome is None
 
     self.t1.push(out2)
 
-    assert self.s2.outcome == SASL.OK
+    assert self.s1.outcome == SASL.AUTH
+    assert self.s2.outcome == SASL.AUTH
 
   def testSaslAndAmqpInSingleChunk(self):
-    self.s1.mechanisms("ANONYMOUS")
-    self.s1.done(SASL.OK)
+    if "java" in sys.platform:
+      raise Skipped("Proton-J does not support client pipelining")
 
-    self.s2.mechanisms("ANONYMOUS")
-    self.s2.done(SASL.OK)
+    self.s1.allowed_mechs('ANONYMOUS')
+    self.s2.allowed_mechs('ANONYMOUS')
 
     # send the server's OK to the client
     # This is still needed for the Java impl
@@ -101,38 +140,15 @@ class SaslTest(Test):
     assert self.s2.outcome == SASL.OK
     assert c2.state & Endpoint.REMOTE_ACTIVE
 
-
-  def testChallengeResponse(self):
-    self.s1.mechanisms("FAKE_MECH")
-    self.s2.mechanisms("FAKE_MECH")
-    self.pump()
-    challenge = "Who goes there!"
-    self.s2.send(challenge)
-    self.pump()
-    ch = self.s1.recv()
-    assert ch == challenge, (ch, challenge)
-
-    response = "It is I, Secundus!"
-    self.s1.send(response)
-    self.pump()
-    re = self.s2.recv()
-    assert re == response, (re, response)
-
-  def testInitialResponse(self):
-    self.s1.plain("secundus", "trustno1")
-    self.pump()
-    re = self.s2.recv()
-    assert re == "\x00secundus\x00trustno1", repr(re)
-
   def testPipelined2(self):
-    self.s1.mechanisms("ANONYMOUS")
+    if "java" in sys.platform:
+      raise Skipped("Proton-J does not support client pipelining")
 
     out1 = self.t1.peek(1024)
     self.t1.pop(len(out1))
     self.t2.push(out1)
 
-    self.s2.mechanisms("ANONYMOUS")
-    self.s2.done(SASL.OK)
+    self.s2.allowed_mechs('ANONYMOUS')
     c2 = Connection()
     c2.open()
     self.t2.bind(c2)
@@ -147,7 +163,6 @@ class SaslTest(Test):
   def testFracturedSASL(self):
     """ PROTON-235
     """
-    self.s1.mechanisms("ANONYMOUS")
     assert self.s1.outcome is None
 
     # self.t1.trace(Transport.TRACE_FRM)
@@ -160,7 +175,8 @@ class SaslTest(Test):
     self.t1.push("\x00\x00\x00")
     out = self.t1.peek(1024)
     self.t1.pop(len(out))
-    self.t1.push("A\x02\x01\x00\x00\x00S@\xc04\x01\xe01\x06\xa3\x06GSSAPI\x05PLAIN\x0aDIGEST-MD5\x08AMQPLAIN\x08CRAM-MD5\x04NTLM")
+
+    self.t1.push("6\x02\x01\x00\x00\x00S@\xc04\x01\xe01\x04\xa3\x05PLAIN\x0aDIGEST-MD5\x09ANONYMOUS\x08CRAM-MD5")
     out = self.t1.peek(1024)
     self.t1.pop(len(out))
     self.t1.push("\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00")
@@ -196,8 +212,21 @@ class SaslTest(Test):
   def testSaslSkipped(self):
     """Verify that the server (with SASL) correctly handles a client without SASL"""
     self.t1 = Transport()
-    self.s2.mechanisms("ANONYMOUS")
-    self.s2.allow_skip(True)
+    self.t2.require_auth(False)
     self.pump()
-    assert self.s2.outcome == SASL.SKIPPED
-
+    assert self.s2.outcome == None
+    self.t2.condition == None
+    self.t2.authenticated == False
+    assert self.s1.outcome == None
+    self.t1.condition == None
+    self.t1.authenticated == False
+
+  def testSaslSkippedFail(self):
+    """Verify that the server (with SASL) correctly handles a client without SASL"""
+    self.t1 = Transport()
+    self.t2.require_auth(True)
+    self.pump()
+    assert self.s2.outcome == None
+    self.t2.condition != None
+    assert self.s1.outcome == None
+    self.t1.condition != None

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4a09c6a1/tests/python/proton_tests/transport.py
----------------------------------------------------------------------
diff --git a/tests/python/proton_tests/transport.py b/tests/python/proton_tests/transport.py
index dcf7636..febb029 100644
--- a/tests/python/proton_tests/transport.py
+++ b/tests/python/proton_tests/transport.py
@@ -165,10 +165,9 @@ class TransportTest(Test):
 
   def testEOSAfterSASL(self):
     srv = Transport(mode=Transport.SERVER)
-    srv.sasl().mechanisms("ANONYMOUS")
-    srv.sasl().done(SASL.OK)
+    srv.sasl().allowed_mechs('ANONYMOUS')
 
-    self.peer.sasl().mechanisms("ANONYMOUS")
+    self.peer.sasl().allowed_mechs('ANONYMOUS')
 
     # this should send over the sasl header plus a sasl-init set up
     # for anonymous


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message