/* * Copyright (C) 1996, 1997, 1998, 1999 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. */ /* * Functions supporting the object management protocol. */ #include /* NULL */ #include /* random */ #include /* memset */ #include #include #include /* * 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, int port, omapi_object_t *authinfo) { isc_result_t result; omapi_protocol_t *obj; obj = NULL; result = omapi_object_create((omapi_object_t **)&obj, omapi_type_protocol, sizeof(*obj)); if (result != ISC_R_SUCCESS) return (result); result = connect_toserver((omapi_object_t *)obj, server_name, port); if (result != ISC_R_SUCCESS) { OBJECT_DEREF(&obj); return (result); } OBJECT_REF(&h->outer, obj); OBJECT_REF(&obj->inner, h); /* * Send the introductory message. */ result = send_intro((omapi_object_t *)obj, OMAPI_PROTOCOL_VERSION); if (result != ISC_R_SUCCESS) { OBJECT_DEREF(&obj); return (result); } /* * Wait 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.) * XXXDCL of course, the above also implies that this function * will return something *useful* if the task thread blows away the * connection, but that is not currently true. I need to make it so. */ result = connection_wait(obj->outer, NULL); if (result != ISC_R_SUCCESS) { OBJECT_DEREF(&obj); return (result); } if (authinfo != NULL) OBJECT_REF(&obj->authinfo, authinfo); OBJECT_DEREF(&obj); return (ISC_R_SUCCESS); } 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; INSIST(protocol != NULL && 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); p = (omapi_protocol_t *)h; connection = (omapi_connection_t *)h->outer; if (h->outer == NULL || h->outer->type != omapi_type_connection) return (OMAPI_R_NOTCONNECTED); result = omapi_connection_putuint32((omapi_object_t *)connection, ver); if (result != ISC_R_SUCCESS) return (result); result = omapi_connection_putuint32((omapi_object_t *)connection, sizeof(omapi_protocolheader_t)); if (result != ISC_R_SUCCESS) return (result); /* * Require the other end to send an intro - this kicks off the * protocol input state machine. */ p->state = omapi_protocol_intro_wait; result = connection_require(connection, 8); if (result != ISC_R_SUCCESS && result != OMAPI_R_NOTYET) return (result); /* * Make up an initial transaction ID for this connection. * XXXDCL better generator than random()? */ p->next_xid = random(); connection_send(connection); return (ISC_R_SUCCESS); } /* * Set up a listener for the omapi protocol. */ isc_result_t omapi_protocol_listen(omapi_object_t *manager, int port, int max) { return (omapi_listener_listen((omapi_object_t *)manager, port, max)); } 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); 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) { OBJECT_DEREF(&message); return (result); } return (omapi_message_send(message, po)); } 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); 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) { OBJECT_DEREF(&message); return (result); } return (omapi_message_send(message, po)); } 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; /* * Signal connection_wait() to wake up. * Only do this for the client side, which needs to be * blocked otherwise it might try to use a connection * which is destroyed during the processing that is * driven by the socket thread. * * XXXDCL this code is duplicated below */ if (connection->is_client) { RUNTIME_CHECK(isc_mutex_lock(&connection->mutex) == ISC_R_SUCCESS); INSIST(connection->messages_expected > 0); connection->messages_expected--; RUNTIME_CHECK(isc_condition_signal(&connection->waiter) == ISC_R_SUCCESS); /* * If the driving program has already called * omapi_message_send and the lock * was acquired in that function, then since * messages_expected would have been >= 2 at * the critical test, the omapi_connection_require * would not have been done yet, and will need * to be. Since messages_expected was decremented, * drop through to the connection_require only if * messages_expected is >= 1. * * The "not yet" response tells the calling * signal handler that there is not yet another * message to be processed. */ if (connection->messages_expected == 0) { RUNTIME_CHECK(isc_mutex_unlock(&connection-> mutex) == ISC_R_SUCCESS); result = OMAPI_R_NOTYET; break; } /* * Proceed to the omapi_connection_require * for the first "real" message's header. */ RUNTIME_CHECK(isc_mutex_unlock(&connection->mutex) == ISC_R_SUCCESS); } /* * 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; /* * Fetch the header values. */ 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->handle); 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); 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); connection_copyout(protocol->message->authenticator-> u.buffer.value, connection, 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; /* * Signal connection_wait() to wake up. * XXXDCL code is duplicated from above. */ if (connection->is_client) { RUNTIME_CHECK(isc_mutex_lock(&connection->mutex) == ISC_R_SUCCESS); INSIST(connection->messages_expected > 0); connection->messages_expected--; RUNTIME_CHECK(isc_condition_signal(&connection->waiter) == ISC_R_SUCCESS); /* * If there are no more messages expected, exit * the signal handler. */ if (connection->messages_expected == 0) { RUNTIME_CHECK(isc_mutex_unlock(&connection-> mutex) == ISC_R_SUCCESS); result = OMAPI_R_NOTYET; break; } RUNTIME_CHECK(isc_mutex_unlock(&connection->mutex) == ISC_R_SUCCESS); } /* * Now, if message_process had indicated an error, let it be * returned from here. */ if (result != ISC_R_SUCCESS) break; /* * Queue up the next recv; if this next header is already * received, ISC_R_SUCCESS will be returned to * protocol_signalhandler, which will cause it to immediately * call this function again to process it. */ result = connection_require(connection, protocol->header_size); break; default: UNEXPECTED_ERROR(__FILE__, __LINE__, "unknown state in " "omapi_protocol_signal_handler: %d\n", 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_object_t *connection; 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); connection = p->outer; 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) { REQUIRE(h != NULL && h->type == omapi_type_protocol); return (omapi_object_passsetvalue(h, name, value)); } 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); } 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)); }