Permalink
822 lines (723 sloc) 27.1 KB
/* =========================================================================
zauth - authentication for ZeroMQ security mechanisms
Copyright (c) the Contributors as noted in the AUTHORS file.
This file is part of CZMQ, the high-level C binding for 0MQ:
http://czmq.zeromq.org.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
=========================================================================
*/
/*
@header
A zauth actor takes over authentication for all incoming connections in
its context. You can whitelist or blacklist peers based on IP address,
and define policies for securing PLAIN, CURVE, and GSSAPI connections.
@discuss
This class replaces zauth_v2, and is meant for applications that use the
CZMQ v3 API (meaning, zsock).
@end
*/
#include "czmq_classes.h"
#define ZAP_ENDPOINT "inproc://zeromq.zap.01"
// --------------------------------------------------------------------------
// The self_t structure holds the state for one actor instance
typedef struct {
zsock_t *pipe; // Actor command pipe
zsock_t *handler; // ZAP handler socket
zhashx_t *whitelist; // Whitelisted addresses
zhashx_t *blacklist; // Blacklisted addresses
zhashx_t *passwords; // PLAIN passwords, if loaded
zpoller_t *poller; // Socket poller
zcertstore_t *certstore; // CURVE certificate store, if loaded
bool allow_any; // CURVE allows arbitrary clients
bool terminated; // Did caller ask us to quit?
bool verbose; // Verbose logging enabled?
} self_t;
static void
s_self_destroy (self_t **self_p)
{
assert (self_p);
if (*self_p) {
self_t *self = *self_p;
zhashx_destroy (&self->passwords);
zhashx_destroy (&self->whitelist);
zhashx_destroy (&self->blacklist);
zcertstore_destroy (&self->certstore);
zpoller_destroy (&self->poller);
if (self->handler) {
zsock_unbind (self->handler, ZAP_ENDPOINT);
zsock_destroy (&self->handler);
}
freen (self);
*self_p = NULL;
}
}
static self_t *
s_self_new (zsock_t *pipe, zcertstore_t *certstore)
{
self_t *self = (self_t *) zmalloc (sizeof (self_t));
assert (self);
if (certstore) {
self->certstore = certstore;
self->allow_any = false;
}
self->pipe = pipe;
self->whitelist = zhashx_new ();
assert (self->whitelist);
self->blacklist = zhashx_new ();
// Create ZAP handler and get ready for requests
assert (self->blacklist);
self->handler = zsock_new (ZMQ_REP);
assert (self->handler);
int rc = zsock_bind (self->handler, ZAP_ENDPOINT);
assert (rc == 0);
self->poller = zpoller_new (self->pipe, self->handler, NULL);
assert (self->poller);
return self;
}
// --------------------------------------------------------------------------
// Handle a command from calling application
static int
s_self_handle_pipe (self_t *self)
{
// Get the whole message off the pipe in one go
zmsg_t *request = zmsg_recv (self->pipe);
if (!request)
return -1; // Interrupted
char *command = zmsg_popstr (request);
if (self->verbose)
zsys_info ("zauth: API command=%s", command);
if (streq (command, "ALLOW")) {
char *address = zmsg_popstr (request);
while (address) {
if (self->verbose)
zsys_info ("zauth: - whitelisting ipaddress=%s", address);
zhashx_insert (self->whitelist, address, "OK");
zstr_free (&address);
address = zmsg_popstr (request);
}
zsock_signal (self->pipe, 0);
}
else
if (streq (command, "DENY")) {
char *address = zmsg_popstr (request);
while (address) {
if (self->verbose)
zsys_info ("zauth: - blacklisting ipaddress=%s", address);
zhashx_insert (self->blacklist, address, "OK");
zstr_free (&address);
address = zmsg_popstr (request);
}
zsock_signal (self->pipe, 0);
}
else
if (streq (command, "PLAIN")) {
// Get password file and load into zhash table
// If the file doesn't exist we'll get an empty table
char *filename = zmsg_popstr (request);
zhashx_destroy (&self->passwords);
self->passwords = zhashx_new ();
if (zhashx_load (self->passwords, filename) && self->verbose)
zsys_info ("zauth: could not load file=%s", filename);
zstr_free (&filename);
zsock_signal (self->pipe, 0);
}
else
if (streq (command, "CURVE")) {
// If location is CURVE_ALLOW_ANY, allow all clients. Otherwise
// treat location as a directory that holds the certificates.
char *location = zmsg_popstr (request);
if (streq (location, CURVE_ALLOW_ANY))
self->allow_any = true;
else {
zcertstore_destroy (&self->certstore);
// FIXME: what if this fails?
self->certstore = zcertstore_new (location);
self->allow_any = false;
}
zstr_free (&location);
zsock_signal (self->pipe, 0);
}
else
if (streq (command, "GSSAPI"))
// GSSAPI authentication is not yet implemented here
zsock_signal (self->pipe, 0);
else
if (streq (command, "VERBOSE")) {
self->verbose = true;
zsock_signal (self->pipe, 0);
}
else
if (streq (command, "$TERM"))
self->terminated = true;
else {
zsys_error ("zauth: - invalid command: %s", command);
assert (false);
}
zstr_free (&command);
zmsg_destroy (&request);
return 0;
}
// --------------------------------------------------------------------------
// A small class for working with ZAP requests and replies.
// Used internally in zauth to simplify working with RFC 27 messages.
// Structure of a ZAP request
typedef struct {
zsock_t *handler; // Socket we're talking to
bool verbose; // Log ZAP requests and replies?
char *version; // Version number, must be "1.0"
char *sequence; // Sequence number of request
char *domain; // Server socket domain
char *address; // Client IP address
char *identity; // Server socket idenntity
char *mechanism; // Security mechansim
char *username; // PLAIN user name
char *password; // PLAIN password, in clear text
char *client_key; // CURVE client public key in ASCII
char *principal; // GSSAPI client principal
char *user_id; // User-Id to return in the ZAP Response
} zap_request_t;
static void
s_zap_request_destroy (zap_request_t **self_p)
{
assert (self_p);
if (*self_p) {
zap_request_t *self = *self_p;
freen (self->version);
freen (self->sequence);
freen (self->domain);
freen (self->address);
freen (self->identity);
freen (self->mechanism);
freen (self->username);
freen (self->password);
freen (self->client_key);
freen (self->principal);
// self->user_id is a pointer to one of the above fields
freen (self);
*self_p = NULL;
}
}
// Receive a valid ZAP request from the handler socket
// If the request was not valid, returns NULL.
static zap_request_t *
s_zap_request_new (zsock_t *handler, bool verbose)
{
zap_request_t *self = (zap_request_t *) zmalloc (sizeof (zap_request_t));
if (!self)
return NULL;
// Store handler socket so we can send a reply easily
self->handler = handler;
self->verbose = verbose;
zmsg_t *request = zmsg_recv (handler);
if (!request) { // interrupted
s_zap_request_destroy (&self);
return NULL;
}
// Get all standard frames off the handler socket
self->version = zmsg_popstr (request);
self->sequence = zmsg_popstr (request);
self->domain = zmsg_popstr (request);
self->address = zmsg_popstr (request);
self->identity = zmsg_popstr (request);
self->mechanism = zmsg_popstr (request);
// If the version is wrong, we're linked with a bogus libzmq, so die
assert (streq (self->version, "1.0"));
// Get mechanism-specific frames
if (streq (self->mechanism, "PLAIN")) {
self->username = zmsg_popstr (request);
self->password = zmsg_popstr (request);
}
else
if (streq (self->mechanism, "CURVE")) {
zframe_t *frame = zmsg_pop (request);
assert (zframe_size (frame) == 32);
self->client_key = (char *) zmalloc (41);
#if (ZMQ_VERSION_MAJOR == 4)
zmq_z85_encode (self->client_key, zframe_data (frame), 32);
#endif
zframe_destroy (&frame);
}
else
if (streq (self->mechanism, "GSSAPI"))
self->principal = zmsg_popstr (request);
if (self->verbose)
zsys_info ("zauth: ZAP request mechanism=%s ipaddress=%s",
self->mechanism, self->address);
zmsg_destroy (&request);
return self;
}
// Send a ZAP reply to the handler socket
static int
s_zap_request_reply (zap_request_t *self, char *status_code, char *status_text, unsigned char *metadata, size_t metasize)
{
if (self->verbose)
zsys_info ("zauth: - ZAP reply status_code=%s status_text=%s",
status_code, status_text);
zmsg_t *msg = zmsg_new ();
int rc = zmsg_addstr(msg, "1.0");
assert (rc == 0);
rc = zmsg_addstr(msg, self->sequence);
assert (rc == 0);
rc = zmsg_addstr(msg, status_code);
assert (rc == 0);
rc = zmsg_addstr(msg, status_text);
assert (rc == 0);
rc = zmsg_addstr(msg, self->user_id ? self->user_id : "");
assert (rc == 0);
rc = zmsg_addmem(msg, metadata, metasize);
assert (rc == 0);
rc = zmsg_send(&msg, self->handler);
assert (rc == 0);
return 0;
}
// --------------------------------------------------------------------------
// Handle an authentication request from libzmq core
// Helper for s_add_property
// THIS IS A COPY OF zmq::put_uint32 (<zmq>/src/wire.hpp)
static void
s_put_uint32 (unsigned char *buffer_, uint32_t value)
{
buffer_ [0] = (unsigned char) (((value) >> 24) & 0xff);
buffer_ [1] = (unsigned char) (((value) >> 16) & 0xff);
buffer_ [2] = (unsigned char) (((value) >> 8) & 0xff);
buffer_ [3] = (unsigned char) (value & 0xff);
}
// Add metadata property to ptr
// THIS IS AN ADAPTATION OF zmq::mechanism_t::add_property (<zmq>/src/mechanism.cpp)
static size_t
s_add_property (unsigned char *ptr, const char *name, const void *value, size_t value_len)
{
const size_t name_len = strlen (name);
assert (name_len <= 255);
*ptr++ = (unsigned char) name_len;
memcpy (ptr, name, name_len);
ptr += name_len;
assert (value_len <= 0x7FFFFFFF);
s_put_uint32 (ptr, (uint32_t) value_len);
ptr += 4;
memcpy (ptr, value, value_len);
return 1 + name_len + 4 + value_len;
}
static bool
s_authenticate_plain (self_t *self, zap_request_t *request)
{
if (self->passwords) {
zhashx_refresh (self->passwords);
char *password = (char *) zhashx_lookup (self->passwords, request->username);
if (password && streq (password, request->password)) {
if (self->verbose)
zsys_info ("zauth: - allowed (PLAIN) username=%s password=%s",
request->username, request->password);
request->user_id = request->username;
return true;
}
else {
if (self->verbose)
zsys_info ("zauth: - denied (PLAIN) username=%s password=%s",
request->username, request->password);
return false;
}
}
else {
if (self->verbose)
zsys_info ("zauth: - denied (PLAIN) no password file defined");
return false;
}
}
static bool
s_authenticate_curve (self_t *self, zap_request_t *request, unsigned char **metadata)
{
if (self->allow_any) {
if (self->verbose)
zsys_info ("zauth: - allowed (CURVE allow any client)");
return true;
}
else
if (self->certstore) {
zcert_t *cert = zcertstore_lookup (self->certstore, request->client_key);
if (cert != NULL) {
zlist_t *meta_k = zcert_meta_keys (cert);
while (true) {
void *key = zlist_next (meta_k);
if (key == NULL) {
break;
}
const char *val = zcert_meta(cert, (const char *) key);
if (val == NULL) {
break;
}
*metadata += s_add_property(*metadata, (const char *) key, val, strlen (val));
}
zlist_destroy (&meta_k);
if (self->verbose)
zsys_info ("zauth: - allowed (CURVE) client_key=%s", request->client_key);
request->user_id = request->client_key;
return true;
}
}
if (self->verbose)
zsys_info ("zauth: - denied (CURVE) client_key=%s", request->client_key);
return false;
}
static bool
s_authenticate_gssapi (self_t *self, zap_request_t *request)
{
if (self->verbose)
zsys_info ("zauth: - allowed (GSSAPI) principal=%s identity=%s",
request->principal, request->identity);
request->user_id = request->principal;
return true;
}
// TODO: allow regular expressions in addresses
static int
s_self_authenticate (self_t *self)
{
zap_request_t *request = s_zap_request_new (self->handler, self->verbose);
if (!request)
return 0; // Interrupted, no request to process
// Is address explicitly whitelisted or blacklisted?
bool allowed = false;
bool denied = false;
// Curve certificate metadata
unsigned char * const metabuf = (unsigned char *) malloc (512);
assert (metabuf != NULL);
unsigned char *metadata = metabuf;
if (zhashx_size (self->whitelist)) {
if (zhashx_lookup (self->whitelist, request->address)) {
allowed = true;
if (self->verbose)
zsys_info ("zauth: - passed (whitelist) address=%s", request->address);
}
else {
denied = true;
if (self->verbose)
zsys_info ("zauth: - denied (not in whitelist) address=%s", request->address);
}
}
else
if (zhashx_size (self->blacklist)) {
if (zhashx_lookup (self->blacklist, request->address)) {
denied = true;
if (self->verbose)
zsys_info ("zauth: - denied (blacklist) address=%s", request->address);
}
else {
allowed = true;
if (self->verbose)
zsys_info ("zauth: - passed (not in blacklist) address=%s", request->address);
}
}
// Mechanism-specific checks
if (!denied) {
if (streq (request->mechanism, "NULL") && !allowed) {
// For NULL, we allow if the address wasn't blacklisted
if (self->verbose)
zsys_info ("zauth: - allowed (NULL)");
allowed = true;
}
else
if (streq (request->mechanism, "PLAIN"))
// For PLAIN, even a whitelisted address must authenticate
allowed = s_authenticate_plain (self, request);
else
if (streq (request->mechanism, "CURVE"))
// For CURVE, even a whitelisted address must authenticate
allowed = s_authenticate_curve (self, request, &metadata);
else
if (streq (request->mechanism, "GSSAPI"))
// For GSSAPI, even a whitelisted address must authenticate
allowed = s_authenticate_gssapi (self, request);
}
if (allowed) {
size_t metasize = metadata - metabuf;
s_zap_request_reply (request, "200", "OK", metabuf, metasize);
} else
s_zap_request_reply (request, "400", "No access", (unsigned char *) "", 0);
s_zap_request_destroy (&request);
free (metabuf);
return 0;
}
// --------------------------------------------------------------------------
// zauth() implements the zauth actor interface
void
zauth (zsock_t *pipe, void *certstore)
{
self_t *self = s_self_new (pipe, (zcertstore_t *)certstore);
assert (self);
// Signal successful initialization
zsock_signal (pipe, 0);
while (!self->terminated) {
zsock_t *which = (zsock_t *) zpoller_wait (self->poller, -1);
if (which == self->pipe)
s_self_handle_pipe (self);
else
if (which == self->handler)
s_self_authenticate (self);
else
if (zpoller_terminated (self->poller))
break; // Interrupted
}
s_self_destroy (&self);
}
// --------------------------------------------------------------------------
// Selftest
#if (ZMQ_VERSION_MAJOR == 4)
// Destroys old sockets and returns new ones
static void
s_renew_sockets (zsock_t **server, zsock_t **client)
{
zsock_destroy (client);
zsock_destroy (server);
*server = zsock_new (ZMQ_PULL);
assert (*server);
*client = zsock_new (ZMQ_PUSH);
assert (*client);
}
// Checks whether client can connect to server
static bool
s_can_connect (zsock_t **server, zsock_t **client, bool renew)
{
int port_nbr = zsock_bind (*server, "tcp://127.0.0.1:*");
assert (port_nbr > 0);
int rc = zsock_connect (*client, "tcp://127.0.0.1:%d", port_nbr);
assert (rc == 0);
// Give the connection time to fail if that's the plan
if (zsock_mechanism (*client) == ZMQ_CURVE)
zclock_sleep (1500);
else
zclock_sleep (200);
// By default PUSH sockets block if there's no peer
zsock_set_sndtimeo (*client, 200);
zstr_send (*client, "Hello, World");
zpoller_t *poller = zpoller_new (*server, NULL);
assert (poller);
bool success = (zpoller_wait (poller, 400) == *server);
zpoller_destroy (&poller);
if (renew == true)
s_renew_sockets (server, client);
return success;
}
static void
s_test_loader (zcertstore_t *certstore)
{
zcertstore_empty (certstore);
byte public_key [32] = { 105, 76, 150, 58, 214, 191, 218, 65, 50, 172,
131, 188, 247, 211, 136, 170, 227, 26, 57, 170,
185, 63, 246, 225, 177, 230, 12, 8, 134, 136,
105, 106 };
byte secret_key [32] = { 245, 217, 172, 73, 106, 28, 195, 17, 218, 132,
135, 209, 99, 240, 98, 232, 7, 137, 244, 100,
242, 23, 29, 114, 70, 223, 83, 1, 113, 207,
132, 149 };
zcert_t *cert = zcert_new_from (public_key, secret_key);
assert (cert);
zcertstore_insert (certstore, &cert);
}
#endif
void
zauth_test (bool verbose)
{
printf (" * zauth: ");
#if (ZMQ_VERSION_MAJOR == 4)
if (verbose)
printf ("\n");
// @selftest
const char *SELFTEST_DIR_RW = "src/selftest-rw";
const char *testbasedir = ".test_zauth";
const char *testpassfile = "password-file";
const char *testcertfile = "mycert.txt";
char *basedirpath = NULL; // subdir in a test, under SELFTEST_DIR_RW
char *passfilepath = NULL; // pathname to testfile in a test, in dirpath
char *certfilepath = NULL; // pathname to testfile in a test, in dirpath
basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);
assert (basedirpath);
passfilepath = zsys_sprintf ("%s/%s", basedirpath, testpassfile);
assert (passfilepath);
certfilepath = zsys_sprintf ("%s/%s", basedirpath, testcertfile);
assert (certfilepath);
// Make sure old aborted tests do not hinder us
zdir_t *dir = zdir_new (basedirpath, NULL);
if (dir) {
zdir_remove (dir, true);
zdir_destroy (&dir);
}
zsys_file_delete (passfilepath);
zsys_file_delete (certfilepath);
zsys_dir_delete (basedirpath);
// Create temporary directory for test files
zsys_dir_create (basedirpath);
// Check there's no authentication
zsock_t *server = zsock_new (ZMQ_PULL);
assert (server);
zsock_t *client = zsock_new (ZMQ_PUSH);
assert (client);
bool success = s_can_connect (&server, &client, true);
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
success = s_can_connect (&server, &client, true);
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.
zsock_set_zap_domain (server, "global");
success = s_can_connect (&server, &client, true);
assert (success);
// Blacklist 127.0.0.1, connection should fail
zsock_set_zap_domain (server, "global");
zstr_sendx (auth, "DENY", "127.0.0.1", NULL);
zsock_wait (auth);
success = s_can_connect (&server, &client, true);
assert (!success);
// Whitelist our address, which overrides the blacklist
zsock_set_zap_domain (server, "global");
zstr_sendx (auth, "ALLOW", "127.0.0.1", NULL);
zsock_wait (auth);
success = s_can_connect (&server, &client, true);
assert (success);
// Try PLAIN authentication
zsock_set_zap_domain (server, "global");
zsock_set_plain_server (server, 1);
zsock_set_plain_username (client, "admin");
zsock_set_plain_password (client, "Password");
success = s_can_connect (&server, &client, true);
assert (!success);
FILE *password = fopen (passfilepath, "w");
assert (password);
fprintf (password, "admin=Password\n");
fclose (password);
zsock_set_zap_domain (server, "global");
zsock_set_plain_server (server, 1);
zsock_set_plain_username (client, "admin");
zsock_set_plain_password (client, "Password");
zstr_sendx (auth, "PLAIN", passfilepath, NULL);
zsock_wait (auth);
success = s_can_connect (&server, &client, false);
assert (success);
#if (ZMQ_VERSION >= ZMQ_MAKE_VERSION (4, 1, 0))
// Test that the User-Id metadata is present
zframe_t *frame = zframe_recv (server);
assert (frame != NULL);
const char *user_id = zframe_meta (frame, "User-Id");
assert (user_id != NULL);
assert (streq (user_id, "admin"));
zframe_destroy (&frame);
#endif
s_renew_sockets(&server, &client);
zsock_set_zap_domain (server, "global");
zsock_set_plain_server (server, 1);
zsock_set_plain_username (client, "admin");
zsock_set_plain_password (client, "Bogus");
success = s_can_connect (&server, &client, true);
assert (!success);
if (zsys_has_curve ()) {
// Try CURVE authentication
// We'll create two new certificates and save the client public
// certificate on disk; in a real case we'd transfer this securely
// from the client machine to the server machine.
zcert_t *server_cert = zcert_new ();
assert (server_cert);
zcert_t *client_cert = zcert_new ();
assert (client_cert);
const char *server_key = zcert_public_txt (server_cert);
// Test without setting-up any authentication
zcert_apply (server_cert, server);
zcert_apply (client_cert, client);
zsock_set_curve_server (server, 1);
zsock_set_curve_serverkey (client, server_key);
zsock_set_zap_domain (server, "global");
success = s_can_connect (&server, &client, true);
assert (!success);
// Test CURVE_ALLOW_ANY
zcert_apply (server_cert, server);
zcert_apply (client_cert, client);
zsock_set_curve_server (server, 1);
zsock_set_curve_serverkey (client, server_key);
zstr_sendx (auth, "CURVE", CURVE_ALLOW_ANY, NULL);
zsock_wait (auth);
success = s_can_connect (&server, &client, true);
assert (success);
// Test full client authentication using certificates
zcert_set_meta (client_cert, "Hello", "%s", "World!");
zcert_apply (server_cert, server);
zcert_apply (client_cert, client);
zsock_set_curve_server (server, 1);
zsock_set_curve_serverkey (client, server_key);
zcert_save_public (client_cert, certfilepath);
zstr_sendx (auth, "CURVE", basedirpath, NULL);
zsock_wait (auth);
zsock_set_zap_domain (server, "global");
success = s_can_connect (&server, &client, false);
assert (success);
#if (ZMQ_VERSION >= ZMQ_MAKE_VERSION (4, 1, 0))
// Test send/recv certificate metadata
zframe_t *frame = zframe_recv (server);
assert (frame != NULL);
const char *meta = zframe_meta (frame, "Hello");
assert (meta != NULL);
assert (streq (meta, "World!"));
const char *user_id = zframe_meta (frame, "User-Id");
assert (user_id != NULL);
assert (streq (user_id, zcert_public_txt(client_cert)));
zframe_destroy (&frame);
s_renew_sockets(&server, &client);
#endif
zcert_destroy (&server_cert);
zcert_destroy (&client_cert);
// Test custom zcertstore
zcertstore_t *certstore = zcertstore_new (NULL);
zcertstore_set_loader (certstore, s_test_loader, NULL, NULL);
zactor_destroy(&auth);
auth = zactor_new (zauth, certstore);
assert (auth);
if (verbose) {
zstr_sendx (auth, "VERBOSE", NULL);
zsock_wait (auth);
}
byte public_key [32] = { 105, 76, 150, 58, 214, 191, 218, 65, 50, 172,
131, 188, 247, 211, 136, 170, 227, 26, 57, 170,
185, 63, 246, 225, 177, 230, 12, 8, 134, 136,
105, 106 };
byte secret_key [32] = { 245, 217, 172, 73, 106, 28, 195, 17, 218, 132,
135, 209, 99, 240, 98, 232, 7, 137, 244, 100,
242, 23, 29, 114, 70, 223, 83, 1, 113, 207,
132, 149 };
zcert_t *shared_cert = zcert_new_from (public_key, secret_key);
assert (shared_cert);
zcert_apply (shared_cert, server);
zcert_apply (shared_cert, client);
zsock_set_curve_server (server, 1);
zsock_set_curve_serverkey (client, "x?T*N/1Y{8goubv{Ts}#&#f}TXJ//DVe#D2HkoLU");
success = s_can_connect (&server, &client, true);
assert (success);
zcert_destroy (&shared_cert);
}
// Remove the authenticator and check a normal connection works
zactor_destroy (&auth);
success = s_can_connect (&server, &client, true);
assert (success);
zsock_destroy (&client);
zsock_destroy (&server);
// Delete all test files
dir = zdir_new (basedirpath, NULL);
assert (dir);
zdir_remove (dir, true);
zdir_destroy (&dir);
zstr_free (&passfilepath);
zstr_free (&certfilepath);
zstr_free (&basedirpath);
#endif
#if defined (__WINDOWS__)
zsys_shutdown();
#endif
// @end
printf ("OK\n");
}