Merge pull request #782 from hashstat/master

Add support for extending ZAP request address with IPC peer credentials.
This commit is contained in:
Pieter Hintjens 2013-12-06 14:41:41 -08:00
commit b4395d15f9
12 changed files with 249 additions and 10 deletions

View File

@ -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

View File

@ -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 <sys/socket.h>])
AC_CHECK_DECLS([LOCAL_PEERCRED], [AC_DEFINE(ZMQ_HAVE_LOCAL_PEERCRED, 1, [Have LOCAL_PEERCRED socket option])], [], [#include <sys/socket.h>])
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

View File

@ -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

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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 <uid_t> ipc_uid_accept_filters_t;
ipc_uid_accept_filters_t ipc_uid_accept_filters;
typedef std::set <gid_t> ipc_gid_accept_filters_t;

View File

@ -32,6 +32,7 @@
#include <string.h>
#include <new>
#include <sstream>
#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

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <sstream>
#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 ;
}