diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ef3b2ae..abf60895 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -631,10 +631,11 @@ list(APPEND tests test_monitor test_pair_ipc test_reqrep_ipc - test_abstract_ipc test_fork + test_abstract_ipc test_proxy test_filter_ipc + test_zap_ipc_creds ) endif() @@ -652,6 +653,15 @@ foreach(test ${tests}) endif() endforeach() +if(NOT WIN32) + if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") + set_tests_properties(test_abstract_ipc PROPERTIES WILL_FAIL true) + endif() + if(NOT ZMQ_HAVE_SO_PEERCRED AND NOT ZMQ_HAVE_LOCAL_PEERCRED) + set_tests_properties(test_zap_ipc_creds PROPERTIES WILL_FAIL true) + endif() +endif() + #----------------------------------------------------------------------------- # installer diff --git a/configure.ac b/configure.ac index a97de859..a258bf09 100644 --- a/configure.ac +++ b/configure.ac @@ -354,6 +354,7 @@ AC_LANG_PUSH(C++) AC_CHECK_DECLS([SO_PEERCRED], [AC_DEFINE(ZMQ_HAVE_SO_PEERCRED, 1, [Have SO_PEERCRED socket option])], [], [#include ]) AC_CHECK_DECLS([LOCAL_PEERCRED], [AC_DEFINE(ZMQ_HAVE_LOCAL_PEERCRED, 1, [Have LOCAL_PEERCRED socket option])], [], [#include ]) +AM_CONDITIONAL(HAVE_IPC_PEERCRED, test "x$ac_cv_have_decl_SO_PEERCRED" = "xyes" || test "x$ac_cv_have_decl_LOCAL_PEERCRED" = "xyes") AC_HEADER_STDBOOL AC_C_CONST diff --git a/doc/zmq_getsockopt.txt b/doc/zmq_getsockopt.txt index 357936b8..9beeb15c 100644 --- a/doc/zmq_getsockopt.txt +++ b/doc/zmq_getsockopt.txt @@ -497,6 +497,25 @@ Default value:: -1 (leave to OS default) Applicable socket types:: all, when using TCP transports. +ZMQ_ZAP_IPC_CREDS: Retrieve IPC peer credentials state +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The 'ZMQ_ZAP_IPC_CREDS' option shall return True (1) if credentials of IPC +peers will be appended to the address sent in ZAP request messages and False +(0) otherwise. + +Refer to linkzmq:zmq_setsockopt[3] for more information. + +NOTE: IPC peer credentials are only available on platforms supporting the +SO_PEERCRED or LOCAL_PEERCRED socket options. + +[horizontal] +Option value type:: int +Option value unit:: boolean +Default value:: 0 (false) +Applicable socket types:: all listening sockets, when using IPC transports. + + ZMQ_MECHANISM: Retrieve current security mechanism ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The 'ZMQ_MECHANISM' option shall retrieve the current security mechanism diff --git a/doc/zmq_setsockopt.txt b/doc/zmq_setsockopt.txt index 49f6dbb8..20778f7e 100644 --- a/doc/zmq_setsockopt.txt +++ b/doc/zmq_setsockopt.txt @@ -663,6 +663,25 @@ Default value:: no filters (allow from all) Applicable socket types:: all listening sockets, when using IPC transports. +ZMQ_ZAP_IPC_CREDS: Append IPC peer credentials to ZAP address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If set, the credentials of IPC peers will be appended to the address sent in +ZAP request messages. The new address will be formatted as ADDRESS:UID:GID:PID +where UID and GID are the effective group and user IDs of the user owning the +peer process and PID is the process ID. PID will be empty on systems not +supporting SO_PEERCRED. + +NOTE: IPC peer credentials are only available on platforms supporting the +SO_PEERCRED or LOCAL_PEERCRED socket options. + +[horizontal] +Option value type:: int +Option value unit:: boolean +Default value:: 0 (false) +Applicable socket types:: all listening sockets, when using IPC transports. + + ZMQ_PLAIN_SERVER: Set PLAIN server role ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/include/zmq.h b/include/zmq.h index 561df21a..4c448fb2 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -293,6 +293,7 @@ ZMQ_EXPORT int zmq_msg_set (zmq_msg_t *msg, int option, int optval); #define ZMQ_IPC_FILTER_PID 58 #define ZMQ_IPC_FILTER_UID 59 #define ZMQ_IPC_FILTER_GID 60 +#define ZMQ_ZAP_IPC_CREDS 61 /* Message options */ #define ZMQ_MORE 1 diff --git a/src/ip.cpp b/src/ip.cpp index 52553c74..79bdc4b0 100644 --- a/src/ip.cpp +++ b/src/ip.cpp @@ -109,7 +109,7 @@ void zmq::enable_ipv4_mapping (fd_t s_) #endif } -bool zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) +int zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) { int rc; struct sockaddr_storage ss; @@ -126,7 +126,7 @@ bool zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) WSAGetLastError () != WSAEFAULT && WSAGetLastError () != WSAEINPROGRESS && WSAGetLastError () != WSAENOTSOCK); - return false; + return 0; } #else if (rc == -1) { @@ -135,7 +135,7 @@ bool zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) errno != EINVAL && errno != ENOTCONN && errno != ENOTSOCK); - return false; + return 0; } #endif @@ -143,10 +143,10 @@ bool zmq::get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_) rc = getnameinfo ((struct sockaddr*) &ss, addrlen, host, sizeof host, NULL, 0, NI_NUMERICHOST); if (rc != 0) - return false; + return 0; ip_addr_ = host; - return true; + return (int) ((struct sockaddr *) &ss)->sa_family; } void zmq::set_ip_type_of_service (fd_t s_, int iptos) diff --git a/src/ip.hpp b/src/ip.hpp index 7b8621c8..665535fb 100644 --- a/src/ip.hpp +++ b/src/ip.hpp @@ -37,7 +37,7 @@ namespace zmq // Returns string representation of peer's address. // Socket sockfd_ must be connected. Returns true iff successful. - bool get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_); + int get_peer_ip_address (fd_t sockfd_, std::string &ip_addr_); // Sets the IP Type-Of-Service for the underlying socket void set_ip_type_of_service (fd_t s_, int iptos); diff --git a/src/options.cpp b/src/options.cpp index 09dc6df7..edf8f67e 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -51,6 +51,9 @@ zmq::options_t::options_t () : tcp_keepalive_cnt (-1), tcp_keepalive_idle (-1), tcp_keepalive_intvl (-1), +# if defined ZMQ_HAVE_SO_PEERCRED || defined ZMQ_HAVE_LOCAL_PEERCRED + zap_ipc_creds (false), +# endif mechanism (ZMQ_NULL), as_server (0), socket_id (0), @@ -258,6 +261,13 @@ int zmq::options_t::setsockopt (int option_, const void *optval_, break; # if defined ZMQ_HAVE_SO_PEERCRED || defined ZMQ_HAVE_LOCAL_PEERCRED + case ZMQ_ZAP_IPC_CREDS: + if (is_int && (value == 0 || value == 1)) { + zap_ipc_creds = (value != 0); + return 0; + } + break; + case ZMQ_IPC_FILTER_UID: if (optvallen_ == 0 && optval_ == NULL) { ipc_uid_accept_filters.clear (); @@ -591,6 +601,15 @@ int zmq::options_t::getsockopt (int option_, void *optval_, size_t *optvallen_) } break; +# if defined ZMQ_HAVE_SO_PEERCRED || defined ZMQ_HAVE_LOCAL_PEERCRED + case ZMQ_ZAP_IPC_CREDS: + if (is_int) { + *value = zap_ipc_creds; + return 0; + } + break; +# endif + case ZMQ_MECHANISM: if (is_int) { *value = mechanism; diff --git a/src/options.hpp b/src/options.hpp index 3cadf95e..8f80e887 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -127,6 +127,7 @@ namespace zmq // IPC accept() filters # if defined ZMQ_HAVE_SO_PEERCRED || defined ZMQ_HAVE_LOCAL_PEERCRED + bool zap_ipc_creds; typedef std::set ipc_uid_accept_filters_t; ipc_uid_accept_filters_t ipc_uid_accept_filters; typedef std::set ipc_gid_accept_filters_t; diff --git a/src/stream_engine.cpp b/src/stream_engine.cpp index 447052ed..07612d7d 100644 --- a/src/stream_engine.cpp +++ b/src/stream_engine.cpp @@ -32,6 +32,7 @@ #include #include +#include #include "stream_engine.hpp" #include "io_thread.hpp" @@ -84,8 +85,34 @@ zmq::stream_engine_t::stream_engine_t (fd_t fd_, const options_t &options_, // Put the socket into non-blocking mode. unblock_socket (s); - if (!get_peer_ip_address (s, peer_address)) + int family = get_peer_ip_address (s, peer_address); + if (family == 0) peer_address = ""; +#if defined ZMQ_HAVE_SO_PEERCRED + else if (family == PF_UNIX && options.zap_ipc_creds) { + 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 && options.zap_ipc_creds) { + 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 diff --git a/tests/Makefile.am b/tests/Makefile.am index bfe287e4..c19fd58c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -51,7 +51,8 @@ noinst_PROGRAMS += test_shutdown_stress \ test_reqrep_ipc \ test_timeo \ test_fork \ - test_filter_ipc + test_filter_ipc \ + test_zap_ipc_creds endif if BUILD_TIPC @@ -113,6 +114,7 @@ test_reqrep_ipc_SOURCES = test_reqrep_ipc.cpp testutil.hpp test_timeo_SOURCES = test_timeo.cpp test_fork_SOURCES = test_fork.cpp test_filter_ipc_SOURCES = test_filter_ipc.cpp +test_zap_ipc_creds_SOURCES = test_zap_ipc_creds.cpp endif if BUILD_TIPC test_connect_delay_tipc_SOURCES = test_connect_delay_tipc.cpp @@ -127,7 +129,12 @@ endif # Run the test cases TESTS = $(noinst_PROGRAMS) +XFAIL_TESTS = if !ON_LINUX -XFAIL_TESTS = test_abstract_ipc +XFAIL_TESTS += test_abstract_ipc +endif + +if !HAVE_IPC_PEERCRED +XFAIL_TESTS += test_zap_ipc_creds endif diff --git a/tests/test_zap_ipc_creds.cpp b/tests/test_zap_ipc_creds.cpp new file mode 100644 index 00000000..10dccb6c --- /dev/null +++ b/tests/test_zap_ipc_creds.cpp @@ -0,0 +1,135 @@ +/* + Copyright (c) 2007-2013 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 + +#include + +#include "testutil.hpp" + +static void zap_handler (void *handler) +{ + // Process ZAP requests forever + while (true) { + char *version = s_recv (handler); + if (!version) + break; // Terminating + char *sequence = s_recv (handler); + char *domain = s_recv (handler); + char *address = s_recv (handler); + char *identity = s_recv (handler); + char *mechanism = s_recv (handler); + + assert (streq (version, "1.0")); + assert (streq (mechanism, "NULL")); + + if (streq (domain, "creds")) { + std::ostringstream buf; + buf << "localhost:" << getuid () << ":" << getgid () << ":"; +# ifdef ZMQ_HAVE_SO_PEERCRED + buf << getpid (); +# endif + assert (streq (address, buf.str ().c_str ())); + } else + assert (streq (address, "localhost")); + + s_sendmore (handler, version); + s_sendmore (handler, sequence); + s_sendmore (handler, "200"); + s_sendmore (handler, "OK"); + s_sendmore (handler, "anonymous"); + s_send (handler, ""); + + free (version); + free (sequence); + free (domain); + free (address); + free (identity); + free (mechanism); + } + zmq_close (handler); +} + +static void run_test (bool with_creds) +{ + void *ctx = zmq_ctx_new (); + assert (ctx); + + // Spawn ZAP handler + // We create and bind ZAP socket in main thread to avoid case + // where child thread does not start up fast enough. + void *handler = zmq_socket (ctx, ZMQ_REP); + assert (handler); + int rc = zmq_bind (handler, "inproc://zeromq.zap.01"); + assert (rc == 0); + void *zap_thread = zmq_threadstart (&zap_handler, handler); + + void *sb = zmq_socket (ctx, ZMQ_PAIR); + assert (sb); + + void *sc = zmq_socket (ctx, ZMQ_PAIR); + assert (sc); + + // Now use the right domain, the test must pass + if (with_creds) { + rc = zmq_setsockopt (sb, ZMQ_ZAP_DOMAIN, "creds", 5); + assert (rc == 0); + int ipc_creds = 1; + rc = zmq_setsockopt (sb, ZMQ_ZAP_IPC_CREDS, &ipc_creds, sizeof (int)); + assert (rc == 0); + } else { + rc = zmq_setsockopt (sb, ZMQ_ZAP_DOMAIN, "none", 4); + assert (rc == 0); + int ipc_creds = 1; + size_t size = sizeof (int); + rc = zmq_getsockopt (sb, ZMQ_ZAP_IPC_CREDS, &ipc_creds, &size); + assert (rc == 0); + assert (ipc_creds == 0); + } + + rc = zmq_bind (sb, "ipc://@/tmp/test"); + assert (rc == 0); + + rc = zmq_connect (sc, "ipc://@/tmp/test"); + assert (rc == 0); + + bounce (sb, sc); + + rc = zmq_close (sc); + assert (rc == 0); + rc = zmq_close (sb); + assert (rc == 0); + + rc = zmq_ctx_term (ctx); + assert (rc == 0); + + // Wait until ZAP handler terminates. + zmq_threadclose (zap_thread); +} + +int main (void) +{ + setup_test_environment(); + + run_test(false); + run_test(true); + + return 0 ; +} +