/*
 * Copyright (c) 2002-2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: sndrcv.c,v 1.20 2006/10/05 01:42:23 ca Exp $")
#include "sm/types.h"
#include "sm/ctype.h"
#include "sm/dns.h"
#include "sm/dnstsk.h"
#include "sm/dns-int.h"
#include "log.h"
#if MTA_LIBDNS_DEBUG
# include "sm/io.h"
#endif

/*
**  See RFC 1035 about the format of messages.

4.2.2. TCP usage

Messages sent over TCP connections use server port 53 (decimal).  The
message is prefixed with a two byte length field which gives the message
length, excluding the two byte length field.  This length field allows
the low-level processing to assemble a complete message before beginning
to parse it.

*/

/*
**  DNS_SEND -- send a query to a nameserver
**
**	Parameters:
**		dns_tsk -- DNS task (includes query)
**
**	Return value:
**		usual sm_error code
*/

sm_ret_T
dns_send(dns_tsk_P dns_tsk)
{
	ssize_t r;

	SM_ASSERT(dns_tsk != NULL);

	/*
	**  XXX shouldn't this switch "automagically", i.e., when a
	**  truncation error occurs? But then we need a different socket...
	*/

	if (SM_IS_FLAG(dns_tsk->dnstsk_flags, DNS_TSK_FL_USETCP))
	{
		u_short len;
		struct iovec io[2];

		len = htons(sm_str_getlen(dns_tsk->dnstsk_wr));
		io[0].iov_base = (void *) &len;
		io[0].iov_len = sizeof(len);
		io[1].iov_base = (void *) sm_str_data(dns_tsk->dnstsk_wr);
		io[1].iov_len = sm_str_getlen(dns_tsk->dnstsk_wr);

		r = writev(dns_tsk->dnstsk_fd, io, 2);
		MTA_LIBDNS_DBG_DPRINTF((smioerr, "dns_send: writev()=%d\n", r));
	}
	else if (SM_IS_FLAG(dns_tsk->dnstsk_flags, DNS_TSK_FL_CONNECTUDP))
	{
		r = write(dns_tsk->dnstsk_fd,
			(const void *) sm_str_data(dns_tsk->dnstsk_wr),
			sm_str_getlen(dns_tsk->dnstsk_wr));
		MTA_LIBDNS_DBG_DPRINTF((smioerr, "dns_send: write()=%d\n", r));
	}
	else
	{
		r = sendto(dns_tsk->dnstsk_fd,
			(const void *) sm_str_data(dns_tsk->dnstsk_wr),
			sm_str_getlen(dns_tsk->dnstsk_wr), 0,
			(sockaddr_P) &dns_tsk->dnstsk_sin,
			sizeof(sockaddr_in_T));
		MTA_LIBDNS_DBG_DPRINTF((smioerr, "dns_send: sendto(%d)=%d\n",
			sm_str_getlen(dns_tsk->dnstsk_wr), r));
	}
	if (r == -1)
		return sm_error_perm(SM_EM_DNS, errno == 0 ? EIO : errno);
	else if (r < sm_str_getlen(dns_tsk->dnstsk_wr))
		return sm_error_temp(SM_EM_DNS, errno == 0 ? EIO : errno);
	return SM_SUCCESS;
}

/*
**  DNS_RECEIVE -- receive a reply from the nameserver
**
**  Parameters:
**		dns_tsk -- DNS task (stores reply)
**
**  Return value:
**		>0 Bytes returned;
**		==0 if the message timed out
**		<0: usual sm_error code
*/

sm_ret_T
dns_receive(dns_tsk_P dns_tsk)
{
	sm_ret_T ret;
	ssize_t r;
	socklen_T fromlen;
	sockaddr_in_T from;

	SM_ASSERT(dns_tsk != NULL);

	fromlen = sizeof(from);
	sm_memzero((char *)&from, sizeof(from));
	if (SM_IS_FLAG(dns_tsk->dnstsk_flags, DNS_TSK_FL_USETCP))
	{
		u_short len;
		int part;
		uchar *where;

		errno = 0;
		r = read(dns_tsk->dnstsk_fd, (void *) &len, sizeof(len));
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"dns_receive: read(%d)=%d\n", len, r));
		if (r == -1)
			return sm_error_temp(SM_EM_DNS, errno);
		else if (r < (int)sizeof(len))
		{
			sm_log_write(dns_tsk->dnstsk_mgr->dnsmgr_lctx,
				LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=dns_receive, where=1, status=nameserver_truncated, r=%d, len=%d, errno=%d, fd=%d\n",
				r, sizeof(len), errno, dns_tsk->dnstsk_fd);
			return sm_error_temp(SM_EM_DNS, EIO);
		}
#if 0
		if (!ipv4_addr_eq(dns_tsk->dnstsk_sin.sin_addr.s_addr,
				from.sin_addr.s_addr))
		{
			MTA_LIBDNS_DBG_DPRINTF((smioerr,
				"read(): send_addr=%x, recv_addr=%x\n",
				dns_tsk->dnstsk_sin.sin_addr.s_addr,
				from.sin_addr.s_addr));
			r = sm_error_temp(SM_EM_DNS, SM_E_IP_MISM);
		}
#endif /* 0 */
		len = ntohs(len);
		ret = sm_str_space(dns_tsk->dnstsk_rd, len);
		if (sm_is_err(ret))
			return sm_error_temp(SM_EM_DNS, sm_error_value(ret));
		SM_STR_SETLEN(dns_tsk->dnstsk_rd, len);
		r = 0;
		where = sm_str_data(dns_tsk->dnstsk_rd);

		/* XXX Do NOT loop here... need to store state in dns_tsk! */
		while (len > 0)
		{
			part = read(dns_tsk->dnstsk_fd, (void *) where, len);
			MTA_LIBDNS_DBG_DPRINTF((smioerr,
				"dns_receive: 2 read(%d)=%d\n", len, part));
			if (part == -1)
			{
				sm_log_write(dns_tsk->dnstsk_mgr->dnsmgr_lctx,
					LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER,
					SM_LOG_ERROR, 4,
					"sev=ERROR, func=dns_receive, read=%s",
					strerror(errno));
				break;
			}
			else if (part == 0)
			{
				MTA_LIBDNS_DBG_DPRINTF((smioerr,
					"2 nameserver read(): truncated"));
				return 0;
			}
			r += part;
			len -= part;
			where += part;
		}
	}
	else
	{
		r = recvfrom(dns_tsk->dnstsk_fd,
			(void *) sm_str_data(dns_tsk->dnstsk_rd),
			sm_str_getsize(dns_tsk->dnstsk_rd), 0,
			(sockaddr_P)&from, &fromlen);
		MTA_LIBDNS_DBG_DPRINTF((smioerr,
			"dns_receive: recvfrom(%d)=%d\n",
			sm_str_getsize(dns_tsk->dnstsk_rd), r));
		if (r == -1)
		{
			MTA_LIBDNS_DBG_DPRINTF((smioerr,
				"nameserver recvfrom(): %s",
				strerror(errno)));
			if (r == EAGAIN)
				r = 0;
			else
				r = sm_error_temp(SM_EM_DNS, r);
		}
		else if (r >= 0)
		{
			if (ipv4_addr_eq(dns_tsk->dnstsk_sin.sin_addr.s_addr,
					from.sin_addr.s_addr))
			{
				/* addresses are equal */
				SM_STR_SETLEN(dns_tsk->dnstsk_rd, r);
			}
			else
			{
				MTA_LIBDNS_DBG_DPRINTF((smioerr,
					"recvfrom(): send_addr=%x, recv_addr=%x\n",
					dns_tsk->dnstsk_sin.sin_addr.s_addr,
					from.sin_addr.s_addr));
				r = sm_error_temp(SM_EM_DNS, SM_E_IP_MISM);
			}
		}
	}
	return r;
}


syntax highlighted by Code2HTML, v. 0.9.1