/* Copyright (c) 2007-2015 Contributors as noted in the AUTHORS file This file is part of 0MQ. 0MQ is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 0MQ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "platform.hpp" #if defined ZMQ_HAVE_WINDOWS #include "windows.hpp" #else #include #include #include #include #include #include #include #if defined ZMQ_HAVE_OPENBSD #define ucred sockpeercred #endif #endif #include #include #include #include "stream_engine.hpp" #include "io_thread.hpp" #include "session_base.hpp" #include "v1_encoder.hpp" #include "v1_decoder.hpp" #include "v2_encoder.hpp" #include "v2_decoder.hpp" #include "null_mechanism.hpp" #include "plain_client.hpp" #include "plain_server.hpp" #include "gssapi_client.hpp" #include "gssapi_server.hpp" #include "curve_client.hpp" #include "curve_server.hpp" #include "raw_decoder.hpp" #include "raw_encoder.hpp" #include "config.hpp" #include "err.hpp" #include "ip.hpp" #include "tcp.hpp" #include "likely.hpp" #include "wire.hpp" zmq::stream_engine_t::stream_engine_t (fd_t fd_, const options_t &options_, const std::string &endpoint_) : s (fd_), inpos (NULL), insize (0), decoder (NULL), outpos (NULL), outsize (0), encoder (NULL), metadata (NULL), handshaking (true), greeting_size (v2_greeting_size), greeting_bytes_read (0), session (NULL), options (options_), endpoint (endpoint_), plugged (false), next_msg (&stream_engine_t::identity_msg), process_msg (&stream_engine_t::process_identity_msg), io_error (false), subscription_required (false), mechanism (NULL), input_stopped (false), output_stopped (false), has_handshake_timer (false), socket (NULL) { int rc = tx_msg.init (); errno_assert (rc == 0); // Put the socket into non-blocking mode. unblock_socket (s); int family = get_peer_ip_address (s, peer_address); if (family == 0) peer_address.clear(); #if defined ZMQ_HAVE_SO_PEERCRED else if (family == PF_UNIX) { struct ucred cred; socklen_t size = sizeof (cred); if (!getsockopt (s, SOL_SOCKET, SO_PEERCRED, &cred, &size)) { std::ostringstream buf; buf << ":" << cred.uid << ":" << cred.gid << ":" << cred.pid; peer_address += buf.str (); } } #elif defined ZMQ_HAVE_LOCAL_PEERCRED else if (family == PF_UNIX) { struct xucred cred; socklen_t size = sizeof (cred); if (!getsockopt (s, 0, LOCAL_PEERCRED, &cred, &size) && cred.cr_version == XUCRED_VERSION) { std::ostringstream buf; buf << ":" << cred.cr_uid << ":"; if (cred.cr_ngroups > 0) buf << cred.cr_groups[0]; buf << ":"; peer_address += buf.str (); } } #endif #ifdef SO_NOSIGPIPE // Make sure that SIGPIPE signal is not generated when writing to a // connection that was already closed by the peer. int set = 1; rc = setsockopt (s, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof (int)); errno_assert (rc == 0); #endif } zmq::stream_engine_t::~stream_engine_t () { zmq_assert (!plugged); if (s != retired_fd) { #ifdef ZMQ_HAVE_WINDOWS int rc = closesocket (s); wsa_assert (rc != SOCKET_ERROR); #else int rc = close (s); errno_assert (rc == 0); #endif s = retired_fd; } int rc = tx_msg.close (); errno_assert (rc == 0); // Drop reference to metadata and destroy it if we are // the only user. if (metadata != NULL) if (metadata->drop_ref ()) delete metadata; delete encoder; delete decoder; delete mechanism; } void zmq::stream_engine_t::plug (io_thread_t *io_thread_, session_base_t *session_) { zmq_assert (!plugged); plugged = true; // Connect to session object. zmq_assert (!session); zmq_assert (session_); session = session_; socket = session-> get_socket (); // Connect to I/O threads poller object. io_object_t::plug (io_thread_); handle = add_fd (s); io_error = false; if (options.raw_socket) { // no handshaking for raw sock, instantiate raw encoder and decoders encoder = new (std::nothrow) raw_encoder_t (out_batch_size); alloc_assert (encoder); decoder = new (std::nothrow) raw_decoder_t (in_batch_size); alloc_assert (decoder); // disable handshaking for raw socket handshaking = false; next_msg = &stream_engine_t::pull_msg_from_session; process_msg = &stream_engine_t::push_raw_msg_to_session; properties_t properties; if (init_properties(properties)) { // Compile metadata. zmq_assert (metadata == NULL); metadata = new (std::nothrow) metadata_t (properties); } if (options.raw_notify) { // For raw sockets, send an initial 0-length message to the // application so that it knows a peer has connected. msg_t connector; connector.init(); push_raw_msg_to_session (&connector); connector.close(); session->flush (); } } else { // start optional timer, to prevent handshake hanging on no input set_handshake_timer (); // Send the 'length' and 'flags' fields of the identity message. // The 'length' field is encoded in the long format. outpos = greeting_send; outpos [outsize++] = 0xff; put_uint64 (&outpos [outsize], options.identity_size + 1); outsize += 8; outpos [outsize++] = 0x7f; } set_pollin (handle); set_pollout (handle); // Flush all the data that may have been already received downstream. in_event (); } void zmq::stream_engine_t::unplug () { zmq_assert (plugged); plugged = false; // Cancel all timers. if (has_handshake_timer) { cancel_timer (handshake_timer_id); has_handshake_timer = false; } // Cancel all fd subscriptions. if (!io_error) rm_fd (handle); // Disconnect from I/O threads poller object. io_object_t::unplug (); session = NULL; } void zmq::stream_engine_t::terminate () { unplug (); delete this; } void zmq::stream_engine_t::in_event () { zmq_assert (!io_error); // If still handshaking, receive and process the greeting message. if (unlikely (handshaking)) if (!handshake ()) return; zmq_assert (decoder); // If there has been an I/O error, stop polling. if (input_stopped) { rm_fd (handle); io_error = true; return; } // If there's no data to process in the buffer... if (!insize) { // Retrieve the buffer and read as much data as possible. // Note that buffer can be arbitrarily large. However, we assume // the underlying TCP layer has fixed buffer size and thus the // number of bytes read will be always limited. size_t bufsize = 0; decoder->get_buffer (&inpos, &bufsize); const int rc = tcp_read (s, inpos, bufsize); if (rc == 0) { error (connection_error); return; } if (rc == -1) { if (errno != EAGAIN) error (connection_error); return; } // Adjust input size insize = static_cast (rc); } int rc = 0; size_t processed = 0; while (insize > 0) { rc = decoder->decode (inpos, insize, processed); zmq_assert (processed <= insize); inpos += processed; insize -= processed; if (rc == 0 || rc == -1) break; rc = (this->*process_msg) (decoder->msg ()); if (rc == -1) break; } // Tear down the connection if we have failed to decode input data // or the session has rejected the message. if (rc == -1) { if (errno != EAGAIN) { error (protocol_error); return; } input_stopped = true; reset_pollin (handle); } session->flush (); } void zmq::stream_engine_t::out_event () { zmq_assert (!io_error); // If write buffer is empty, try to read new data from the encoder. if (!outsize) { // Even when we stop polling as soon as there is no // data to send, the poller may invoke out_event one // more time due to 'speculative write' optimisation. if (unlikely (encoder == NULL)) { zmq_assert (handshaking); return; } outpos = NULL; outsize = encoder->encode (&outpos, 0); while (outsize < out_batch_size) { if ((this->*next_msg) (&tx_msg) == -1) break; encoder->load_msg (&tx_msg); unsigned char *bufptr = outpos + outsize; size_t n = encoder->encode (&bufptr, out_batch_size - outsize); zmq_assert (n > 0); if (outpos == NULL) outpos = bufptr; outsize += n; } // If there is no data to send, stop polling for output. if (outsize == 0) { output_stopped = true; reset_pollout (handle); return; } } // If there are any data to write in write buffer, write as much as // possible to the socket. Note that amount of data to write can be // arbitrarily large. However, we assume that underlying TCP layer has // limited transmission buffer and thus the actual number of bytes // written should be reasonably modest. const int nbytes = tcp_write (s, outpos, outsize); // IO error has occurred. We stop waiting for output events. // The engine is not terminated until we detect input error; // this is necessary to prevent losing incoming messages. if (nbytes == -1) { reset_pollout (handle); return; } outpos += nbytes; outsize -= nbytes; // If we are still handshaking and there are no data // to send, stop polling for output. if (unlikely (handshaking)) if (outsize == 0) reset_pollout (handle); } void zmq::stream_engine_t::restart_output () { if (unlikely (io_error)) return; if (likely (output_stopped)) { set_pollout (handle); output_stopped = false; } // Speculative write: The assumption is that at the moment new message // was sent by the user the socket is probably available for writing. // Thus we try to write the data to socket avoiding polling for POLLOUT. // Consequently, the latency should be better in request/reply scenarios. out_event (); } void zmq::stream_engine_t::restart_input () { zmq_assert (input_stopped); zmq_assert (session != NULL); zmq_assert (decoder != NULL); int rc = (this->*process_msg) (decoder->msg ()); if (rc == -1) { if (errno == EAGAIN) session->flush (); else error (protocol_error); return; } while (insize > 0) { size_t processed = 0; rc = decoder->decode (inpos, insize, processed); zmq_assert (processed <= insize); inpos += processed; insize -= processed; if (rc == 0 || rc == -1) break; rc = (this->*process_msg) (decoder->msg ()); if (rc == -1) break; } if (rc == -1 && errno == EAGAIN) session->flush (); else if (io_error) error (connection_error); else if (rc == -1) error (protocol_error); else { input_stopped = false; set_pollin (handle); session->flush (); // Speculative read. in_event (); } } bool zmq::stream_engine_t::handshake () { zmq_assert (handshaking); zmq_assert (greeting_bytes_read < greeting_size); // Receive the greeting. while (greeting_bytes_read < greeting_size) { const int n = tcp_read (s, greeting_recv + greeting_bytes_read, greeting_size - greeting_bytes_read); if (n == 0) { error (connection_error); return false; } if (n == -1) { if (errno != EAGAIN) error (connection_error); return false; } greeting_bytes_read += n; // We have received at least one byte from the peer. // If the first byte is not 0xff, we know that the // peer is using unversioned protocol. if (greeting_recv [0] != 0xff) break; if (greeting_bytes_read < signature_size) continue; // Inspect the right-most bit of the 10th byte (which coincides // with the 'flags' field if a regular message was sent). // Zero indicates this is a header of identity message // (i.e. the peer is using the unversioned protocol). if (!(greeting_recv [9] & 0x01)) break; // The peer is using versioned protocol. // Send the major version number. if (outpos + outsize == greeting_send + signature_size) { if (outsize == 0) set_pollout (handle); outpos [outsize++] = 3; // Major version number } if (greeting_bytes_read > signature_size) { if (outpos + outsize == greeting_send + signature_size + 1) { if (outsize == 0) set_pollout (handle); // Use ZMTP/2.0 to talk to older peers. if (greeting_recv [10] == ZMTP_1_0 || greeting_recv [10] == ZMTP_2_0) outpos [outsize++] = options.type; else { outpos [outsize++] = 0; // Minor version number memset (outpos + outsize, 0, 20); zmq_assert (options.mechanism == ZMQ_NULL || options.mechanism == ZMQ_PLAIN || options.mechanism == ZMQ_CURVE || options.mechanism == ZMQ_GSSAPI); if (options.mechanism == ZMQ_NULL) memcpy (outpos + outsize, "NULL", 4); else if (options.mechanism == ZMQ_PLAIN) memcpy (outpos + outsize, "PLAIN", 5); else if (options.mechanism == ZMQ_GSSAPI) memcpy (outpos + outsize, "GSSAPI", 6); else if (options.mechanism == ZMQ_CURVE) memcpy (outpos + outsize, "CURVE", 5); outsize += 20; memset (outpos + outsize, 0, 32); outsize += 32; greeting_size = v3_greeting_size; } } } } // Position of the revision field in the greeting. const size_t revision_pos = 10; // Is the peer using ZMTP/1.0 with no revision number? // If so, we send and receive rest of identity message if (greeting_recv [0] != 0xff || !(greeting_recv [9] & 0x01)) { if (session->zap_enabled ()) { // reject ZMTP 1.0 connections if ZAP is enabled error (protocol_error); return false; } encoder = new (std::nothrow) v1_encoder_t (out_batch_size); alloc_assert (encoder); decoder = new (std::nothrow) v1_decoder_t (in_batch_size, options.maxmsgsize); alloc_assert (decoder); // We have already sent the message header. // Since there is no way to tell the encoder to // skip the message header, we simply throw that // header data away. const size_t header_size = options.identity_size + 1 >= 255 ? 10 : 2; unsigned char tmp [10], *bufferp = tmp; // Prepare the identity message and load it into encoder. // Then consume bytes we have already sent to the peer. const int rc = tx_msg.init_size (options.identity_size); zmq_assert (rc == 0); memcpy (tx_msg.data (), options.identity, options.identity_size); encoder->load_msg (&tx_msg); size_t buffer_size = encoder->encode (&bufferp, header_size); zmq_assert (buffer_size == header_size); // Make sure the decoder sees the data we have already received. inpos = greeting_recv; insize = greeting_bytes_read; // To allow for interoperability with peers that do not forward // their subscriptions, we inject a phantom subscription message // message into the incoming message stream. if (options.type == ZMQ_PUB || options.type == ZMQ_XPUB) subscription_required = true; // We are sending our identity now and the next message // will come from the socket. next_msg = &stream_engine_t::pull_msg_from_session; // We are expecting identity message. process_msg = &stream_engine_t::process_identity_msg; } else if (greeting_recv [revision_pos] == ZMTP_1_0) { if (session->zap_enabled ()) { // reject ZMTP 1.0 connections if ZAP is enabled error (protocol_error); return false; } encoder = new (std::nothrow) v1_encoder_t ( out_batch_size); alloc_assert (encoder); decoder = new (std::nothrow) v1_decoder_t ( in_batch_size, options.maxmsgsize); alloc_assert (decoder); } else if (greeting_recv [revision_pos] == ZMTP_2_0) { if (session->zap_enabled ()) { // reject ZMTP 2.0 connections if ZAP is enabled error (protocol_error); return false; } encoder = new (std::nothrow) v2_encoder_t (out_batch_size); alloc_assert (encoder); decoder = new (std::nothrow) v2_decoder_t ( in_batch_size, options.maxmsgsize); alloc_assert (decoder); } else { encoder = new (std::nothrow) v2_encoder_t (out_batch_size); alloc_assert (encoder); decoder = new (std::nothrow) v2_decoder_t ( in_batch_size, options.maxmsgsize); alloc_assert (decoder); if (options.mechanism == ZMQ_NULL && memcmp (greeting_recv + 12, "NULL\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) == 0) { mechanism = new (std::nothrow) null_mechanism_t (session, peer_address, options); alloc_assert (mechanism); } else if (options.mechanism == ZMQ_PLAIN && memcmp (greeting_recv + 12, "PLAIN\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) == 0) { if (options.as_server) mechanism = new (std::nothrow) plain_server_t (session, peer_address, options); else mechanism = new (std::nothrow) plain_client_t (options); alloc_assert (mechanism); } #ifdef HAVE_LIBSODIUM else if (options.mechanism == ZMQ_CURVE && memcmp (greeting_recv + 12, "CURVE\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) == 0) { if (options.as_server) mechanism = new (std::nothrow) curve_server_t (session, peer_address, options); else mechanism = new (std::nothrow) curve_client_t (options); alloc_assert (mechanism); } #endif #ifdef HAVE_LIBGSSAPI_KRB5 else if (options.mechanism == ZMQ_GSSAPI && memcmp (greeting_recv + 12, "GSSAPI\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) == 0) { if (options.as_server) mechanism = new (std::nothrow) gssapi_server_t (session, peer_address, options); else mechanism = new (std::nothrow) gssapi_client_t (options); alloc_assert (mechanism); } #endif else { // Temporary support for security debugging char mechanism [21]; memcpy (mechanism, greeting_recv + 12, 20); mechanism [20] = 0; printf ("LIBZMQ I: security failure, self=%s peer=%s\n", options.mechanism == ZMQ_NULL? "NULL": options.mechanism == ZMQ_PLAIN? "PLAIN": options.mechanism == ZMQ_CURVE? "CURVE": options.mechanism == ZMQ_GSSAPI? "GSSAPI": "OTHER", mechanism); error (protocol_error); return false; } next_msg = &stream_engine_t::next_handshake_command; process_msg = &stream_engine_t::process_handshake_command; } // Start polling for output if necessary. if (outsize == 0) set_pollout (handle); // Handshaking was successful. // Switch into the normal message flow. handshaking = false; if (has_handshake_timer) { cancel_timer (handshake_timer_id); has_handshake_timer = false; } return true; } int zmq::stream_engine_t::identity_msg (msg_t *msg_) { int rc = msg_->init_size (options.identity_size); errno_assert (rc == 0); if (options.identity_size > 0) memcpy (msg_->data (), options.identity, options.identity_size); next_msg = &stream_engine_t::pull_msg_from_session; return 0; } int zmq::stream_engine_t::process_identity_msg (msg_t *msg_) { if (options.recv_identity) { msg_->set_flags (msg_t::identity); int rc = session->push_msg (msg_); errno_assert (rc == 0); } else { int rc = msg_->close (); errno_assert (rc == 0); rc = msg_->init (); errno_assert (rc == 0); } if (subscription_required) process_msg = &stream_engine_t::write_subscription_msg; else process_msg = &stream_engine_t::push_msg_to_session; return 0; } int zmq::stream_engine_t::next_handshake_command (msg_t *msg_) { zmq_assert (mechanism != NULL); if (mechanism->status () == mechanism_t::ready) { mechanism_ready (); return pull_and_encode (msg_); } else if (mechanism->status () == mechanism_t::error) { errno = EPROTO; return -1; } else { const int rc = mechanism->next_handshake_command (msg_); if (rc == 0) msg_->set_flags (msg_t::command); return rc; } } int zmq::stream_engine_t::process_handshake_command (msg_t *msg_) { zmq_assert (mechanism != NULL); const int rc = mechanism->process_handshake_command (msg_); if (rc == 0) { if (mechanism->status () == mechanism_t::ready) mechanism_ready (); else if (mechanism->status () == mechanism_t::error) { errno = EPROTO; return -1; } if (output_stopped) restart_output (); } return rc; } void zmq::stream_engine_t::zap_msg_available () { zmq_assert (mechanism != NULL); const int rc = mechanism->zap_msg_available (); if (rc == -1) { error (protocol_error); return; } if (input_stopped) restart_input (); if (output_stopped) restart_output (); } void zmq::stream_engine_t::mechanism_ready () { if (options.recv_identity) { msg_t identity; mechanism->peer_identity (&identity); const int rc = session->push_msg (&identity); if (rc == -1 && errno == EAGAIN) { // If the write is failing at this stage with // an EAGAIN the pipe must be being shut down, // so we can just bail out of the identity set. return; } errno_assert (rc == 0); session->flush (); } next_msg = &stream_engine_t::pull_and_encode; process_msg = &stream_engine_t::write_credential; // Compile metadata. properties_t properties; init_properties(properties); // Add ZAP properties. const properties_t& zap_properties = mechanism->get_zap_properties (); properties.insert(zap_properties.begin (), zap_properties.end ()); // Add ZMTP properties. const properties_t& zmtp_properties = mechanism->get_zmtp_properties (); properties.insert(zmtp_properties.begin (), zmtp_properties.end ()); zmq_assert (metadata == NULL); if (!properties.empty ()) metadata = new (std::nothrow) metadata_t (properties); } int zmq::stream_engine_t::pull_msg_from_session (msg_t *msg_) { return session->pull_msg (msg_); } int zmq::stream_engine_t::push_msg_to_session (msg_t *msg_) { return session->push_msg (msg_); } int zmq::stream_engine_t::push_raw_msg_to_session (msg_t *msg_) { if (metadata) msg_->set_metadata(metadata); return push_msg_to_session(msg_); } int zmq::stream_engine_t::write_credential (msg_t *msg_) { zmq_assert (mechanism != NULL); zmq_assert (session != NULL); const blob_t credential = mechanism->get_user_id (); if (credential.size () > 0) { msg_t msg; int rc = msg.init_size (credential.size ()); zmq_assert (rc == 0); memcpy (msg.data (), credential.data (), credential.size ()); msg.set_flags (msg_t::credential); rc = session->push_msg (&msg); if (rc == -1) { rc = msg.close (); errno_assert (rc == 0); return -1; } } process_msg = &stream_engine_t::decode_and_push; return decode_and_push (msg_); } int zmq::stream_engine_t::pull_and_encode (msg_t *msg_) { zmq_assert (mechanism != NULL); if (session->pull_msg (msg_) == -1) return -1; if (mechanism->encode (msg_) == -1) return -1; return 0; } int zmq::stream_engine_t::decode_and_push (msg_t *msg_) { zmq_assert (mechanism != NULL); if (mechanism->decode (msg_) == -1) return -1; if (metadata) msg_->set_metadata (metadata); if (session->push_msg (msg_) == -1) { if (errno == EAGAIN) process_msg = &stream_engine_t::push_one_then_decode_and_push; return -1; } return 0; } int zmq::stream_engine_t::push_one_then_decode_and_push (msg_t *msg_) { const int rc = session->push_msg (msg_); if (rc == 0) process_msg = &stream_engine_t::decode_and_push; return rc; } int zmq::stream_engine_t::write_subscription_msg (msg_t *msg_) { msg_t subscription; // Inject the subscription message, so that also // ZMQ 2.x peers receive published messages. int rc = subscription.init_size (1); errno_assert (rc == 0); *(unsigned char*) subscription.data () = 1; rc = session->push_msg (&subscription); if (rc == -1) return -1; process_msg = &stream_engine_t::push_msg_to_session; return push_msg_to_session (msg_); } void zmq::stream_engine_t::error (error_reason_t reason) { if (options.raw_socket) { // For raw sockets, send a final 0-length message to the application // so that it knows the peer has been disconnected. msg_t terminator; terminator.init(); (this->*process_msg) (&terminator); terminator.close(); } zmq_assert (session); socket->event_disconnected (endpoint, (int) s); session->flush (); session->engine_error (reason); unplug (); delete this; } void zmq::stream_engine_t::set_handshake_timer () { zmq_assert (!has_handshake_timer); if (!options.raw_socket && options.handshake_ivl > 0) { add_timer (options.handshake_ivl, handshake_timer_id); has_handshake_timer = true; } } bool zmq::stream_engine_t::init_properties (properties_t & properties) { if (peer_address.empty()) return false; properties.insert (std::make_pair("Peer-Address", peer_address)); return true; } void zmq::stream_engine_t::timer_event (int id_) { zmq_assert (id_ == handshake_timer_id); has_handshake_timer = false; // handshake timer expired before handshake completed, so engine fails error (timeout_error); }