/* * Copyright (C) 1996, 1997, 1998, 1999, 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. */ /* * Subroutines for dealing with message objects. */ #include #include #include #include #include #include #include omapi_message_t *registered_messages; isc_result_t omapi_message_create(omapi_object_t **o) { omapi_message_t *message = NULL; omapi_object_t *g; isc_result_t result; result = omapi_object_create((omapi_object_t **)&message, omapi_type_message, sizeof(*message)); if (result != ISC_R_SUCCESS) return (result); g = NULL; result = omapi_object_create(&g, NULL, 0); if (result != ISC_R_SUCCESS) { OBJECT_DEREF(&message); return (result); } OBJECT_REF(&message->inner, g); OBJECT_REF(&g->outer, message); OBJECT_REF(o, message); OBJECT_DEREF(&message); OBJECT_DEREF(&g); return (result); } /* * XXXDCL Make register/unregister implicitly part of omapi_message_send? */ void omapi_message_register(omapi_object_t *h) { omapi_message_t *m; REQUIRE(h != NULL && h->type == omapi_type_message); m = (omapi_message_t *)h; /* * Already registered? */ REQUIRE(m->prev == NULL && m->next == NULL && registered_messages != m); if (registered_messages != NULL) { OBJECT_REF(&m->next, registered_messages); OBJECT_REF(®istered_messages->prev, m); OBJECT_DEREF(®istered_messages); } OBJECT_REF(®istered_messages, m); } void omapi_message_unregister(omapi_object_t *h) { omapi_message_t *m; omapi_message_t *n; REQUIRE(h != NULL && h->type == omapi_type_message); m = (omapi_message_t *)h; /* * Not registered? */ REQUIRE(m->prev != NULL || registered_messages == m); n = NULL; if (m->next != NULL) { OBJECT_REF(&n, m->next); OBJECT_DEREF(&m->next); } if (m->prev != NULL) { omapi_message_t *tmp = NULL; OBJECT_REF(&tmp, m->prev); OBJECT_DEREF(&m->prev); if (tmp->next != NULL) OBJECT_DEREF(&tmp->next); if (n != NULL) OBJECT_REF(&tmp->next, n); OBJECT_DEREF(&tmp); } else { OBJECT_DEREF(®istered_messages); if (n != NULL) OBJECT_REF(®istered_messages, n); } if (n != NULL) OBJECT_DEREF(&n); } isc_result_t omapi_message_send(omapi_object_t *message, omapi_object_t *protocol) { /* * For this function, at least, generic objects have fully spelled * names and special type objects have short names. * XXXDCL It would be good to be more consistent about this throughout * the omapi library code. */ omapi_protocol_t *p; omapi_connection_t *c; omapi_message_t *m; omapi_object_t *connection; unsigned int authlen = 0; isc_result_t result = ISC_R_SUCCESS; REQUIRE(message != NULL && message->type == omapi_type_message); /* * Allow the function to be called with an object that is managing * the client side. */ REQUIRE((protocol != NULL && protocol->type == omapi_type_protocol) || (protocol->outer != NULL && protocol->outer->type == omapi_type_protocol)); if (protocol->type != omapi_type_protocol) protocol = protocol->outer; p = (omapi_protocol_t *)protocol; connection = (omapi_object_t *)(protocol->outer); c = (omapi_connection_t *)connection; INSIST(connection != NULL && connection->type == omapi_type_connection); m = (omapi_message_t *)message; if (p->key != NULL) { p->dstctx = NULL; result = dst_context_create(p->key, omapi_mctx, &p->dstctx); if (result == ISC_R_SUCCESS) result = dst_key_sigsize(p->key, &authlen); p->dst_update = ISC_TRUE; } if (result == ISC_R_SUCCESS) /* XXXTL Write the ID of the authentication key we're using. */ result = omapi_connection_putuint32(connection, 0); if (result == ISC_R_SUCCESS) result = omapi_connection_putuint32(connection, authlen); if (result == ISC_R_SUCCESS) /* * Write the opcode. */ result = omapi_connection_putuint32(connection, m->op); if (result == ISC_R_SUCCESS) /* * Write the handle. If we've been given an explicit handle, * use that. Otherwise, use the handle of the object we're * sending. The caller is responsible for arranging for one of * these handles to be set (or not). */ result = omapi_connection_putuint32(connection, (m->h ? m->h : (m->object ? m->object->handle : 0))); if (result == ISC_R_SUCCESS) { /* * Set and write the transaction ID. */ m->id = p->next_xid++; result = omapi_connection_putuint32(connection, m->id); } if (result == ISC_R_SUCCESS) /* * Write the transaction ID of the message to which this is a * response. */ result = omapi_connection_putuint32(connection, m->rid); if (result == ISC_R_SUCCESS) /* * Stuff out the name/value pairs specific to this message. */ result = object_stuffvalues(connection, message); if (result == ISC_R_SUCCESS) /* * Write the zero-length name that terminates the list of * name/value pairs specific to the message. */ result = omapi_connection_putuint16(connection, 0); if (result == ISC_R_SUCCESS && m->object != NULL) /* * Stuff out all the published name/value pairs in the object * that's being sent in the message, if there is one. */ result = object_stuffvalues(connection, m->object); if (result == ISC_R_SUCCESS) /* * Write the zero-length name length value that terminates * the list of name/value pairs for the associated object. */ result = omapi_connection_putuint16(connection, 0); if (result == ISC_R_SUCCESS && p->key != NULL) { isc_region_t r; isc_buffer_clear(p->signature_out); result = dst_context_sign(p->dstctx, p->signature_out); dst_context_destroy(&p->dstctx); isc_buffer_region(p->signature_out, &r); p->dst_update = ISC_FALSE; if (result == ISC_R_SUCCESS) result = omapi_connection_putmem(connection, r.base, r.length); } /* * Prime the bytes_needed for the server's reply message. * There is no need to lock. In the server everything happens in * the socket thread so only one event function is running at a time, * and in the client, there should be no events outstanding which * would cause the socket thread to access this variable . */ if (result == ISC_R_SUCCESS) { INSIST(c->bytes_needed == 0); c->bytes_needed = p->header_size; result = connection_send(c); /* * 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. Unfortunately, it is not known whether * it was send_done or recv_done that ended the connection; * if the connection object were not destroyed, one way it * could be inferred is by seeing whether connection->out_bytes * is 0. * * XXXDCL "connection disconnected" */ if (result != ISC_R_SUCCESS) object_signal(message, "status", result, NULL); } else if (c->is_client) { /* * One of the calls to omapi_connection_put* or to * object_stuffvalues 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 recv_done; let the error propagate back up * the stack to there, and it will close the connection safely. * If the server tried to free the connection here, recv_done * wouldn't be able to distinguish the error from errors * coming out of parts of the library that did not destroy * the connection. * * The client needs the connection destroyed right here, * because control is about to return to the driving thread * and it is guaranteed that if omapi_message_send returns * an error for any reason, then the connection will be gone. * Otherwise the client would have the same problem described * for recv_done on the server -- it wouldn't be able to tell * whether the error freed the connection. */ omapi_connection_disconnect(connection, OMAPI_FORCE_DISCONNECT); /* * The client also needs to be notified the message * never got sent. * * XXXDCL "message not sent; connection disconnected" */ object_signal(message, "status", result, NULL); } return (result); } isc_result_t message_process(omapi_object_t *mo, omapi_object_t *po) { omapi_message_t *message, *m; omapi_object_t *object = NULL; omapi_objecttype_t *type = NULL; omapi_protocol_t *protocol; omapi_connection_t *connection; omapi_value_t *tv = NULL; unsigned long create, update, exclusive; isc_result_t result, waitstatus; REQUIRE(mo != NULL && mo->type == omapi_type_message); REQUIRE(po != NULL); message = (omapi_message_t *)mo; protocol = (omapi_protocol_t *)po; INSIST(po->outer != NULL && po->outer->type == omapi_type_connection); /* * Note that the checking of connection->is_client throughout this * function pretty much means that peer-to-peer transactions can't * happen over a single connection. It is not clear, yet, whether that * is such a bad thing, but the original design document didn't * specify that particular operations were only valid on the client * or on the server. */ connection = (omapi_connection_t *)po->outer; if (message->rid != 0) { for (m = registered_messages; m != NULL; m = m->next) if (m->id == message->rid) break; /* * If we don't have a real message corresponding to * the message ID to which this message claims it is a * response, something's fishy. */ if (m == NULL) return (ISC_R_NOTFOUND); } else m = NULL; if (protocol->key != NULL) { if (protocol->verify_result == ISC_R_SUCCESS) { protocol->verify_result = dst_context_verify(protocol->dstctx, &protocol->signature_in); dst_context_destroy(&protocol->dstctx); } if (protocol->verify_result != ISC_R_SUCCESS) { if (connection->is_client) { INSIST(m != NULL); result = omapi_object_setstring(mo, "message", "failed to verify signature"); if (result == ISC_R_SUCCESS) (void)omapi_object_getvalue(mo, "message", &tv); object_signal((omapi_object_t *)m, "status", protocol->verify_result, tv); if (tv != NULL) omapi_value_dereference(&tv); /* * This keeps the connection from being blown * away, although it seems fairly reasonable * to force a disconnect. */ return (ISC_R_SUCCESS); } else /* * XXXDCL Should the key be stricken? * The curious thing about the way this * is currently set up is that the status * message won't verify on the client if * the secret was wrong ... so rather than * getting processed in OMAPI_OP_STATUS * below, it will be handled by this ``if'' * statement on the client. */ return (send_status(po, protocol->verify_result, message->id, "failed to verify " "signature")); } } switch (message->op) { case OMAPI_OP_OPEN: if (connection->is_client) return (ISC_R_UNEXPECTED); if (m != NULL) { return (send_status(po, OMAPI_R_INVALIDARG, message->id, "OPEN can't be a response")); } /* * Get the type of the requested object, if one was * specified. * * In this and subsequent calls to omapi_object_getvalue, * an error could be returned, typically ISC_R_NOMEMORY. * send_status *might* fail if the problem is being out * of memory ... but it is worth a shot. */ result = omapi_object_getvalue(mo, "type", &tv); if (result == ISC_R_SUCCESS) { if (tv->value->type == omapi_datatype_data || tv->value->type == omapi_datatype_string) type = object_findtype(tv); omapi_value_dereference(&tv); } else if (result == ISC_R_NOTFOUND) type = NULL; else return (send_status(po, result, message->id, isc_result_totext(result))); /* * Get the create flag. */ result = omapi_object_getvalue(mo, "create", &tv); if (result == ISC_R_SUCCESS) { create = omapi_value_getint(tv); omapi_value_dereference(&tv); } else if (result == ISC_R_NOTFOUND) create = 0; else return (send_status(po, result, message->id, isc_result_totext(result))); /* * Get the update flag. */ result = omapi_object_getvalue(mo, "update", &tv); if (result == ISC_R_SUCCESS) { update = omapi_value_getint(tv); omapi_value_dereference(&tv); } else if (result == ISC_R_NOTFOUND) update = 0; else return (send_status(po, result, message->id, isc_result_totext(result))); /* * Get the exclusive flag. */ result = omapi_object_getvalue(mo, "exclusive", &tv); if (result == ISC_R_SUCCESS) { exclusive = omapi_value_getint(tv); omapi_value_dereference(&tv); } else if (result == ISC_R_NOTFOUND) exclusive = 0; else return (send_status(po, result, message->id, isc_result_totext(result))); /* * All messages except for the first attempt to set * the dst key used by the protocol must be signed. */ if (type != omapi_type_protocol && protocol->key == NULL) return (send_status(po, ISC_R_NOPERM, message->id, "unauthorized access")); /* * If we weren't given a type, look the object up with * the handle. */ if (type == NULL) { if (create != 0) return (send_status(po, OMAPI_R_INVALIDARG, message->id, "type required on create")); goto refresh; } if (message->object == NULL) return (send_status(po, ISC_R_NOTFOUND, message->id, "no lookup key specified")); /* * This is pretty hackish, a special case for an attempt * to open the protocol object. It was done because * under the current design of OMAPI, there just isn't * a good way to set the authentication values. The * connection object and protocol object are the only * things that hold state on the server throughout the life * of a particular connection, and the original design * for lookup methods does not provide a way to identify * the current protocol or connection object. * * To minimize the hackishness, at least the rest of * the manipulation of the protocol object is done through * the normal object interfaces, rather than having a * a special block do the work directly. Small consolation. */ if (type == omapi_type_protocol) { OBJECT_REF(&object, po); result = ISC_R_SUCCESS; } else result = object_methodlookup(type, &object, message->object); if (result == ISC_R_NOTIMPLEMENTED) return (send_status(po, result, message->id, "unsearchable object type")); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND && result != OMAPI_R_NOKEYS) return (send_status(po, result, message->id, "object lookup failed")); /* * If we didn't find the object and we aren't supposed to * create it, return an error. */ if (result == ISC_R_NOTFOUND && create == 0) { return (send_status(po, ISC_R_NOTFOUND, message->id, "no object matches specification")); } /* * If we found an object, we're supposed to be creating an * object, and we're not supposed to have found an object, * return an error. */ if (result == ISC_R_SUCCESS && create != 0 && exclusive != 0) { OBJECT_DEREF(&object); return (send_status(po, ISC_R_EXISTS, message->id, "specified object already exists")); } /* * If we're creating the object, do it now. */ if (object == NULL) { result = object_methodcreate(type, &object); if (result != ISC_R_SUCCESS) return (send_status(po, result, message->id, "can't create new object")); } /* * If we're updating it, do so now. */ if (create != 0 || update != 0) { result = object_update(object, message->object, message->h); if (result != ISC_R_SUCCESS) { OBJECT_DEREF(&object); return (send_status(po, result, message->id, "can't update object")); } } /* * Now send the new contents of the object back in response. */ goto send; case OMAPI_OP_REFRESH: if (connection->is_client) return (ISC_R_UNEXPECTED); if (protocol->key == NULL) return (send_status(po, ISC_R_NOPERM, message->id, "unauthorized access")); refresh: result = handle_lookup(&object, message->h); if (result != ISC_R_SUCCESS) return (send_status(po, result, message->id, "no matching handle")); send: result = send_update(po, message->id, object); OBJECT_DEREF(&object); return (result); case OMAPI_OP_UPDATE: if (! connection->is_client) return (send_status(po, OMAPI_R_INVALIDARG, message->id, "OMAPI_OP_UPDATE is not a " "valid server operation")); if (m->object != NULL) OBJECT_REF(&object, m->object); else { result = handle_lookup(&object, message->h); if (result != ISC_R_SUCCESS) return (send_status(po, result, message->id, "no matching handle")); } if (message->object != NULL) result = object_update(object, message->object, message->h); else result = ISC_R_SUCCESS; OBJECT_DEREF(&object); if (result != ISC_R_SUCCESS) { if (message->rid == 0) return (send_status(po, result, message->id, "can't update object")); if (m != NULL) object_signal((omapi_object_t *)m, "status", result, NULL); return (ISC_R_SUCCESS); } if (message->rid == 0) result = send_status(po, ISC_R_SUCCESS, message->id, NULL); if (m != NULL) object_signal((omapi_object_t *)m, "status", ISC_R_SUCCESS, NULL); return (result); case OMAPI_OP_NOTIFY: return (send_status(po, ISC_R_NOTIMPLEMENTED, message->id, "notify not implemented yet")); case OMAPI_OP_STATUS: if (! connection->is_client) return (send_status(po, OMAPI_R_INVALIDARG, message->id, "OMAPI_OP_STATUS is not a " "valid server operation")); /* * The return status of a request. */ if (m == NULL) return (ISC_R_UNEXPECTED); /* * Get the wait status. */ result = omapi_object_getvalue(mo, "result", &tv); if (result == ISC_R_SUCCESS) { waitstatus = omapi_value_getint(tv); omapi_value_dereference(&tv); } else waitstatus = ISC_R_UNEXPECTED; result = omapi_object_getvalue(mo, "message", &tv); object_signal((omapi_object_t *)m, "status", waitstatus, tv); if (result == ISC_R_SUCCESS) omapi_value_dereference(&tv); /* * Even if the two omapi_object_getvalue calls in this * section returned errors, the operation is considered * successful. XXXDCL (should it be?) */ return (ISC_R_SUCCESS); case OMAPI_OP_DELETE: if (connection->is_client) return (ISC_R_UNEXPECTED); if (protocol->key == NULL) return (send_status(po, ISC_R_NOPERM, message->id, "unauthorized delete")); result = handle_lookup(&object, message->h); if (result != ISC_R_SUCCESS) return (send_status(po, result, message->id, "no matching handle")); result = object_methodremove(object->type, object); if (result == ISC_R_NOTIMPLEMENTED) return (send_status(po, ISC_R_NOTIMPLEMENTED, message->id, "no remove method for object")); OBJECT_DEREF(&object); return (send_status(po, result, message->id, NULL)); } return (ISC_R_NOTIMPLEMENTED); } static isc_result_t message_setvalue(omapi_object_t *h, omapi_string_t *name, omapi_data_t *value) { omapi_message_t *m; REQUIRE(h != NULL && h->type == omapi_type_message); m = (omapi_message_t *)h; /* * Can't set authlen. */ /* * Can set authenticator, but the value must be typed data. * XXXDCL (no longer meaningful) */ if (omapi_string_strcmp(name, "authenticator") == 0) { if (m->authenticator != NULL) omapi_data_dereference(&m->authenticator); omapi_data_reference(&m->authenticator, value); return (ISC_R_SUCCESS); } else if (omapi_string_strcmp(name, "object") == 0) { INSIST(value != NULL && value->type == omapi_datatype_object); if (m->object != NULL) OBJECT_DEREF(&m->object); OBJECT_REF(&m->object, value->u.object); return (ISC_R_SUCCESS); } else if (omapi_string_strcmp(name, "notify-object") == 0) { INSIST(value != NULL && value->type == omapi_datatype_object); if (m->notify_object != NULL) OBJECT_DEREF(&m->notify_object); OBJECT_REF(&m->notify_object, value->u.object); return (ISC_R_SUCCESS); /* * Can set authid, but it has to be an integer. */ } else if (omapi_string_strcmp(name, "authid") == 0) { INSIST(value != NULL && value->type == omapi_datatype_int); m->authid = value->u.integer; return (ISC_R_SUCCESS); /* * Can set op, but it has to be an integer. */ } else if (omapi_string_strcmp(name, "op") == 0) { INSIST(value != NULL && value->type == omapi_datatype_int); m->op = value->u.integer; return (ISC_R_SUCCESS); /* * Handle also has to be an integer. */ } else if (omapi_string_strcmp(name, "handle") == 0) { INSIST(value != NULL && value->type == omapi_datatype_int); m->h = value->u.integer; return (ISC_R_SUCCESS); /* * Transaction ID has to be an integer. */ } else if (omapi_string_strcmp(name, "id") == 0) { INSIST(value != NULL && value->type == omapi_datatype_int); m->id = value->u.integer; return (ISC_R_SUCCESS); /* * Remote transaction ID has to be an integer. */ } else if (omapi_string_strcmp(name, "rid") == 0) { INSIST(value != NULL && value->type == omapi_datatype_int); m->rid = value->u.integer; return (ISC_R_SUCCESS); } /* * Try to find some inner object that can take the value. */ return (omapi_object_passsetvalue(h, name, value)); } static isc_result_t message_getvalue(omapi_object_t *h, omapi_string_t *name, omapi_value_t **value) { omapi_message_t *m; REQUIRE(h != NULL && h->type == omapi_type_message); m = (omapi_message_t *)h; /* * Look for values that are in the message data structure. */ if (omapi_string_strcmp(name, "authenticator") == 0) { if (m->authenticator != NULL) return (omapi_value_storedata(value, name, m->authenticator)); else return (ISC_R_NOTFOUND); } else if (omapi_string_strcmp(name, "authlen") == 0) return (omapi_value_storeint(value, name, (int)m->authlen)); else if (omapi_string_strcmp(name, "authid") == 0) return (omapi_value_storeint(value, name, (int)m->authid)); else if (omapi_string_strcmp(name, "op") == 0) return (omapi_value_storeint(value, name, (int)m->op)); else if (omapi_string_strcmp(name, "handle") == 0) return (omapi_value_storeint(value, name, (int)m->h)); else if (omapi_string_strcmp(name, "id") == 0) return (omapi_value_storeint(value, name, (int)m->id)); else if (omapi_string_strcmp(name, "rid") == 0) return (omapi_value_storeint(value, name, (int)m->rid)); /* * See if there's an inner object that has the value. */ return (omapi_object_passgetvalue(h, name, value)); } static void message_destroy(omapi_object_t *handle) { omapi_message_t *message; REQUIRE(handle != NULL && handle->type == omapi_type_message); message = (omapi_message_t *)handle; if (message->authenticator != NULL) omapi_data_dereference(&message->authenticator); INSIST(message->prev == NULL && message->next == NULL && registered_messages != message); if (message->object != NULL) OBJECT_DEREF(&message->object); if (message->notify_object != NULL) OBJECT_DEREF(&message->notify_object); } static isc_result_t message_signalhandler(omapi_object_t *handle, const char *name, va_list ap) { omapi_message_t *message; REQUIRE(handle != NULL && handle->type == omapi_type_message); message = (omapi_message_t *)handle; /* * XXXDCL It would make the client side a bit cleaner if when "status" * is signalled, it sets both "waitresult" and "waittext" (or some * such) in the OMAPI_OBJECT_PREAMBLE of both the message and * the notify_object or regular object. */ if (strcmp(name, "status") == 0 && (message->object != NULL || message->notify_object != NULL)) { if (message->notify_object != NULL) return (object_vsignal(message->notify_object, name, ap)); else return (object_vsignal(message->object, name, ap)); } return (omapi_object_passsignal(handle, name, ap)); } /* * Write all the published values associated with the object through the * specified connection. */ static isc_result_t message_stuffvalues(omapi_object_t *connection, omapi_object_t *message) { REQUIRE(message != NULL && message->type == omapi_type_message); return (omapi_object_passstuffvalues(connection, message)); } isc_result_t message_init(void) { return (omapi_object_register(&omapi_type_message, "message", message_setvalue, message_getvalue, message_destroy, message_signalhandler, message_stuffvalues, NULL, NULL, NULL)); }