protocol.c revision 15a44745412679c30a6d022733925af70a38b715
/*
* Copyright (C) 1996-2000 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
* INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: protocol.c,v 1.30 2000/07/27 09:54:23 tale Exp $ */
/*
* Functions supporting the object management protocol.
*/
#include <config.h>
#include <stddef.h> /* NULL */
#include <stdlib.h> /* random */
#include <isc/buffer.h>
#include <isc/mem.h>
#include <isc/string.h>
#include <isc/util.h>
#include <dst/result.h>
#include <omapi/private.h>
#include <omapi/result.h>
/*
* OMAPI protocol header, version 1.00
*/
typedef struct omapi_protocolheader {
unsigned int authlen; /* Length of authenticator. */
unsigned int authid; /* Authenticator object ID. */
unsigned int op; /* Operation code. */
omapi_handle_t handle; /* Handle of object being operated on or 0. */
unsigned int id; /* Transaction ID. */
unsigned int rid; /* ID of transaction responding to. */
} omapi_protocolheader_t;
isc_result_t
omapi_protocol_connect(omapi_object_t *h, const char *server_name,
in_port_t port, omapi_object_t *authinfo)
{
isc_result_t result;
omapi_protocol_t *obj = NULL;
REQUIRE(h != NULL && server_name != NULL);
REQUIRE(port != 0);
result = omapi_object_create((omapi_object_t **)&obj,
omapi_type_protocol, sizeof(*obj));
if (result != ISC_R_SUCCESS)
return (result);
OBJECT_REF(&h->outer, obj);
OBJECT_REF(&obj->inner, h);
/*
* Drop this function's direct reference to the protocol object
* so that connect_toserver or send_intro can free the connection
* and protocol objects in the event of an error.
*/
OBJECT_DEREF(&obj);
result = connect_toserver(h->outer, server_name, port);
/*
* Send the introductory message. This will also wait (via
* connection_send) for the server's introductory message before
* proceeding. While the original design for OMAPI declared that this
* was to be entirely asynchronous, it just won't work for the client
* side program to go storming ahead, making calls that try to use the
* connection object, when it is possible that the thread that reads
* the socket will wake up with the server's intro message, find some
* sort of problem, and then blow away the connection object while the
* client program is asynchronously trying to use it. (This could be
* done, of course, with a lot more thread locking than currently
* happens.)
*
* If send_intro fails, the connection is already destroyed.
*/
if (result == ISC_R_SUCCESS)
result = send_intro(h->outer, OMAPI_PROTOCOL_VERSION);
if (authinfo != NULL)
OBJECT_REF(&((omapi_protocol_t *)h->outer)->authinfo,authinfo);
return (result);
}
void
omapi_protocol_disconnect(omapi_object_t *handle, isc_boolean_t force) {
omapi_protocol_t *protocol;
omapi_connection_t *connection;
REQUIRE(handle != NULL);
protocol = (omapi_protocol_t *)handle->outer;
if (protocol == NULL)
return; /* Already disconnected. */
INSIST(protocol->type == omapi_type_protocol);
connection = (omapi_connection_t *)protocol->outer;
INSIST(connection != NULL &&
connection->type == omapi_type_connection);
omapi_connection_disconnect((omapi_object_t *)connection, force);
}
/*
* Send the protocol introduction message.
*/
isc_result_t
send_intro(omapi_object_t *h, unsigned int ver) {
isc_result_t result;
omapi_protocol_t *p;
omapi_connection_t *connection;
REQUIRE(h != NULL && h->type == omapi_type_protocol);
REQUIRE(h->outer != NULL && h->outer->type == omapi_type_connection);
p = (omapi_protocol_t *)h;
connection = (omapi_connection_t *)h->outer;
result = omapi_connection_putuint32((omapi_object_t *)connection, ver);
if (result == ISC_R_SUCCESS)
result =
omapi_connection_putuint32((omapi_object_t *)connection,
sizeof(omapi_protocolheader_t));
/*
* Require the other end to send an intro - this kicks off the
* protocol input state machine. This does not use connection_require
* to set the number of bytes required because then a socket recv would
* be queued. To simplify the MT issues, the library only expects to
* have one task outstanding at a time, so the number of bytes
* that will be expected is set here, but the actual recv for
* them is not queued until after the send event posts.
*/
if (result == ISC_R_SUCCESS) {
p->state = omapi_protocol_intro_wait;
connection->bytes_needed = 8;
/*
* Make up an initial transaction ID for this connection.
* XXXDCL better generator than random()?
*/
p->next_xid = random();
result = connection_send(connection);
/*
* The client waited for the result; the server did not.
* The server's result will always be ISC_R_SUCCESS.
*
* If the client's result is not ISC_R_SUCCESS, the connection
* was already closed by the socket event handler that got
* the error.
*/
} else
/*
* One of the calls to omapi_connection_put* failed. As of the
* time of writing this comment, that would pretty much only
* happen if the required output buffer space could be
* dynamically allocated.
*
* The server is in listener_accept, so the connection can just
* be freed right here; listener_accept will not try to
* use it when this function exits.
*
* The client is in omapi_protocol_connect, its driving thread.
* It too has no events pending, so the connection will
* be freed.
*/
omapi_connection_disconnect(h->outer, OMAPI_FORCE_DISCONNECT);
return (result);
}
/*
* Set up a listener for the omapi protocol.
*/
isc_result_t
omapi_protocol_listen(omapi_object_t *manager, isc_sockaddr_t *addr,
isc_boolean_t ((*verify_connection)
(isc_sockaddr_t *incoming,
void *connect_arg)),
isc_boolean_t ((*verify_key)
(const char *name,
unsigned int algorithm,
void *key_arg)),
isc_taskaction_t destroy_action, void *arg)
{
return (omapi_listener_listen(manager, addr, verify_connection,
verify_key, destroy_action, arg));
}
isc_result_t
send_status(omapi_object_t *po, isc_result_t waitstatus,
unsigned int rid, const char *msg)
{
isc_result_t result;
omapi_object_t *message = NULL;
REQUIRE(po != NULL && po->type == omapi_type_protocol);
REQUIRE(po->outer != NULL && po->outer->type == omapi_type_connection);
REQUIRE(! ((omapi_connection_t *)po->outer)->is_client);
result = omapi_message_create(&message);
if (result != ISC_R_SUCCESS)
return (result);
result = omapi_object_setinteger(message, "op", OMAPI_OP_STATUS);
if (result == ISC_R_SUCCESS)
result = omapi_object_setinteger(message, "rid", (int)rid);
if (result == ISC_R_SUCCESS)
result = omapi_object_setinteger(message, "result",
(int)waitstatus);
/*
* If a message has been provided, send it.
*/
if (result == ISC_R_SUCCESS && msg != NULL)
result = omapi_object_setstring(message, "message", msg);
if (result == ISC_R_SUCCESS)
result = omapi_message_send(message, po);
OBJECT_DEREF(&message);
return (result);
}
isc_result_t
send_update(omapi_object_t *po, unsigned int rid, omapi_object_t *object) {
isc_result_t result;
omapi_object_t *message = NULL;
REQUIRE(po != NULL && po->type == omapi_type_protocol);
REQUIRE(! ((omapi_connection_t *)po->outer)->is_client);
result = omapi_message_create(&message);
if (result != ISC_R_SUCCESS)
return (result);
result = omapi_object_setinteger(message, "op", OMAPI_OP_UPDATE);
if (result == ISC_R_SUCCESS && rid != 0) {
omapi_handle_t handle;
result = omapi_object_setinteger(message, "rid", (int)rid);
if (result == ISC_R_SUCCESS)
result = object_gethandle(&handle, object);
if (result == ISC_R_SUCCESS)
result = omapi_object_setinteger(message, "handle",
(int)handle);
}
if (result == ISC_R_SUCCESS)
result = omapi_object_setobject(message, "object", object);
if (result == ISC_R_SUCCESS)
result = omapi_message_send(message, po);
OBJECT_DEREF(&message);
return (result);
}
static isc_result_t
dispatch_messages(omapi_protocol_t *protocol,
omapi_connection_t *connection)
{
isc_uint16_t nlen;
isc_uint32_t vlen;
isc_result_t result;
/*
* XXXDCL figure out how come when this function throws
* an error, it does not seem to be seen by the driving program.
* (this comment may no longer be true, but bears testing anyway)
*/
/*
* We get here because we requested that we be woken up after
* some number of bytes were read, and that number of bytes
* has in fact been read.
*/
switch (protocol->state) {
case omapi_protocol_intro_wait:
/*
* Get protocol version and header size in network byte order.
*/
connection_getuint32(connection, &protocol->protocol_version);
connection_getuint32(connection, &protocol->header_size);
/*
* Currently only the current protocol version is supported.
*/
if (protocol->protocol_version != OMAPI_PROTOCOL_VERSION)
return (OMAPI_R_VERSIONMISMATCH);
if (protocol->header_size < sizeof(omapi_protocolheader_t))
return (OMAPI_R_PROTOCOLERROR);
/*
* The next thing that shows up on incoming connections
* should be a message header.
*/
protocol->state = omapi_protocol_header_wait;
/*
* The client needs to have bytes_needed primed for the
* size of a message header, so that when send_done runs,
* it can kick off an isc_socket_recv (via connection_require)
* to get the server's response. It does this in
* omapi_message_send, so nothing need be done here now.
*
* The server needs to actually kick off its recv now to
* be ready for the first message from the client. The
* server's startup path looks like this:
* 1 server sends intro, bytes_needed is set to intro size (8).
* 2 send_done posts, recv of 8 for intro is queued.
* 3 recv_done posts, calls the protocol_signalhandler and
* ends up here.
*/
if (connection->is_client) {
result = OMAPI_R_NOTYET;
break;
}
/*
* Register a need for the number of bytes in a header, and if
* that many are here already, process them immediately.
*/
result = connection_require(connection, protocol->header_size);
if (result != ISC_R_SUCCESS)
break;
/* FALLTHROUGH */
case omapi_protocol_header_wait:
result = omapi_message_create((omapi_object_t **)
&protocol->message);
if (result != ISC_R_SUCCESS)
break;
if (protocol->key != NULL) {
protocol->dstctx = NULL;
protocol->verify_result =
dst_context_create(protocol->key,
omapi_mctx,
&protocol->dstctx);
protocol->dst_update = ISC_TRUE;
}
/*
* Fetch the header values.
*/
/* XXXDCL authid is unused */
connection_getuint32(connection, &protocol->message->authid);
/* XXXTL bind the authenticator here! */
connection_getuint32(connection, &protocol->message->authlen);
connection_getuint32(connection, &protocol->message->op);
connection_getuint32(connection, &protocol->message->h);
connection_getuint32(connection, &protocol->message->id);
connection_getuint32(connection, &protocol->message->rid);
/*
* If there was any extra header data, skip over it,
* because it has no use in this version of the protocol.
*/
if (protocol->header_size > sizeof(omapi_protocolheader_t))
connection_copyout(NULL, connection,
(protocol->header_size -
sizeof(omapi_protocolheader_t)));
/*
* XXXTL must compute partial signature across the preceding
* bytes. Also, if authenticator specifies encryption as well
* as signing, we may have to decrypt the data on the way in.
*/
/*
* After reading the header, first read in message-specific
* values, then object values.
*/
protocol->reading_message_values = ISC_TRUE;
need_name_length:
/*
* Need to get the 16-bit length of the value's name.
*/
protocol->state = omapi_protocol_name_length_wait;
result = connection_require(connection, 2);
if (result != ISC_R_SUCCESS)
break;
/* FALLTHROUGH */
case omapi_protocol_name_length_wait:
connection_getuint16(connection, &nlen);
/*
* A zero-length name signals the end of name+value pairs.
*/
if (nlen == 0) {
/*
* If the message values were being read, now
* the object values need to be read. Otherwise
* move on to reading the authenticator.
*/
if (protocol->reading_message_values) {
protocol->reading_message_values = ISC_FALSE;
/*
* The goto could be removed by setting the
* state and doing omapi_connection_require()
* here, then returning the result to
* protocol_signalhandler which would call
* this function immediately if the result
* was ISC_R_SUCCESS, but that seems even
* more obtuse than using goto.
*/
goto need_name_length;
}
/*
* If the authenticator length is zero, there's no
* signature to read in, so go straight to processing
* the message.
*/
if (protocol->message->authlen == 0)
goto message_done;
/*
* The next thing that is expected is the message
* signature.
*/
protocol->state = omapi_protocol_signature_wait;
/* Wait for the number of bytes specified for the
* authenticator. If they are all here, go read it in.
* As noted above, the goto could be removed by
* returning the result to the caller no matter
* what its value, because the protocol_signalhandler
* would just call this function right back, but
* something seems more obtuse about that than goto.
*/
result = connection_require(connection,
protocol->message->authlen);
if (result == ISC_R_SUCCESS)
goto signature_wait;
else
break;
}
/*
* Non-zero name length. Allocate a buffer for the name
* then wait for all its bytes to be available.
*/
result = omapi_string_create(&protocol->name, nlen);
if (result != ISC_R_SUCCESS)
break;
protocol->state = omapi_protocol_name_wait;
result = connection_require(connection, nlen);
if (result != ISC_R_SUCCESS)
break;
/* FALLTHROUGH */
case omapi_protocol_name_wait:
connection_copyout(protocol->name->value, connection,
protocol->name->len);
/*
* Wait for the 32-bit length of the value.
*/
protocol->state = omapi_protocol_value_length_wait;
result = connection_require(connection, 4);
if (result != ISC_R_SUCCESS)
break;
/* FALLTHROUGH */
case omapi_protocol_value_length_wait:
connection_getuint32(connection, &vlen);
/*
* Zero-length values are allowed; they are for deleted
* values. If the value length is zero, skip the read but
* still store the name with its zero length value.
*/
if (vlen == 0)
goto insert_new_value;
result = omapi_data_create(&protocol->value,
omapi_datatype_data, vlen);
if (result != ISC_R_SUCCESS)
break;
/*
* Check to see if all the bytes of the value are here.
*/
protocol->state = omapi_protocol_value_wait;
result = connection_require(connection, vlen);
if (result != ISC_R_SUCCESS)
break;
/* FALLTHROUGH */
case omapi_protocol_value_wait:
connection_copyout(protocol->value->u.buffer.value,
connection,
protocol->value->u.buffer.len);
/*
* Silence the gcc message "warning: `result' might be used
* uninitialized in this function"
*/
result = ISC_R_SUCCESS;
insert_new_value:
if (protocol->reading_message_values)
result = omapi_object_set((omapi_object_t *)
protocol->message,
protocol->name,
protocol->value);
else {
if (protocol->message->object == NULL) {
/*
* Create a generic object to receive the
* values of the object in the incoming
* message.
*/
result = omapi_object_create(&protocol->
message->object,
NULL, 0);
if (result != ISC_R_SUCCESS)
break;
}
result = omapi_object_set((omapi_object_t *)
protocol->message->object,
protocol->name,
protocol->value);
}
if (result != ISC_R_SUCCESS)
break;
omapi_string_dereference(&protocol->name);
omapi_data_dereference(&protocol->value);
goto need_name_length;
signature_wait:
case omapi_protocol_signature_wait:
result = omapi_data_create(&protocol->message->authenticator,
omapi_datatype_data,
protocol->message->authlen);
if (result != ISC_R_SUCCESS)
return (result);
/*
* Turn off the dst_verify updating while the signature
* bytes are copied; they are not part of what was signed.
*/
protocol->dst_update = ISC_FALSE;
connection_copyout(protocol->message->authenticator->
u.buffer.value,
connection,
protocol->message->authlen);
protocol->signature_in.base =
protocol->message->authenticator->u.buffer.value;
protocol->signature_in.length = protocol->message->authlen;
/* XXXTL now do something to verify the signature. */
/* FALLTHROUGH */
message_done:
/*
* Hail, hail, the gang's all here! The whole message
* has been read in, so process it. Even if an error
* is returned, a bit of cleanup has to be done, but
* it can't muck with the result assigned here.
*/
result = message_process((omapi_object_t *)protocol->message,
(omapi_object_t *)protocol);
/* XXXTL unbind the authenticator. */
/*
* Free the message object.
*/
OBJECT_DEREF(&protocol->message);
/*
* The next thing the protocol reads will be a new message.
*/
protocol->state = omapi_protocol_header_wait;
/*
* Now, if message_process had indicated an error, let it be
* returned from here.
*/
if (result != ISC_R_SUCCESS)
break;
/*
* The next recv will be queued from send_done. On the
* server, this will be after it has sent its reply to the
* just-processed message by using omapi_message_send.
* On the client it will happen after it sends its
* next message with omapi_message_send.
*
* The OMAPI_R_NOTYET return value tells protocol_signalhandler
* that to return ISC_R_SUCCESS back to recv_done.
*/
result = OMAPI_R_NOTYET;
break;
default:
UNEXPECTED_ERROR(__FILE__, __LINE__, "unknown state in "
"omapi_protocol_signal_handler: %d",
protocol->state);
result = ISC_R_UNEXPECTED;
break;
}
return (result);
}
static isc_result_t
protocol_signalhandler(omapi_object_t *h, const char *name, va_list ap) {
isc_result_t result;
omapi_protocol_t *p;
omapi_connection_t *c;
REQUIRE(h != NULL && h->type == omapi_type_protocol);
p = (omapi_protocol_t *)h;
c = (omapi_connection_t *)p->outer;
/*
* Not a signal we recognize?
*/
if (strcmp(name, "ready") != 0)
return (omapi_object_passsignal(h, name, ap));
INSIST(p->outer != NULL && p->outer->type == omapi_type_connection);
do {
result = dispatch_messages(p, c);
} while (result == ISC_R_SUCCESS);
/*
* Getting "not yet" means more data is needed before another message
* can be processed.
*/
if (result == OMAPI_R_NOTYET)
result = ISC_R_SUCCESS;
return (result);
}
static isc_result_t
protocol_setvalue(omapi_object_t *h, omapi_string_t *name, omapi_data_t *value)
{
omapi_protocol_t *p;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(h != NULL && h->type == omapi_type_protocol);
p = (omapi_protocol_t *)h;
if (omapi_string_strcmp(name, "auth-name") == 0) {
p->authname = omapi_data_strdup(omapi_mctx, value);
if (p->authname == NULL)
return (ISC_R_NOMEMORY);
} else if (omapi_string_strcmp(name, "auth-algorithm") == 0) {
p->algorithm = omapi_data_getint(value);
if (p->algorithm == 0)
/*
* XXXDCL better error?
*/
return (DST_R_UNSUPPORTEDALG);
} else
return (omapi_object_passsetvalue(h, name, value));
/*
* XXXDCL if either auth-name or auth-algorithm is not in the incoming
* message, then the client will not get a meaningful error message
* in reply. this is bad.
*
* ... it is a general problem in the current omapi design ...
*/
if (p->authname != NULL && p->algorithm != 0) {
unsigned int sigsize;
/*
* Verifying the key through a callback is (currently) only
* done by the server.
* XXXDCL the client should have some way of checking whether
* what is being set is what it asked for.
*/
if (p->verify_key != NULL &&
!p->verify_key(p->authname, p->algorithm,
p->verify_key_arg))
return (ISC_R_NOPERM);
if (p->key != NULL)
dst_key_free(&p->key);
result = auth_makekey(p->authname, p->algorithm, &p->key);
if (result == ISC_R_SUCCESS)
result = dst_key_sigsize(p->key, &sigsize);
if (result == ISC_R_SUCCESS)
result = isc_buffer_allocate(omapi_mctx,
&p->signature_out,
sigsize);
if (result != ISC_R_SUCCESS) {
if (p->key != NULL)
dst_key_free(&p->key);
isc_mem_put(omapi_mctx, p->authname,
strlen(p->authname) + 1);
p->authname = NULL;
p->algorithm = 0;
p->key = NULL;
}
}
return (result);
}
static isc_result_t
protocol_getvalue(omapi_object_t *h, omapi_string_t *name,
omapi_value_t **value)
{
REQUIRE(h != NULL && h->type == omapi_type_protocol);
return (omapi_object_passgetvalue(h, name, value));
}
static void
protocol_destroy(omapi_object_t *h) {
omapi_protocol_t *p;
REQUIRE(h != NULL && h->type == omapi_type_protocol);
p = (omapi_protocol_t *)h;
if (p->message != NULL)
OBJECT_DEREF(&p->message);
if (p->authinfo != NULL)
OBJECT_DEREF(&p->authinfo);
if (p->authname != NULL) {
isc_mem_put(omapi_mctx, p->authname, strlen(p->authname) + 1);
p->authname = NULL;
}
if (p->signature_out != NULL) {
isc_buffer_free(&p->signature_out);
p->signature_out = NULL;
}
if (p->key != NULL) {
dst_key_free(&p->key);
p->key = NULL;
}
}
static isc_result_t
protocol_stuffvalues(omapi_object_t *connection, omapi_object_t *h) {
REQUIRE(h != NULL && h->type == omapi_type_protocol);
return (omapi_object_passstuffvalues(connection, h));
}
isc_result_t
protocol_init(void) {
return (omapi_object_register(&omapi_type_protocol, "protocol",
protocol_setvalue,
protocol_getvalue,
protocol_destroy,
protocol_signalhandler,
protocol_stuffvalues,
NULL, NULL, NULL));
}