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: [email protected] For additional commands, e-mail: [email protected]
