/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * 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 https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

#pragma once

/*****
***** Module Info
*****/

/*! \file
 * \brief
 * This module defines two objects, ns_client_t and ns_clientmgr_t.
 *
 * An ns_client_t object handles incoming DNS requests from clients
 * on a given network interface.
 *
 * Each ns_client_t object can handle only one TCP connection or UDP
 * request at a time.  Therefore, several ns_client_t objects are
 * typically created to serve each network interface, e.g., one
 * for handling TCP requests and a few (one per CPU) for handling
 * UDP requests.
 *
 * Incoming requests are classified as queries, zone transfer
 * requests, update requests, notify requests, etc, and handed off
 * to the appropriate request handler.  When the request has been
 * fully handled (which can be much later), the ns_client_t must be
 * notified of this by calling one of the following functions
 * exactly once in the context of its task:
 * \code
 *   ns_client_send()	 (sending a non-error response)
 *   ns_client_sendraw() (sending a raw response)
 *   ns_client_error()	 (sending an error response)
 *   ns_client_drop() (sending no response, logging the reason)
 *\endcode
 * This will release any resources used by the request and
 * and allow the ns_client_t to listen for the next request.
 *
 * A ns_clientmgr_t manages a number of ns_client_t objects.
 * New ns_client_t objects are created by calling
 * ns_clientmgr_createclients(). They are destroyed by
 * destroying their manager.
 */

/***
 *** Imports
 ***/

#include <inttypes.h>
#include <stdbool.h>

#include <isc/atomic.h>
#include <isc/buffer.h>
#include <isc/magic.h>
#include <isc/netmgr.h>
#include <isc/quota.h>
#include <isc/stdtime.h>

#include <dns/db.h>
#include <dns/ecs.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include <dns/rdataclass.h>
#include <dns/rdatatype.h>
#include <dns/types.h>

#include <ns/query.h>
#include <ns/types.h>

/***
 *** Types
 ***/

#define NS_CLIENT_TCP_BUFFER_SIZE  65535
#define NS_CLIENT_SEND_BUFFER_SIZE 4096

/*!
 * Client object states.  Ordering is significant: higher-numbered
 * states are generally "more active", meaning that the client can
 * have more dynamically allocated data, outstanding events, etc.
 * In the list below, any such properties listed for state N
 * also apply to any state > N.
 */

typedef enum {
	NS_CLIENTSTATE_FREED = 0,
	/*%<
	 * The client object no longer exists.
	 */

	NS_CLIENTSTATE_INACTIVE = 1,
	/*%<
	 * The client object exists and has a task and timer.
	 * Its "query" struct and sendbuf are initialized.
	 * It has a message and OPT, both in the reset state.
	 */

	NS_CLIENTSTATE_READY = 2,
	/*%<
	 * The client object is either a TCP or a UDP one, and
	 * it is associated with a network interface.  It is on the
	 * client manager's list of active clients.
	 *
	 * If it is a TCP client object, it has a TCP listener socket
	 * and an outstanding TCP listen request.
	 *
	 * If it is a UDP client object, it has a UDP listener socket
	 * and an outstanding UDP receive request.
	 */

	NS_CLIENTSTATE_WORKING = 3,
	/*%<
	 * The client object has received a request and is working
	 * on it.  It has a view, and it may have any of a non-reset OPT,
	 * recursion quota, and an outstanding write request.
	 */

	NS_CLIENTSTATE_RECURSING = 4,
	/*%<
	 * The client object is recursing.  It will be on the
	 * 'recursing' list.
	 */

	NS_CLIENTSTATE_MAX = 5
	/*%<
	 * Sentinel value used to indicate "no state".
	 */
} ns_clientstate_t;

typedef ISC_LIST(ns_client_t) client_list_t;

/*% nameserver client manager structure */
struct ns_clientmgr {
	/* Unlocked. */
	unsigned int magic;

	isc_mem_t      *mctx;
	ns_server_t    *sctx;
	isc_taskmgr_t  *taskmgr;
	isc_timermgr_t *timermgr;
	isc_refcount_t	references;
	int		tid;

	/* Attached by clients, needed for e.g. recursion */
	isc_task_t *task;

	dns_aclenv_t *aclenv;

	/* Lock covers the recursing list */
	isc_mutex_t   reclock;
	client_list_t recursing; /*%< Recursing clients */

	uint8_t tcp_buffer[NS_CLIENT_TCP_BUFFER_SIZE];
};

/*% nameserver client structure */
struct ns_client {
	unsigned int	 magic;
	isc_mem_t	*mctx;
	int		 tid;
	bool		 allocated; /* Do we need to free it? */
	ns_server_t	*sctx;
	ns_clientmgr_t	*manager;
	ns_clientstate_t state;
	int		 nupdates;
	bool		 nodetach;
	bool		 shuttingdown;
	unsigned int	 attributes;
	isc_task_t	*task;
	dns_view_t	*view;
	dns_dispatch_t	*dispatch;
	isc_nmhandle_t	*handle;	/* Permanent pointer to handle */
	isc_nmhandle_t	*sendhandle;	/* Waiting for send callback */
	isc_nmhandle_t	*reqhandle;	/* Waiting for request callback
					   (query, update, notify) */
	isc_nmhandle_t *fetchhandle;	/* Waiting for recursive fetch */
	isc_nmhandle_t *prefetchhandle; /* Waiting for prefetch / rpzfetch */
	isc_nmhandle_t *updatehandle;	/* Waiting for update callback */
	unsigned char  *tcpbuf;
	size_t		tcpbuf_size;
	dns_message_t  *message;
	dns_rdataset_t *opt;
	dns_ednsopt_t  *ede;
	uint16_t	udpsize;
	uint16_t	extflags;
	int16_t		ednsversion; /* -1 noedns */
	uint16_t	additionaldepth;
	void (*cleanup)(ns_client_t *);
	ns_query_t    query;
	isc_time_t    requesttime;
	isc_stdtime_t now;
	isc_time_t    tnow;
	dns_name_t    signername; /*%< [T]SIG key name */
	dns_name_t   *signer;	  /*%< NULL if not valid sig */
	bool	      mortal;	  /*%< Die after handling request */
	isc_quota_t  *recursionquota;

	isc_sockaddr_t peeraddr;
	bool	       peeraddr_valid;
	isc_netaddr_t  destaddr;
	isc_sockaddr_t destsockaddr;

	dns_ecs_t ecs; /*%< EDNS client subnet sent by client */

	struct in6_pktinfo pktinfo;
	/*%
	 * Information about recent FORMERR response(s), for
	 * FORMERR loop avoidance.  This is separate for each
	 * client object rather than global only to avoid
	 * the need for locking.
	 */
	struct {
		isc_sockaddr_t	addr;
		isc_stdtime_t	time;
		dns_messageid_t id;
	} formerrcache;

	/*% Callback function to send a response when unit testing */
	void (*sendcb)(isc_buffer_t *buf);

	ISC_LINK(ns_client_t) rlink;
	unsigned char  cookie[8];
	uint32_t       expire;
	unsigned char *keytag;
	uint16_t       keytag_len;

	/*%
	 * Used to override the DNS response code in ns_client_error().
	 * If set to -1, the rcode is determined from the result code,
	 * but if set to any other value, the least significant 12
	 * bits will be used as the rcode in the response message.
	 */
	int32_t rcode_override;

	uint8_t sendbuf[NS_CLIENT_SEND_BUFFER_SIZE];
};

#define NS_CLIENT_MAGIC	   ISC_MAGIC('N', 'S', 'C', 'c')
#define NS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, NS_CLIENT_MAGIC)

#define NS_CLIENTATTR_TCP	 0x00001
#define NS_CLIENTATTR_RA	 0x00002 /*%< Client gets recursive service */
#define NS_CLIENTATTR_PKTINFO	 0x00004 /*%< pktinfo is valid */
#define NS_CLIENTATTR_MULTICAST	 0x00008 /*%< recv'd from multicast */
#define NS_CLIENTATTR_WANTDNSSEC 0x00010 /*%< include dnssec records */
#define NS_CLIENTATTR_WANTNSID	 0x00020 /*%< include nameserver ID */
/* Obsolete: NS_CLIENTATTR_FILTER_AAAA	0x00040 */
/* Obsolete: NS_CLIENTATTR_FILTER_AAAA_RC 0x00080 */
#define NS_CLIENTATTR_WANTAD	 0x00100 /*%< want AD in response if possible */
#define NS_CLIENTATTR_WANTCOOKIE 0x00200 /*%< return a COOKIE */
#define NS_CLIENTATTR_HAVECOOKIE 0x00400 /*%< has a valid COOKIE */
#define NS_CLIENTATTR_WANTEXPIRE 0x00800 /*%< return seconds to expire */
#define NS_CLIENTATTR_HAVEEXPIRE 0x01000 /*%< return seconds to expire */
#define NS_CLIENTATTR_WANTOPT	 0x02000 /*%< add opt to reply */
#define NS_CLIENTATTR_HAVEECS	 0x04000 /*%< received an ECS option */
#define NS_CLIENTATTR_WANTPAD	 0x08000 /*%< pad reply */
#define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */

#define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */

/*
 * Flag to use with the SERVFAIL cache to indicate
 * that a query had the CD bit set.
 */
#define NS_FAILCACHE_CD 0x01

extern atomic_uint_fast64_t ns_client_requests;

/***
 *** Functions
 ***/

/*
 * Note!  These ns_client_ routines MUST be called ONLY from the client's
 * task in order to ensure synchronization.
 */

void
ns_client_send(ns_client_t *client);
/*%<
 * Finish processing the current client request and
 * send client->message as a response.
 * \brief
 * Note!  These ns_client_ routines MUST be called ONLY from the client's
 * task in order to ensure synchronization.
 */

void
ns_client_sendraw(ns_client_t *client, dns_message_t *msg);
/*%<
 * Finish processing the current client request and
 * send msg as a response using client->message->id for the id.
 */

void
ns_client_error(ns_client_t *client, isc_result_t result);
/*%<
 * Finish processing the current client request and return
 * an error response to the client.  The error response
 * will have an RCODE determined by 'result'.
 */

void
ns_client_extendederror(ns_client_t *client, uint16_t code, const char *text);
/*%<
 * Set extended error with INFO-CODE <code> and EXTRA-TEXT <text>.
 */

void
ns_client_drop(ns_client_t *client, isc_result_t result);
/*%<
 * Log the reason the current client request has failed; no response
 * will be sent.
 */

bool
ns_client_shuttingdown(ns_client_t *client);
/*%<
 * Return true iff the client is currently shutting down.
 */

isc_result_t
ns_client_replace(ns_client_t *client);
/*%<
 * Try to replace the current client with a new one, so that the
 * current one can go off and do some lengthy work without
 * leaving the dispatch/socket without service.
 */

void
ns_client_settimeout(ns_client_t *client, unsigned int seconds);
/*%<
 * Set a timer in the client to go off in the specified amount of time.
 */

isc_result_t
ns_clientmgr_create(ns_server_t *sctx, isc_taskmgr_t *taskmgr,
		    isc_timermgr_t *timermgr, dns_aclenv_t *aclenv, int tid,
		    ns_clientmgr_t **managerp);
/*%<
 * Create a client manager.
 */

void
ns_clientmgr_shutdown(ns_clientmgr_t *manager);
/*%<
 * Shutdown a client manager and all ns_client_t objects
 * managed by it
 */

void
ns_clientmgr_detach(ns_clientmgr_t **managerp);
/*%<
 * Detach from a client manager.
 */

isc_sockaddr_t *
ns_client_getsockaddr(ns_client_t *client);
/*%<
 * Get the socket address of the client whose request is
 * currently being processed.
 */

isc_sockaddr_t *
ns_client_getdestaddr(ns_client_t *client);
/*%<
 * Get the destination address (server) for the request that is
 * currently being processed.
 */

isc_result_t
ns_client_checkaclsilent(ns_client_t *client, isc_netaddr_t *netaddr,
			 dns_acl_t *acl, bool default_allow);

/*%<
 * Convenience function for client request ACL checking.
 *
 * Check the current client request against 'acl'.  If 'acl'
 * is NULL, allow the request iff 'default_allow' is true.
 * If netaddr is NULL, check the ACL against client->peeraddr;
 * otherwise check it against netaddr.
 *
 * Notes:
 *\li	This is appropriate for checking allow-update,
 * 	allow-query, allow-transfer, etc.  It is not appropriate
 * 	for checking the blackhole list because we treat positive
 * 	matches as "allow" and negative matches as "deny"; in
 *	the case of the blackhole list this would be backwards.
 *
 * Requires:
 *\li	'client' points to a valid client.
 *\li	'netaddr' points to a valid address, or is NULL.
 *\li	'acl' points to a valid ACL, or is NULL.
 *
 * Returns:
 *\li	ISC_R_SUCCESS	if the request should be allowed
 * \li	DNS_R_REFUSED	if the request should be denied
 *\li	No other return values are possible.
 */

isc_result_t
ns_client_checkacl(ns_client_t *client, isc_sockaddr_t *sockaddr,
		   const char *opname, dns_acl_t *acl, bool default_allow,
		   int log_level);
/*%<
 * Like ns_client_checkaclsilent, except the outcome of the check is
 * logged at log level 'log_level' if denied, and at debug 3 if approved.
 * Log messages will refer to the request as an 'opname' request.
 *
 * Requires:
 *\li	'client' points to a valid client.
 *\li	'sockaddr' points to a valid address, or is NULL.
 *\li	'acl' points to a valid ACL, or is NULL.
 *\li	'opname' points to a null-terminated string.
 */

void
ns_client_log(ns_client_t *client, isc_logcategory_t *category,
	      isc_logmodule_t *module, int level, const char *fmt, ...)
	ISC_FORMAT_PRINTF(5, 6);

void
ns_client_logv(ns_client_t *client, isc_logcategory_t *category,
	       isc_logmodule_t *module, int level, const char *fmt, va_list ap)
	ISC_FORMAT_PRINTF(5, 0);

void
ns_client_aclmsg(const char *msg, const dns_name_t *name, dns_rdatatype_t type,
		 dns_rdataclass_t rdclass, char *buf, size_t len);

#define NS_CLIENT_ACLMSGSIZE(x)                           \
	(DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + \
	 DNS_RDATACLASS_FORMATSIZE + sizeof(x) + sizeof("'/'"))

void
ns_client_recursing(ns_client_t *client);
/*%<
 * Add client to end of th recursing list.
 */

void
ns_client_killoldestquery(ns_client_t *client);
/*%<
 * Kill the oldest recursive query (recursing list head).
 */

void
ns_client_dumprecursing(FILE *f, ns_clientmgr_t *manager);
/*%<
 * Dump the outstanding recursive queries to 'f'.
 */

void
ns_client_qnamereplace(ns_client_t *client, dns_name_t *name);
/*%<
 * Replace the qname.
 */

isc_result_t
ns_client_sourceip(dns_clientinfo_t *ci, isc_sockaddr_t **addrp);

isc_result_t
ns_client_addopt(ns_client_t *client, dns_message_t *message,
		 dns_rdataset_t **opt);

/*%<
 * Get a client object from the inactive queue, or create one, as needed.
 * (Not intended for use outside this module and associated tests.)
 */

void
ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
		   isc_region_t *region, void *arg);

/*%<
 * Handle client requests.
 * (Not intended for use outside this module and associated tests.)
 */

isc_result_t
ns__client_tcpconn(isc_nmhandle_t *handle, isc_result_t result, void *arg);

/*%<
 * Called every time a TCP connection is establish.  This is used for
 * updating TCP statistics.
 */

dns_rdataset_t *
ns_client_newrdataset(ns_client_t *client);

void
ns_client_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp);
/*%<
 * Get and release temporary rdatasets in the client message;
 * used in query.c and in plugins.
 */

isc_result_t
ns_client_newnamebuf(ns_client_t *client);
/*%<
 * Allocate a name buffer for the client message.
 */

dns_name_t *
ns_client_newname(ns_client_t *client, isc_buffer_t *dbuf, isc_buffer_t *nbuf);
/*%<
 * Get a temporary name for the client message.
 */

isc_buffer_t *
ns_client_getnamebuf(ns_client_t *client);
/*%<
 * Get a name buffer from the pool, or allocate a new one if needed.
 */

void
ns_client_keepname(ns_client_t *client, dns_name_t *name, isc_buffer_t *dbuf);
/*%<
 * Adjust buffer 'dbuf' to reflect that 'name' is using space in it,
 * and set client attributes appropriately.
 */

void
ns_client_releasename(ns_client_t *client, dns_name_t **namep);
/*%<
 * Release 'name' back to the pool of temporary names for the client
 * message. If it is using a name buffer, relinquish its exclusive
 * rights on the buffer.
 */

isc_result_t
ns_client_newdbversion(ns_client_t *client, unsigned int n);
/*%<
 * Allocate 'n' new database versions for use by client queries.
 */

ns_dbversion_t *
ns_client_getdbversion(ns_client_t *client);
/*%<
 * Get a free database version for use by a client query, allocating
 * a new one if necessary.
 */

ns_dbversion_t *
ns_client_findversion(ns_client_t *client, dns_db_t *db);
/*%<
 * Find the correct database version to use with a client query.
 * If we have already done a query related to the database 'db',
 * make sure subsequent queries are from the same version;
 * otherwise, take a database version from the list of dbversions
 * allocated by ns_client_newdbversion().
 */

isc_result_t
ns__client_setup(ns_client_t *client, ns_clientmgr_t *manager, bool new);
/*%<
 * Perform initial setup of an allocated client.
 */

void
ns__client_reset_cb(void *client0);
/*%<
 * Reset the client object so that it can be reused.
 */

void
ns__client_put_cb(void *client0);
/*%<
 * Free all resources allocated to this client object, so that
 * it can be freed.
 */
