/*
 * Copyright (c) 1985, 1989 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static char Version[] = "@(#)send.c	e07@nikhef.nl (Eric Wassenaar) 991331";
#endif

#include "host.h"

char *dbprefix = DBPREFIX;	/* prefix for debug messages to stdout */

ipaddr_t srcaddr = INADDR_ANY;	/* explicit source ip address */

int minport = 0;		/* first source port in explicit range */
int maxport = 0;		/* last  source port in explicit range */

static int timeout;		/* connection read timeout */

#if !defined(NO_CONNECTED_DGRAM)
static bool connected = TRUE;	/* we can use connected datagram sockets */
#else
static bool connected = FALSE;	/* connected datagram sockets unavailable */
#endif

static struct sockaddr_in from;	/* address of inbound packet */
static struct sockaddr *from_sa = (struct sockaddr *)&from;

#ifdef HOST_RES_SEND

/*
** RES_SEND -- Send nameserver query and retrieve answer
** -----------------------------------------------------
**
**	Returns:
**		Length of (untruncated) nameserver answer, if obtained.
**		-1 if an error occurred (errno set appropriately).
**
**	This is a simplified version of the BIND 4.8.3 res_send().
**	- Always use connected datagrams to get proper error messages.
**	- Do not only return ETIMEDOUT or ECONNREFUSED in datagram mode,
**	  but also host or network unreachable status if appropriate.
**	- Never leave a connection open after we got an answer.
**	- No special ECONNRESET handling when using virtual circuits.
**
**	Note that this private version of res_send() is not only called
**	directly by 'host' but also indirectly by gethostbyname() or by
**	gethostbyaddr() via their resolver interface routines.
*/

int
res_send(query, querylen, answer, anslen)
input CONST qbuf_t *query;		/* location of formatted query buffer */
input int querylen;			/* length of query buffer */
output qbuf_t *answer;			/* location of buffer to store answer */
input int anslen;			/* maximum size of answer buffer */
{
	HEADER *bp = (HEADER *)answer;
	struct sockaddr_in *addr;	/* the server address to connect to */
	int v_circuit;			/* virtual circuit or datagram switch */
	int servfail[MAXNS];		/* saved failure codes per nameserver */
	register int try, ns;
	register int n;

	/* make sure resolver has been initialized */
	if (!bitset(RES_INIT, _res.options) && res_init() == -1)
		return(-1);

	if (bitset(RES_DEBUG, _res.options))
	{
		printf("%sres_send()\n", dbprefix);
		pr_query(query, querylen, stdout);
	}

	/* use virtual circuit if requested or if necessary */
	v_circuit = bitset(RES_USEVC, _res.options) || (querylen > PACKETSZ);

	/* reset server failure codes */
	for (ns = 0; ns < MAXNS; ns++)
		servfail[ns] = 0;

/*
 * Do _res.retry attempts for each of the _res.nscount addresses.
 * Upon failure, the current server will be marked bad if we got
 * an error condition which makes it unlikely that we will succeed
 * the next time we try this server.
 * Internal operating system failures, such as temporary lack of
 * resources, do not fall in that category.
 */
	for (try = 0; try < _res.retry; try++)
	{
	    for (ns = 0; ns < _res.nscount; ns++)
	    {
		/* skip retry if server failed permanently */
		if (servfail[ns])
			continue;

		/* fetch server address */
		addr = &nslist(ns);
retry:
		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sQuerying server (# %d) %s address = %s\n",
				dbprefix, ns+1, v_circuit ? "tcp" : "udp",
				inet_ntoa(addr->sin_addr));
		}

		if (v_circuit)
		{
			/* at most one attempt per server */
			try = _res.retry;

			/* connect via virtual circuit */
			n = send_stream(addr, query, querylen, answer, anslen);
		}
		else
		{
			/* set datagram read timeout for recv_sock() */
			timeout = (_res.retrans << try);
			if (try > 0)
				timeout /= _res.nscount;
			if (timeout <= 0)
				timeout = 1;

			/* connect via datagram */
			n = send_dgram(addr, query, querylen, answer, anslen);

			/* check truncation; use v_circuit with same server */
			if ((n > 0) && bp->tc)
			{
				if (bitset(RES_DEBUG, _res.options))
				{
					printf("%struncated answer, %d bytes\n",
						dbprefix, n);
				}

				if (!bitset(RES_IGNTC, _res.options))
				{
					v_circuit = 1;
					goto retry;
				}
			}
		}

		if (n <= 0)
		{
			switch (errno)
			{
			    case ECONNREFUSED:
			    case ENETDOWN:
			    case ENETUNREACH:
			    case EHOSTDOWN:
			    case EHOSTUNREACH:
				servfail[ns] = errno;
				break;
			}

			/* try next server */
			continue;
		}

		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sgot answer, %d bytes:\n", dbprefix, n);
			pr_query(answer, (n > anslen) ? anslen : n, stdout);
		}

		/* we have an answer; clear possible error condition */
		seterrno(0);
		return(n);
	    }
	}

	/* no answer obtained; return error condition */
	return(-1);
}

/*
** _RES_CLOSE -- Close an open stream or dgram connection
** ------------------------------------------------------
**
**	Returns:
**		None.
*/

static int srvsock = -1;	/* socket descriptor */

void
_res_close()
{
	int save_errno = errno;		/* preserve state */

	/* close the connection if open */
	if (srvsock >= 0)
	{
		(void) close(srvsock);
		srvsock = -1;
	}

	/* restore state */
	seterrno(save_errno);
}

/*
** CHECK_FROM -- Make sure the response comes from a known server
** --------------------------------------------------------------
**
**	Returns:
**		Nonzero if the source address is known.
**		Zero otherwise.
*/

static bool
check_from()
{
	struct sockaddr_in *addr;
	register int ns;

	for (ns = 0; ns < _res.nscount; ns++)
	{
		/* fetch server address */
		addr = &nslist(ns);

		if (from.sin_family != addr->sin_family)
			continue;

		if (from.sin_port != addr->sin_port)
			continue;

		/* this allows a reply from any responding server */
		if (addr->sin_addr.s_addr == INADDR_ANY)
			return(TRUE);

		if (from.sin_addr.s_addr == addr->sin_addr.s_addr)
			return(TRUE);
	}

	/* matches none of the known addresses */
	return(FALSE);
}

/*
** SEND_STREAM -- Query nameserver via virtual circuit
** ---------------------------------------------------
**
**	Returns:
**		Length of (untruncated) nameserver answer, if obtained.
**		-1 if an error occurred.
**
**	A new socket is allocated for each call, and it is never
**	left open. Checking the packet id is rather redundant.
**
**	Note that connect() is the call that is allowed to fail
**	under normal circumstances. All other failures generate
**	an unconditional error message.
**	Note that truncation is handled within _res_read().
*/

static int
send_stream(addr, query, querylen, answer, anslen)
input struct sockaddr_in *addr;		/* the server address to connect to */
input qbuf_t *query;			/* location of formatted query buffer */
input int querylen;			/* length of query buffer */
output qbuf_t *answer;			/* location of buffer to store answer */
input int anslen;			/* maximum size of answer buffer */
{
	char *host = NULL;		/* name of server is unknown */
	HEADER *qp = (HEADER *)query;
	HEADER *bp = (HEADER *)answer;
	register int n;

/*
 * Setup a virtual circuit connection.
 */
	srvsock = _res_socket(AF_INET, SOCK_STREAM, 0);
	if (srvsock < 0)
	{
		_res_perror(addr, host, "socket");
		return(-1);
	}

	if (_res_connect(srvsock, addr, sizeof(*addr)) < 0)
	{
		if (bitset(RES_DEBUG, _res.options))
			_res_perror(addr, host, "connect");
		_res_close();
		return(-1);
	}

	if (bitset(RES_DEBUG, _res.options))
	{
		printf("%sconnected to %s\n",
			dbprefix, inet_ntoa(addr->sin_addr));
	}

/*
 * Send the query buffer.
 */
	if (_res_write(srvsock, addr, host, (char *)query, querylen) < 0)
	{
		_res_close();
		return(-1);
	}

/*
 * Read the answer buffer.
 */
wait:
	n = _res_read(srvsock, addr, host, (char *)answer, anslen);
	if (n <= 0)
	{
		_res_close();
		return(-1);
	}

/*
 * Make sure it is the proper response by checking the packet id.
 */
	if (qp->id != bp->id)
	{
		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sunexpected answer:\n", dbprefix);
			pr_query(answer, (n > anslen) ? anslen : n, stdout);
		}
		goto wait;
	}

/*
 * Never leave the socket open.
 */
	_res_close();
	return(n);
}

/*
** SEND_DGRAM -- Query nameserver via datagram
** -------------------------------------------
**
**	Returns:
**		Length of nameserver answer, if obtained.
**		-1 if an error occurred.
**
**	Inputs:
**		The global variable ``timeout'' should have been
**		set with the desired timeout value in seconds.
**
**	Sending to a nameserver datagram port with no nameserver running
**	will cause an ICMP port unreachable message to be returned. If the
**	socket is connected, we get an ECONNREFUSED error on the next socket
**	operation, and select returns if the error message is received.
**	Also, we get ENETUNREACH or EHOSTUNREACH errors if appropriate.
**	We thus get a proper error status before timing out.
**	This method usually works only if BSD >= 43.
**
**	Note that send() and recvfrom() are now the calls that are allowed
**	to fail under normal circumstances.
*/

static int
send_dgram(addr, query, querylen, answer, anslen)
input struct sockaddr_in *addr;		/* the server address to connect to */
input qbuf_t *query;			/* location of formatted query buffer */
input int querylen;			/* length of query buffer */
output qbuf_t *answer;			/* location of buffer to store answer */
input int anslen;			/* maximum size of answer buffer */
{
	char *host = NULL;		/* name of server is unknown */
	HEADER *qp = (HEADER *)query;
	HEADER *bp = (HEADER *)answer;
	register int n;

/*
 * Setup a connected (if possible) datagram socket.
 */
	srvsock = _res_socket(AF_INET, SOCK_DGRAM, 0);
	if (srvsock < 0)
	{
		_res_perror(addr, host, "socket");
		return(-1);
	}

	if (connected)
	{
		if (connect(srvsock, (struct sockaddr *)addr, sizeof(*addr)) < 0)
		{
			_res_perror(addr, host, "connect");
			_res_close();
			return(-1);
		}
	}

/*
 * Send the query buffer.
 */
	if (connected)
		n = send(srvsock, (char *)query, querylen, 0);
	else
		n = sendto(srvsock, (char *)query, querylen, 0,
			(struct sockaddr *)addr, sizeof(*addr));

	if (n != querylen)
	{
		if (bitset(RES_DEBUG, _res.options))
			_res_perror(addr, host, "send");
		_res_close();
		return(-1);
	}

/*
 * Wait for the arrival of a reply, timeout, or error message.
 */
wait:
	n = recv_sock(srvsock, (char *)answer, anslen);
	if (n <= 0)
	{
		if (bitset(RES_DEBUG, _res.options))
			_res_perror(addr, host, "recvfrom");
		_res_close();
		return(-1);
	}

/*
 * Make sure it is the proper response by checking the packet id.
 */
	if (qp->id != bp->id)
	{
		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sold answer:\n", dbprefix);
			pr_query(answer, (n > anslen) ? anslen : n, stdout);
		}
		goto wait;
	}

/*
 * Make sure it comes from a known server.
 */
	if (!check_from())
	{
		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sunknown server %s:\n",
				dbprefix, inet_ntoa(from.sin_addr));
			pr_query(answer, (n > anslen) ? anslen : n, stdout);
		}
		goto wait;
	}

/*
 * Never leave the socket open.
 */
	_res_close();
	return(n);
}

#endif /*HOST_RES_SEND*/

/*
** _RES_SOCKET -- Obtain a socket and set additional parameters
** ------------------------------------------------------------
**
**	Returns:
**		socket descriptor if successfully obtained.
**		-1 in case of failure.
**
**	In special circumstances it may be necessary to assign
**	an explicit port number to the client communication socket,
**	e.g. when we are behind a packet filtering firewall that
**	only allows incoming traffic with port numbers in a certain
**	specific range.
**
**	In the case of a stream (tcp) socket, we could have set
**	the SO_REUSEADDR socket option, but this has side-effects.
**	Therefore a single explicit tcp port cannot be used.
**
**	An explicit source IP address may be necessary in case of
**	multi-homed hosts with asymmetric routing policy.
*/

int
_res_socket(family, type, protocol)
input int family;
input int type;
input int protocol;
{
	struct sockaddr_in sin;
	register int port;
	int sock;

	/* try to obtain the desired socket */
	sock = socket(family, type, protocol);
	if (sock < 0)
		return(-1);

	/* set an explicit source address/port if so requested */
	for (port = minport; port > 0 || srcaddr != INADDR_ANY; port++)
	{
		/* setup source address */
		bzero((char *)&sin, sizeof(sin));

		sin.sin_family = family;
		sin.sin_addr.s_addr = srcaddr;
		sin.sin_port = htons((u_short)port);

		if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
		{
			int save_errno = errno;
			if (port > 0 && errno == EADDRINUSE)
			{
				/* save_errno = EAGAIN; */
				if (port < maxport)
					continue;
			}

			/* bad source address, or no free port numbers */
			(void) close(sock);
			seterrno(save_errno);
			return(-1);
		}

		if (bitset(RES_DEBUG, _res.options))
		{
			if (srcaddr == INADDR_ANY)
				printf("%susing source port %d\n",
					dbprefix, port);
			else if (port == 0)
				printf("%susing source address %s\n",
					dbprefix, inet_ntoa(sin.sin_addr));
			else
				printf("%susing source address %s port %d\n",
					dbprefix, inet_ntoa(sin.sin_addr), port);
		}

		/* socket with well-defined source address/port */
		return(sock);
	}

	/* socket with random source address/port */
	return(sock);
}

/*
** _RES_BLOCKING -- Use blocking or non-blocking socket I/O
** --------------------------------------------------------
**
**	Returns:
**		0 if new mode was successfully set.
**		-1 in case of failure.
**	
**	Under obscure circumstances when reading input from the socket,
**	select() may indicate that data is available, whereas in practice
**	there isn't any, and the subsequent recvfrom() will hang forever.
**	This condition can be detected by reading in non-blocking mode,
**	and restarting the select() if appropriate.
*/

int
_res_blocking(sock, blocking)
input int sock;
input bool blocking;			/* indicate blocking or not */
{
	int flags;
	register int n;

#if !defined(WINNT)

	/* fetch current settings */
	n = fcntl(sock, F_GETFL, 0);
	flags = (n == -1) ? 0 : n;

	/* update settings appropriately */
	flags = blocking ? (flags & ~O_NDELAY) : (flags | O_NDELAY);
	n = fcntl(sock, F_SETFL, flags);

#else /*WINNT*/

	/* just set the desired mode */
	flags = blocking ? 0 : 1;
	n = ioctlsocket(sock, FIONBIO, (u_long *)&flags);

#endif /*WINNT*/

	return(n);
}

/*
** _RES_CONNECT -- Connect to a stream (virtual circuit) socket
** ------------------------------------------------------------
**
**	Returns:
**		0 if successfully connected.
**		-1 in case of failure or timeout.
**
**	Note that we use _res.retrans to override the default
**	connect timeout value.
*/

static jmp_buf timer_buf;

static sigtype_t
/*ARGSUSED*/
timer(sig)
int sig;
{
	longjmp(timer_buf, 1);
	/*NOTREACHED*/
}

int
_res_connect(sock, addr, addrlen)
input int sock;
input struct sockaddr_in *addr;		/* the server address to connect to */
input int addrlen;
{
	if (setjmp(timer_buf) != 0)
	{
		seterrno(ETIMEDOUT);
		setalarm(0);
		return(-1);
	}

	setsignal(SIGALRM, timer);
	setalarm(_res.retrans);

	if (connect(sock, (struct sockaddr *)addr, addrlen) < 0)
	{
		if (errno == EINTR)
			seterrno(ETIMEDOUT);
		setalarm(0);
		return(-1);
	}

	setalarm(0);
	return(0);
}

/*
** _RES_WRITE -- Write the query buffer via a stream socket
** --------------------------------------------------------
**
**	Returns:
**		Length of buffer if successfully transmitted.
**		-1 in case of failure (error message is issued).
**
**	The query is sent in two steps: first a single word with
**	the length of the buffer, followed by the buffer itself.
*/

int
_res_write(sock, addr, host, buf, bufsize)
input int sock;
input struct sockaddr_in *addr;		/* the server address to connect to */
input char *host;			/* name of server to connect to */
input char *buf;			/* location of formatted query buffer */
input int bufsize;			/* length of query buffer */
{
	u_short len;

/*
 * Protect against remote peer prematurely closing the connection.
 */
	/* setsignal(SIGPIPE, SIG_IGN); done in main() */

/*
 * Write the length of the query buffer.
 */
	/* len = htons((u_short)bufsize); */
	putshort((u_short)bufsize, (u_char *)&len);

	if (send(sock, (char *)&len, INT16SZ, 0) != INT16SZ)
	{
		_res_perror(addr, host, "write query length");
		return(-1);
	}

/*
 * Write the query buffer itself.
 */
	if (send(sock, buf, bufsize, 0) != bufsize)
	{
		_res_perror(addr, host, "write query");
		return(-1);
	}

/*
 * Use non-blocking I/O to read the answer.
 */
	(void) _res_blocking(sock, FALSE);

	return(bufsize);
}

/*
** _RES_READ -- Read the answer buffer via a stream socket
** -------------------------------------------------------
**
**	Returns:
**		Length of (untruncated) answer if successfully received.
**		-1 in case of failure (error message is issued).
**
**	The answer is read in two steps: first a single word which
**	gives the length of the buffer, followed by the buffer itself.
**	If the answer is too long to fit into the supplied buffer,
**	only the portion that fits will be stored, the residu will be
**	flushed, and the truncation flag will be set.
**
**	Note. The returned length is that of the *un*truncated answer,
**	however, and not the amount of data that is actually available.
**	This may give the caller a hint about new buffer reallocation.
*/

int
_res_read(sock, addr, host, buf, bufsize)
input int sock;
input struct sockaddr_in *addr;		/* the server address to connect to */
input char *host;			/* name of server to connect to */
output char *buf;			/* location of buffer to store answer */
input int bufsize;			/* maximum size of answer buffer */
{
	u_short len;
	char *buffer;
	int buflen;
	int reslen;
	register int n;

	/* set stream timeout for recv_sock() */
	timeout = READTIMEOUT;

/*
 * Read the length of answer buffer.
 */
	buffer = (char *)&len;
	buflen = INT16SZ;

	while (buflen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
	{
		buffer += n;
		buflen -= n;
	}

	if (buflen != 0)
	{
		_res_perror(addr, host, "read answer length");
		return(-1);
	}

/*
 * Terminate if length is zero.
 */
	/* len = ntohs(len); */
	len = _getshort((u_char *)&len);
	if (len == 0)
		return(0);

/*
 * Check for truncation.
 * Do not chop the returned length in case of buffer overflow.
 */
	reslen = 0;
	if ((int)len > bufsize)
	{
		reslen = len - bufsize;
		/* len = bufsize; */
	}

/*
 * Read the answer buffer itself.
 * Truncate the answer is the supplied buffer is not big enough.
 */
	buffer = buf;
	buflen = (reslen > 0) ? bufsize : len;

	while (buflen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
	{
		buffer += n;
		buflen -= n;
	}

	if (buflen != 0)
	{
		_res_perror(addr, host, "read answer");
		return(-1);
	}

/*
 * Discard the residu to keep connection in sync.
 */
	if (reslen > 0)
	{
		HEADER *bp = (HEADER *)buf;
		char resbuf[PACKETSZ];

		buffer = resbuf;
		buflen = (reslen < sizeof(resbuf)) ? reslen : sizeof(resbuf);

		while (reslen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
		{
			reslen -= n;
			buflen = (reslen < sizeof(resbuf)) ? reslen : sizeof(resbuf);
		}

		if (reslen != 0)
		{
			_res_perror(addr, host, "read residu");
			return(-1);
		}

		if (bitset(RES_DEBUG, _res.options))
		{
			printf("%sresponse truncated to %d bytes\n",
				dbprefix, bufsize);
		}

		/* set truncation flag */
		bp->tc = 1;
	}

	return(len);
}

/*
** RECV_SOCK -- Read from stream or datagram socket with timeout
** -------------------------------------------------------------
**
**	Returns:
**		Length of buffer if successfully received.
**		-1 in case of failure or timeout.
**	Inputs:
**		The global variable ``timeout'' should have been
**		set with the desired timeout value in seconds.
**	Outputs:
**		Sets ``from'' to the address of the packet sender.
*/

static int
recv_sock(sock, buffer, buflen)
input int sock;
output char *buffer;			/* current buffer address */
input int buflen;			/* remaining buffer size */
{
	fd_set fds;
	struct timeval wait;
	int fromlen;
	register int n;

	wait.tv_sec = timeout;
	wait.tv_usec = 0;
rewait:
	/* FD_ZERO(&fds); */
	bzero((char *)&fds, sizeof(fds));
	FD_SET(sock, &fds);

	/* wait for the arrival of data, or timeout */
	n = select(FD_SETSIZE, &fds, (fd_set *)NULL, (fd_set *)NULL, &wait);
	if (n < 0 && errno == EINTR)
		goto rewait;
	if (n == 0)
		seterrno(ETIMEDOUT);
	if (n <= 0)
		return(-1);
reread:
	/* fake an error if nothing was actually read */
	fromlen = sizeof(from);
	n = recvfrom(sock, buffer, buflen, 0, from_sa, &fromlen);
	if (n < 0 && errno == EINTR)
		goto reread;
	if (n < 0 && errno == EWOULDBLOCK)
		goto rewait;
	if (n == 0)
		seterrno(ECONNRESET);
	return(n);
}

/*
 * Alternative version for systems with broken networking code.
 *
 * The select() system call may fail on the solaris 2.4 platform
 * without appropriate patches. However, these patches are reported
 * to break client NFS.
 *
 * This version uses an alarm() instead of select(). This introduces
 * additional system call overhead.
 * Note that we cannot use non-blocking I/O in this mode.
 */

#ifdef BROKEN_SELECT

static int
recv_sock(sock, buffer, buflen)
input int sock;
output char *buffer;			/* current buffer address */
input int buflen;			/* remaining buffer size */
{
	int fromlen;
	register int n;

	if (setjmp(timer_buf) != 0)
	{
		seterrno(ETIMEDOUT);
		setalarm(0);
		return(-1);
	}

	setsignal(SIGALRM, timer);
	setalarm(timeout);
reread:
	/* fake an error if nothing was actually read */
	fromlen = sizeof(from);
	n = recvfrom(sock, buffer, buflen, 0, from_sa, &fromlen);
	if (n < 0 && errno == EINTR)
		goto reread;
	if (n == 0)
		seterrno(ECONNRESET);
	setalarm(0);
	return(n);
}

#endif /*BROKEN_SELECT*/

/*
** _RES_PERROR -- Issue perror message including host info
** -------------------------------------------------------
**
**	Returns:
**		None.
*/

void
_res_perror(addr, host, message)
input struct sockaddr_in *addr;		/* the server address to connect to */
input char *host;			/* name of server to connect to */
input char *message;			/* perror message string */
{
	int save_errno = errno;		/* preserve state */

	/* prepend server address and name */
	if (addr != NULL)
		(void) fprintf(stderr, "%s ", inet_ntoa(addr->sin_addr));
	if (host != NULL)
		(void) fprintf(stderr, "(%s) ", host);

	/* issue actual message */
	seterrno(save_errno);
	perror(message);

	/* restore state */
	seterrno(save_errno);
}


syntax highlighted by Code2HTML, v. 0.9.1