mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 18:19:42 +00:00
507 lines
13 KiB
C
507 lines
13 KiB
C
/*
|
|
* 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: listener.c,v 1.31 2000/10/20 13:29:37 marka Exp $ */
|
|
|
|
/*
|
|
* Subroutines that support the generic listener object.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h> /* NULL and abort() */
|
|
|
|
#include <isc/buffer.h>
|
|
#include <isc/bufferlist.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/string.h>
|
|
#include <isc/task.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <omapi/private.h>
|
|
|
|
typedef struct omapi_listener_object {
|
|
OMAPI_OBJECT_PREAMBLE;
|
|
isc_mutex_t mutex;
|
|
isc_task_t *task;
|
|
isc_socket_t *socket; /* Listening socket. */
|
|
isc_boolean_t (*verify_connection)(isc_sockaddr_t *sockaddr,
|
|
void *connect_arg);
|
|
isc_boolean_t (*verify_key)(const char *name, unsigned int algorithm,
|
|
void *key_arg);
|
|
void *callback_arg;
|
|
/*
|
|
* Locked by mutex.
|
|
*/
|
|
isc_boolean_t listening;
|
|
ISC_LIST(omapi_connection_t) connections;
|
|
} omapi_listener_t;
|
|
|
|
static void
|
|
free_listener(omapi_listener_t *listener) {
|
|
/*
|
|
* Break the link between the listener object and its parent
|
|
* (usually a generic object); this is done so the server's
|
|
* reference to its managing object does not prevent the
|
|
* listener object from being destroyed.
|
|
*/
|
|
OBJECT_DEREF(&listener->inner->outer);
|
|
OBJECT_DEREF(&listener->inner);
|
|
|
|
/*
|
|
* The listener object can now be freed.
|
|
*/
|
|
OBJECT_DEREF(&listener);
|
|
}
|
|
|
|
/*
|
|
* Reader callback for a listener object. Accept an incoming connection.
|
|
*/
|
|
static void
|
|
listener_accept(isc_task_t *task, isc_event_t *event) {
|
|
isc_result_t result;
|
|
isc_buffer_t *ibuffer = NULL;
|
|
isc_buffer_t *obuffer = NULL;
|
|
isc_task_t *connection_task = NULL;
|
|
isc_socket_t *sock;
|
|
isc_sockaddr_t sockaddr;
|
|
omapi_connection_t *connection = NULL;
|
|
omapi_protocol_t *protocol = NULL;
|
|
omapi_listener_t *listener;
|
|
|
|
/*
|
|
* XXXDCL audit error handling
|
|
*/
|
|
|
|
result = ((isc_socket_newconnev_t *)event)->result;
|
|
sock = ((isc_socket_newconnev_t *)event)->newsocket;
|
|
listener = (omapi_listener_t *)event->ev_arg;
|
|
|
|
/*
|
|
* No more need for the event, once all the desired data has been
|
|
* used from it.
|
|
*/
|
|
isc_event_free(&event);
|
|
|
|
if (result == ISC_R_CANCELED) {
|
|
/*
|
|
* omapi_listener_shutdown was called.
|
|
*/
|
|
LOCK(&listener->mutex);
|
|
|
|
listener->listening = ISC_FALSE;
|
|
|
|
if (ISC_LIST_HEAD(listener->connections) == NULL) {
|
|
UNLOCK(&listener->mutex);
|
|
free_listener(listener);
|
|
|
|
} else {
|
|
/*
|
|
* All connections this listener was responsible for
|
|
* must be removed.
|
|
*
|
|
* XXXDCL
|
|
* Since it is possible that this shutdown was
|
|
* triggered by one of the clients, it would be nice
|
|
* to give it a little time to exit, as well as allow
|
|
* any other connections to finish up cleanly.
|
|
* Unfortunately, since this could be called in the
|
|
* task/event thread of a program (as it is in named),
|
|
* no other events can be delivered while this routine
|
|
* blocks, so a loop to use isc_condition_waituntil
|
|
* until all of the connections are gone is pointless.
|
|
*/
|
|
for (connection = ISC_LIST_HEAD(listener->connections);
|
|
connection != NULL;
|
|
connection = ISC_LIST_NEXT(connection, link))
|
|
omapi_connection_disconnect((omapi_object_t *)
|
|
connection,
|
|
OMAPI_FORCE_DISCONNECT);
|
|
UNLOCK(&listener->mutex);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set up another accept task for the socket.
|
|
*/
|
|
isc_socket_accept(listener->socket, task, listener_accept, listener);
|
|
|
|
/*
|
|
* Check for the validity of new connection event.
|
|
* If the result is not ISC_R_SUCCESS, what can really
|
|
* be done about it other than just flunking out of here?
|
|
*/
|
|
if (result != ISC_R_SUCCESS)
|
|
return;
|
|
|
|
/*
|
|
* Is the connection from a valid host?
|
|
*/
|
|
result = isc_socket_getpeername(sock, &sockaddr);
|
|
if (result != ISC_R_SUCCESS ||
|
|
!listener->verify_connection(&sockaddr, listener->callback_arg)) {
|
|
/*
|
|
* Permission denied. Close the connection.
|
|
* XXXDCL isc_log_write an error.
|
|
*/
|
|
isc_socket_detach(&sock);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The new connection is good to go. Allocate the buffers for it and
|
|
* prepare its own task.
|
|
*/
|
|
if (isc_task_create(omapi_taskmgr, 0, &connection_task) !=
|
|
ISC_R_SUCCESS)
|
|
goto free_task;
|
|
|
|
ibuffer = NULL;
|
|
result = isc_buffer_allocate(omapi_mctx, &ibuffer, OMAPI_BUFFER_SIZE);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto free_ibuffer;
|
|
|
|
obuffer = NULL;
|
|
result = isc_buffer_allocate(omapi_mctx, &obuffer, OMAPI_BUFFER_SIZE);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto free_obuffer;
|
|
|
|
/*
|
|
* Create a new connection object.
|
|
*/
|
|
result = omapi_object_create((omapi_object_t **)&connection,
|
|
omapi_type_connection, sizeof(*connection));
|
|
if (result != ISC_R_SUCCESS)
|
|
goto free_obuffer;
|
|
|
|
connection->task = connection_task;
|
|
connection->state = omapi_connection_connected;
|
|
connection->socket = sock;
|
|
connection->is_client = ISC_FALSE;
|
|
|
|
ISC_LIST_INIT(connection->input_buffers);
|
|
ISC_LIST_APPEND(connection->input_buffers, ibuffer, link);
|
|
ISC_LIST_INIT(connection->output_buffers);
|
|
ISC_LIST_APPEND(connection->output_buffers, obuffer, link);
|
|
ISC_LINK_INIT(connection, link);
|
|
|
|
/*
|
|
* Create a new protocol object to oversee the handling of this
|
|
* connection.
|
|
*/
|
|
protocol = NULL;
|
|
result = omapi_object_create((omapi_object_t **)&protocol,
|
|
omapi_type_protocol,
|
|
sizeof(omapi_protocol_t));
|
|
if (result != ISC_R_SUCCESS)
|
|
goto free_connection_object;
|
|
|
|
|
|
/*
|
|
* Hand off the key verification information to the protocol object.
|
|
*/
|
|
protocol->verify_key = listener->verify_key;
|
|
protocol->verify_key_arg = listener->callback_arg;
|
|
|
|
/*
|
|
* Tie the protocol object bidirectionally to the connection
|
|
* object, with the connection as the outer object.
|
|
*/
|
|
OBJECT_REF(&protocol->outer, connection);
|
|
OBJECT_REF(&connection->inner, protocol);
|
|
|
|
/*
|
|
* Lose the external reference to the protocol object so both the
|
|
* connection object and protocol object will be freed when the
|
|
* connection ends.
|
|
*/
|
|
OBJECT_DEREF(&protocol);
|
|
|
|
/*
|
|
* Add the connection to the list of connections known by the
|
|
* listener. This is an added reference to the connection
|
|
* object, but since there's no easy way to use omapi_object_reference
|
|
* with the ISC_LIST macros, that reference is just not counted.
|
|
*/
|
|
LOCK(&listener->mutex);
|
|
ISC_LIST_APPEND(listener->connections, connection, link);
|
|
UNLOCK(&listener->mutex);
|
|
|
|
/*
|
|
* Remember the listener that accepted the connection, so it
|
|
* can be told when the connection goes away.
|
|
*/
|
|
OBJECT_REF(&connection->listener, listener);
|
|
|
|
/*
|
|
* Send the introductory message. The return value does not
|
|
* matter; if send_intro failed, it already destroyed the connection.
|
|
*/
|
|
(void)send_intro(connection->inner, OMAPI_PROTOCOL_VERSION);
|
|
|
|
return;
|
|
|
|
free_connection_object:
|
|
/*
|
|
* Destroy the connection. This will free everything created
|
|
* in this function but the event, which was already freed.
|
|
*/
|
|
OBJECT_DEREF(&connection);
|
|
return;
|
|
|
|
/*
|
|
* Free resources that were being created for the connection object.
|
|
*/
|
|
free_obuffer:
|
|
isc_buffer_free(&obuffer);
|
|
free_ibuffer:
|
|
isc_buffer_free(&ibuffer);
|
|
free_task:
|
|
isc_task_destroy(&connection_task);
|
|
}
|
|
|
|
isc_result_t
|
|
omapi_listener_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)
|
|
{
|
|
isc_result_t result;
|
|
isc_task_t *task;
|
|
omapi_listener_t *listener;
|
|
|
|
REQUIRE(manager != NULL);
|
|
REQUIRE(addr != NULL && isc_sockaddr_getport(addr) != 0);
|
|
|
|
task = NULL;
|
|
result = isc_task_create(omapi_taskmgr, 0, &task);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
/*
|
|
* Create the listener object.
|
|
*/
|
|
listener = NULL;
|
|
result = omapi_object_create((omapi_object_t **)&listener,
|
|
omapi_type_listener, sizeof(*listener));
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_task_destroy(&task);
|
|
return (result);
|
|
}
|
|
|
|
listener->task = task;
|
|
|
|
RUNTIME_CHECK(isc_mutex_init(&listener->mutex) == ISC_R_SUCCESS);
|
|
ISC_LIST_INIT(listener->connections);
|
|
|
|
/*
|
|
* Create a socket on which to listen.
|
|
*/
|
|
listener->socket = NULL;
|
|
result = isc_socket_create(omapi_socketmgr, PF_INET,
|
|
isc_sockettype_tcp, &listener->socket);
|
|
|
|
if (result == ISC_R_SUCCESS)
|
|
result = isc_socket_bind(listener->socket, addr);
|
|
|
|
if (result == ISC_R_SUCCESS)
|
|
/*
|
|
* Now tell the kernel to listen for connections.
|
|
*/
|
|
result = isc_socket_listen(listener->socket, 0);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
/*
|
|
* Queue up the first accept event. The listener object
|
|
* will be passed to listener_accept() when it is called.
|
|
*/
|
|
listener->listening = ISC_TRUE;
|
|
result = isc_socket_accept(listener->socket, task,
|
|
listener_accept, listener);
|
|
}
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
/*
|
|
* Tie the listener object to the calling object.
|
|
*/
|
|
OBJECT_REF(&manager->outer, listener);
|
|
OBJECT_REF(&listener->inner, manager);
|
|
|
|
/*
|
|
* The destroy action is not set until here because it should
|
|
* only be called if the listener was successfully set up.
|
|
*/
|
|
listener->destroy_action = destroy_action;
|
|
listener->destroy_arg = arg;
|
|
listener->verify_connection = verify_connection;
|
|
listener->verify_key = verify_key;
|
|
listener->callback_arg = arg;
|
|
|
|
} else {
|
|
/*
|
|
* Failed to set up the listener.
|
|
*/
|
|
listener->listening = ISC_FALSE;
|
|
OBJECT_DEREF(&listener);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
omapi_listener_shutdown(omapi_object_t *listener) {
|
|
omapi_listener_t *l;
|
|
|
|
REQUIRE((listener != NULL && listener->type == omapi_type_listener) ||
|
|
(listener->outer != NULL &&
|
|
listener->outer->type == omapi_type_listener));
|
|
|
|
if (listener->type == omapi_type_listener)
|
|
l = (omapi_listener_t *)listener;
|
|
else
|
|
l = (omapi_listener_t *)listener->outer;
|
|
|
|
/*
|
|
* It is improper to call this function without having had a successful
|
|
* run of omapi_listener_listen.
|
|
*/
|
|
REQUIRE(l->socket != NULL && l->task != NULL);
|
|
|
|
/*
|
|
* Stop accepting connections.
|
|
*/
|
|
isc_socket_cancel(l->socket, NULL, ISC_SOCKCANCEL_ACCEPT);
|
|
}
|
|
|
|
static isc_result_t
|
|
listener_setvalue(omapi_object_t *listener, omapi_string_t *name,
|
|
omapi_data_t *value)
|
|
{
|
|
/*
|
|
* Nothing meaningful can be set in a listener object; just
|
|
* continue the call through the object chain.
|
|
*/
|
|
REQUIRE(listener != NULL && listener->type == omapi_type_listener);
|
|
|
|
return (omapi_object_passsetvalue(listener, name, value));
|
|
}
|
|
|
|
static isc_result_t
|
|
listener_getvalue(omapi_object_t *listener, omapi_string_t *name,
|
|
omapi_value_t **value)
|
|
{
|
|
/*
|
|
* Nothing meaningful can be fetched from a listener object; just
|
|
* continue the call through the object chain.
|
|
*/
|
|
REQUIRE(listener != NULL && listener->type == omapi_type_listener);
|
|
|
|
return (omapi_object_passgetvalue(listener, name, value));
|
|
}
|
|
|
|
static void
|
|
listener_destroy(omapi_object_t *listener) {
|
|
omapi_listener_t *l;
|
|
|
|
REQUIRE(listener != NULL && listener->type == omapi_type_listener);
|
|
|
|
l = (omapi_listener_t *)listener;
|
|
|
|
LOCK(&l->mutex);
|
|
INSIST(ISC_LIST_EMPTY(l->connections));
|
|
UNLOCK(&l->mutex);
|
|
|
|
DESTROYLOCK(&l->mutex);
|
|
|
|
if (l->task != NULL)
|
|
isc_task_destroy(&l->task);
|
|
|
|
if (l->socket != NULL)
|
|
isc_socket_detach(&l->socket);
|
|
}
|
|
|
|
static isc_result_t
|
|
listener_signalhandler(omapi_object_t *listener, const char *name, va_list ap)
|
|
{
|
|
omapi_connection_t *c;
|
|
omapi_listener_t *l;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(listener != NULL && listener->type == omapi_type_listener);
|
|
|
|
l = (omapi_listener_t *)listener;
|
|
|
|
/*
|
|
* free_connection() signals the listener when one of the connections
|
|
* it accepted has gone away.
|
|
*/
|
|
if (strcmp(name, "disconnect") == 0) {
|
|
c = va_arg(ap, omapi_connection_t *);
|
|
|
|
LOCK(&l->mutex);
|
|
ISC_LIST_UNLINK(l->connections, c, link);
|
|
|
|
if (! l->listening && ISC_LIST_HEAD(l->connections) == NULL) {
|
|
/*
|
|
* The listener has been shutdown and the last
|
|
* connection was received.
|
|
*/
|
|
UNLOCK(&l->mutex);
|
|
free_listener(l);
|
|
|
|
} else
|
|
UNLOCK(&l->mutex);
|
|
|
|
result = ISC_R_SUCCESS;
|
|
} else
|
|
result = omapi_object_passsignal(listener, name, ap);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Write all the published values associated with the object through the
|
|
* specified connection.
|
|
*/
|
|
static isc_result_t
|
|
listener_stuffvalues(omapi_object_t *connection, omapi_object_t *listener)
|
|
{
|
|
REQUIRE(listener != NULL && listener->type == omapi_type_listener);
|
|
|
|
return (omapi_object_passstuffvalues(connection, listener));
|
|
}
|
|
|
|
isc_result_t
|
|
listener_init(void) {
|
|
return (omapi_object_register(&omapi_type_listener, "listener",
|
|
listener_setvalue,
|
|
listener_getvalue,
|
|
listener_destroy,
|
|
listener_signalhandler,
|
|
listener_stuffvalues,
|
|
NULL, NULL, NULL));
|
|
}
|