zproxy(3)
=========

NAME
----
zproxy - run a steerable proxy in the background

SYNOPSIS
--------
----
//  Create new zproxy actor instance. The proxy switches messages between
//  a frontend socket and a backend socket; use the FRONTEND and BACKEND
//  commands to configure these:
//
//      zactor_t *proxy = zactor_new (zproxy, NULL);
//
//  Destroy zproxy instance. This destroys the two sockets and stops any
//  message flow between them:
//
//      zactor_destroy (&proxy);
//
//  Note that all zproxy commands are synchronous, so your application always
//  waits for a signal from the actor after each command.
//
//  Enable verbose logging of commands and activity:
//
//      zstr_send (proxy, "VERBOSE");
//      zsock_wait (proxy);
//
//  Specify frontend socket type -- see zsock_type_str () -- and attach to
//  endpoints, see zsock_attach (). Note that a proxy socket is always
//  serverish:
//
//      zstr_sendx (proxy, "FRONTEND", "XSUB", endpoints, NULL);
//      zsock_wait (proxy);
//
//  Specify backend socket type -- see zsock_type_str () -- and attach to
//  endpoints, see zsock_attach (). Note that a proxy socket is always
//  serverish:
//
//      zstr_sendx (proxy, "BACKEND", "XPUB", endpoints, NULL);
//      zsock_wait (proxy);
//
//  Capture all proxied messages; these are delivered to the application
//  via an inproc PULL socket that you have already bound to the specified
//  endpoint:
//
//      zstr_sendx (proxy, "CAPTURE", endpoint, NULL);
//      zsock_wait (proxy);
//
//  Pause the proxy. A paused proxy will cease processing messages, causing
//  them to be queued up and potentially hit the high-water mark on the
//  frontend or backend socket, causing messages to be dropped, or writing
//  applications to block:
//
//      zstr_sendx (proxy, "PAUSE", NULL);
//      zsock_wait (proxy);
//
//  Resume the proxy. Note that the proxy starts automatically as soon as it
//  has a properly attached frontend and backend socket:
//
//      zstr_sendx (proxy, "RESUME", NULL);
//      zsock_wait (proxy);
//
//  Configure an authentication domain for the "FRONTEND" or "BACKEND" proxy
//  socket -- see zsock_set_zap_domain (). Call before binding socket:
//
//      zstr_sendx (proxy, "DOMAIN", "FRONTEND", "global", NULL);
//      zsock_wait (proxy);
//
//  Configure PLAIN authentication for the "FRONTEND" or "BACKEND" proxy
//  socket -- see zsock_set_plain_server (). Call before binding socket:
//
//      zstr_sendx (proxy, "PLAIN", "BACKEND", NULL);
//      zsock_wait (proxy);
//
//  Configure CURVE authentication for the "FRONTEND" or "BACKEND" proxy
//  socket -- see zsock_set_curve_server () -- specifying both the public and
//  secret keys of a certificate as Z85 armored strings -- see
//  zcert_public_txt () and zcert_secret_txt (). Call before binding socket:
//
//      zstr_sendx (proxy, "CURVE", "FRONTEND", public_txt, secret_txt, NULL);
//      zsock_wait (proxy);
//
//  This is the zproxy constructor as a zactor_fn; the argument is a
//  character string specifying frontend and backend socket types as two
//  uppercase strings separated by a hyphen:
CZMQ_EXPORT void
    zproxy (zsock_t *pipe, void *unused);

//  Selftest
CZMQ_EXPORT void
    zproxy_test (bool verbose);
----

DESCRIPTION
-----------

A zproxy actor switches messages between a frontend and a backend socket.
It acts much like the zmq_proxy_steerable method, though it makes benefit
of CZMQ's facilities, to be somewhat simpler to set-up.

This class replaces zproxy_v2, and is meant for applications that use the
CZMQ v3 API (meaning, zsock).

EXAMPLE
-------
.From zproxy_test method
----
//  Create and configure our proxy
zactor_t *proxy = zactor_new (zproxy, NULL);
assert (proxy);
if (verbose) {
    zstr_sendx (proxy, "VERBOSE", NULL);
    zsock_wait (proxy);
}
zstr_sendx (proxy, "FRONTEND", "PULL", "inproc://frontend", NULL);
zsock_wait (proxy);
zstr_sendx (proxy, "BACKEND", "PUSH", "inproc://backend", NULL);
zsock_wait (proxy);

//  Connect application sockets to proxy
zsock_t *faucet = zsock_new_push (">inproc://frontend");
assert (faucet);
zsock_t *sink = zsock_new_pull (">inproc://backend");
assert (sink);

//  Send some messages and check they arrived
char *hello, *world;
zstr_sendx (faucet, "Hello", "World", NULL);
zstr_recvx (sink, &hello, &world, NULL);
assert (streq (hello, "Hello"));
assert (streq (world, "World"));
zstr_free (&hello);
zstr_free (&world);

//  Test pause/resume functionality
zstr_sendx (proxy, "PAUSE", NULL);
zsock_wait (proxy);
zstr_sendx (faucet, "Hello", "World", NULL);
zsock_set_rcvtimeo (sink, 100);
zstr_recvx (sink, &hello, &world, NULL);
assert (!hello && !world);

zstr_sendx (proxy, "RESUME", NULL);
zsock_wait (proxy);
zstr_recvx (sink, &hello, &world, NULL);
assert (streq (hello, "Hello"));
assert (streq (world, "World"));
zstr_free (&hello);
zstr_free (&world);

//  Test capture functionality
zsock_t *capture = zsock_new_pull ("inproc://capture");
assert (capture);

//  Switch on capturing, check that it works
zstr_sendx (proxy, "CAPTURE", "inproc://capture", NULL);
zsock_wait (proxy);
zstr_sendx (faucet, "Hello", "World", NULL);
zstr_recvx (sink, &hello, &world, NULL);
assert (streq (hello, "Hello"));
assert (streq (world, "World"));
zstr_free (&hello);
zstr_free (&world);

zstr_recvx (capture, &hello, &world, NULL);
assert (streq (hello, "Hello"));
assert (streq (world, "World"));
zstr_free (&hello);
zstr_free (&world);

zsock_destroy (&faucet);
zsock_destroy (&sink);
zsock_destroy (&capture);
zactor_destroy (&proxy);

//  Test socket creation dependency
proxy = zactor_new (zproxy, NULL);
assert (proxy);

sink = zsock_new_sub (">ipc://backend", "whatever");
assert (sink);

zstr_sendx (proxy, "BACKEND", "XPUB", "ipc://backend", NULL);
zsock_wait (proxy);

zsock_destroy(&sink);
zactor_destroy(&proxy);

#if (ZMQ_VERSION_MAJOR == 4)
// Test authentication functionality
#   define TESTDIR ".test_zproxy"

//  Create temporary directory for test files
zsys_dir_create (TESTDIR);

char *frontend = NULL;
char *backend = NULL;

//  Check there's no authentication
s_create_test_sockets (&proxy, &faucet, &sink, verbose);
s_bind_test_sockets (proxy, &frontend, &backend);
bool success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  Install the authenticator
zactor_t *auth = zactor_new (zauth, NULL);
assert (auth);
if (verbose) {
    zstr_sendx (auth, "VERBOSE", NULL);
    zsock_wait (auth);
}

//  Check there's no authentication on a default NULL server
s_bind_test_sockets (proxy, &frontend, &backend);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  When we set a domain on the server, we switch on authentication
//  for NULL sockets, but with no policies, the client connection
//  will be allowed.
zstr_sendx (proxy, "DOMAIN", "FRONTEND", "global", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  Blacklist 127.0.0.1, connection should fail
zstr_sendx (proxy, "DOMAIN", "FRONTEND", "global", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
zstr_sendx (auth, "DENY", "127.0.0.1", NULL);
zsock_wait (auth);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (!success);

//  Whitelist our address, which overrides the blacklist
zstr_sendx (proxy, "DOMAIN", "FRONTEND", "global", NULL);
zsock_wait (proxy);
zstr_sendx (proxy, "DOMAIN", "BACKEND", "global", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
zstr_sendx (auth, "ALLOW", "127.0.0.1", NULL);
zsock_wait (auth);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  Try PLAIN authentication

//  Test negative case (no server-side passwords defined)
zstr_sendx (proxy, "PLAIN", "FRONTEND", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
zsock_set_plain_username (faucet, "admin");
zsock_set_plain_password (faucet, "Password");
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (!success);

//  Test positive case (server-side passwords defined)
FILE *password = fopen (TESTDIR "/password-file", "w");
assert (password);
fprintf (password, "admin=Password\n");
fclose (password);
zstr_sendx (proxy, "PLAIN", "FRONTEND", NULL);
zsock_wait (proxy);
zstr_sendx (proxy, "PLAIN", "BACKEND", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
zsock_set_plain_username (faucet, "admin");
zsock_set_plain_password (faucet, "Password");
zsock_set_plain_username (sink, "admin");
zsock_set_plain_password (sink, "Password");
zstr_sendx (auth, "PLAIN", TESTDIR "/password-file", NULL);
zsock_wait (auth);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  Test negative case (bad client password)
zstr_sendx (proxy, "PLAIN", "FRONTEND", NULL);
zsock_wait (proxy);
s_bind_test_sockets (proxy, &frontend, &backend);
zsock_set_plain_username (faucet, "admin");
zsock_set_plain_password (faucet, "Bogus");
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (!success);

if (zsys_has_curve ()) {
    //  We'll create two new certificates and save the client public
    //  certificate on disk
    zcert_t *server_cert = zcert_new ();
    assert (server_cert);
    zcert_t *client_cert = zcert_new ();
    assert (client_cert);
    const char *public_key = zcert_public_txt (server_cert);
    const char *secret_key = zcert_secret_txt (server_cert);

    //  Try CURVE authentication

    //  Test without setting-up any authentication
    zstr_sendx (proxy, "CURVE", "FRONTEND", public_key, secret_key, NULL);
    zsock_wait (proxy);
    s_bind_test_sockets (proxy, &frontend, &backend);
    zcert_apply (client_cert, faucet);
    zsock_set_curve_serverkey (faucet, public_key);
    success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
    assert (!success);

    //  Test CURVE_ALLOW_ANY
    zstr_sendx (proxy, "CURVE", "FRONTEND", public_key, secret_key, NULL);
    zsock_wait (proxy);
    s_bind_test_sockets (proxy, &frontend, &backend);
    zcert_apply (client_cert, faucet);
    zsock_set_curve_serverkey (faucet, public_key);
    zstr_sendx (auth, "CURVE", CURVE_ALLOW_ANY, NULL);
    zsock_wait (auth);
    success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
    assert (success);

    //  Test with client certificate file in authentication folder
    zstr_sendx (proxy, "CURVE", "FRONTEND", public_key, secret_key, NULL);
    zsock_wait (proxy);
    zstr_sendx (proxy, "CURVE", "BACKEND", public_key, secret_key, NULL);
    zsock_wait (proxy);
    s_bind_test_sockets (proxy, &frontend, &backend);
    zcert_apply (client_cert, faucet);
    zsock_set_curve_serverkey (faucet, public_key);
    zcert_apply (client_cert, sink);
    zsock_set_curve_serverkey (sink, public_key);
    zcert_save_public (client_cert, TESTDIR "/mycert.txt");
    zstr_sendx (auth, "CURVE", TESTDIR, NULL);
    zsock_wait (auth);
    success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
    assert (success);

    zcert_destroy (&server_cert);
    zcert_destroy (&client_cert);
}

//  Remove the authenticator and check a normal connection works
zactor_destroy (&auth);
s_bind_test_sockets (proxy, &frontend, &backend);
success = s_can_connect (&proxy, &faucet, &sink, frontend, backend, verbose);
assert (success);

//  Cleanup
zsock_destroy (&faucet);
zsock_destroy (&sink);
zactor_destroy (&proxy);
zstr_free (&frontend);
zstr_free (&backend);

//  Delete temporary directory and test files
zsys_file_delete (TESTDIR "/password-file");
zsys_file_delete (TESTDIR "/mycert.txt");
zsys_dir_delete (TESTDIR);
#endif
----
