/**************************************************************************************************
	$Header: /pub/cvsroot/yencode/src/ypost/sock.c,v 1.2 2002/03/21 04:58:31 bboy Exp $

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/


#include "ypost.h"


/* Current socket operation being performed (for timeout handler). */
static char *current_operation;

/* Macro to start a timeout condition block. */
#define START_TIMEOUT(s) \
	if (opt_timeout) { \
		current_operation = s; \
		signal(SIGALRM, sock_timeout_handler); \
		alarm(opt_timeout); \
	}

/* Macro to stop a timeout condition block. */
#define STOP_TIMEOUT \
	if (opt_timeout) { \
		signal(SIGALRM, SIG_DFL); \
		alarm(0); \
	}

/* Read buffers grow by this many bytes at a time. */
#define BUFFER_INCREMENT 128

/* Current FD if sock_setfd() is used. */
int _sock_current_fd = -1;

#define	Y_SOCKDEBUG_SEND	0
#define	Y_SOCKDEBUG_RECV	1


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCKDEBUG
	If debug is enabled, this will output the data sent or received, hiding wierd control chars
	and converting CR and LF to '\r' and '\n'.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
sockdebug(int direction, unsigned char *buf, size_t buflen)
{
	register int ct;

	if (!buf)
		return;

	if (direction == Y_SOCKDEBUG_RECV)
		fputs("[DEBUG]  IN: `", stderr);
	else
		fputs("[DEBUG] OUT: `", stderr);

	for (ct = 0; ct < buflen; ct++)
	{
		if (buf[ct] == CR)
		{
			fputc('\\', stderr);
			fputc('r', stderr);
		}
		else if (buf[ct] == LF)
		{
			fputc('\\', stderr);
			fputc('n', stderr);
		}
//		else if (iscntrl(buf[ct]))
//			fputc('?', stderr);
		else
			fputc(buf[ct], stderr);
	}
	fputc('\'', stderr);
	fputc('\n', stderr);
	fflush(stderr);
}
/*--- sockdebug() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_TIMEOUT_HANDLER
	Called if socket operations time out.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sock_timeout_handler(int signo)
{
	Err("%s: %s\n", current_operation ? current_operation : _("socket operation"),
		 _("specified timeout exceeded"));
	exit(EXIT_FAILURE);
}
/*--- sock_timeout_handler() --------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	Given a host and port, which should be specified in the format "host:port", constructs and
	returns a sockaddr_in record.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
struct sockaddr_in *
sock_getsockaddr(char *address)
{
	static struct sockaddr_in sa;								// Socket structureA
	struct servent *ent;											// Service entry
	struct hostent *he;											// Host entry
	char *hostname, *portname;									// Host and port part of `hostport'

	memset(&sa, 0, sizeof(struct sockaddr_in));        // Reset socket structure
	sa.sin_family = AF_INET;
	portname = address;
	hostname = strsep(&portname, ":");
	if (!hostname)
		Err("%s: %s", address, _("invalid server name"));

	if (portname)
		sa.sin_port = htons(atoi(portname));
	else
	{
		START_TIMEOUT(_("service entry lookup"));
		if (!(ent = getservbyname("nntp", "tcp")))
			Err(_("unable to get default port for service `nntp/tcp'"));
		STOP_TIMEOUT;
		sa.sin_port = ent->s_port;
	}

	START_TIMEOUT(_("server name resolution"));
	if (!(he = gethostbyname(hostname)))
		Err("%s: %s", address, _("unable to resolve server address"));
	STOP_TIMEOUT;

	memcpy(&sa.sin_addr, he->h_addr_list[0], sizeof(struct in_addr));
	return (&sa);
}
/*--- sock_getsockaddr() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	Opens a connection to the specified address.  Returns the new file descriptor.  Errors fatal.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
sock_open(struct sockaddr_in *sa)
{
	int fd;															// File descriptor of new socket
	int rv;															// Return value from connect

	START_TIMEOUT(_("socket creation"));
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)		// Create socket
		ErrERR(_("error creating socket"));
	STOP_TIMEOUT;

	START_TIMEOUT(_("socket connection"));
	while ((rv = connect(fd, (struct sockaddr *)sa, sizeof(struct sockaddr_in))) != 0)
	{
		if (errno == EAGAIN)
			continue;
		else
			ErrERR(_("error connecting to server"));
	}
	STOP_TIMEOUT;
	return (fd);
}
/*--- sock_open() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_READ_FD
	Reads from the socket, obeying timeout.  Returns number of bytes read.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
ssize_t
sock_read_fd(int fd, unsigned char *buf, size_t count)
{
	int  rv;															// Return value from read()

	memset(buf, 0, count);										// XXX Is this necessary?

	START_TIMEOUT(_("socket read"));
	rv = read(fd, buf, count);
	STOP_TIMEOUT;

	if (rv < 0)														// Will this fail falsely on EAGAIN?
		ErrERR(_("error reading from socket"));

	return (rv);
}
/*--- sock_read_fd() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_GETS_FD
	Reads from the specified descriptor until '\n' is read.  Returns a pointer to a dynamically
	allocated buffer, which should be free()'d when no longer needed.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
unsigned char *
sock_gets_fd(int fd)
{
	int rv;															// Return value from select
	int offset = 0;												// Number of characters read
	int buflen = 0;												// Current size of input buffer
	unsigned char *buf;											// Input buffer

	// Start our input buffer off
	buf = (unsigned char *)xmalloc(BUFFER_INCREMENT, 1);
	memset(buf, 0, BUFFER_INCREMENT);
	buflen = BUFFER_INCREMENT;

	for (;;)
	{
		START_TIMEOUT(_("socket read"));
		if (!(rv = sock_read_fd(fd, buf + offset, 1)))		// Read one char at a time
			Err(_("connection closed by remote"));
		STOP_TIMEOUT;

		if (buf[offset] == LF)										// Are we done?
			break;

		// Grow the buffer if necessary
		if (++offset == buflen - 1)
		{
			buf = xrealloc(buf, buflen + BUFFER_INCREMENT);
			memset(buf + buflen, 0, BUFFER_INCREMENT);
			buflen += BUFFER_INCREMENT;
		}
	};

	if (opt_debug)
		sockdebug(Y_SOCKDEBUG_RECV, buf, strlen(buf));

	while ((buf[offset] == CR) || (buf[offset] == LF))
		buf[offset--] = (unsigned char)'\0';

	return (buf);
}
/*--- sock_gets_fd() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_WRITE_FD
	Writes to the socket, obeying timeout.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sock_write_fd(int fd, const unsigned char *buf, int count)
{
	int  offset = 0;												// Number of bytes written
	int  rv;															// Return value from write()

	do
	{
		START_TIMEOUT(_("socket write"));
		rv = write(fd, buf + offset, count - offset);
		STOP_TIMEOUT;
		if (rv == 0)
			Err(_("connection closed by remote"));
		if (rv < 0)
		{
			if (errno == EAGAIN)
				continue;
			Err(_("error writing to socket"));
		}
		if (opt_debug)
			sockdebug(Y_SOCKDEBUG_SEND, (unsigned char *)(buf+offset), rv);
		offset += rv;

	} while (offset < count);
}
/*--- sock_write_fd() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_PUTS_FD
	Writes a line to the descriptor.  Appends CR/LF if necessary.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sock_puts_fd(int fd, const unsigned char *data)
{
	int datalen = strlen(data);

	if (!strstr(data, CRLF))										// Append CR/LF if necessary
	{
		unsigned char *buf;

		buf = (unsigned char *)xmalloc((datalen + 3) * sizeof(unsigned char));
		memcpy(buf, data, datalen);
		buf[datalen] = CR;
		buf[datalen+1] = LF;
		buf[datalen+2] = (unsigned char)'\0';
		sock_write_fd(fd, buf, datalen + 2);
		free(buf);
	}
	else
		sock_write_fd(fd, data, datalen);
}
/*--- sock_puts_fd() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_PRINTF
	Only works if _sock_current_fd is set.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sock_printf(const char *fmt, ...)
{
	va_list	ap;
	size_t	len;
	unsigned char buf[BUFSIZ];
	unsigned char *outbuf;
	register unsigned char *i, *o;

	if (_SOCK_CURRENT_FD == -1)
		Err(_("unable to write; not connected to remote server"));

	va_start(ap, fmt);
	len = vsnprintf(buf, sizeof(buf)-4, fmt, ap);
	va_end(ap);

	/* Remove CR, convert LF to CRLF */
	outbuf = (unsigned char *)xmalloc(((len * 2) + 1) * sizeof(unsigned char));
	for (i = buf, o = outbuf; *i; i++)
	{
		if (*i == CR)
			continue;
		else if (*i == LF)
		{
			*(o++) = CR;
			*(o++) = LF;
		}
		else
			*(o++) = *i;
	}
	*o = '\0';
	sock_puts_fd(_SOCK_CURRENT_FD, outbuf);
	free(outbuf);
}
/*--- sock_printf() -----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SOCK_PUTS_METER_FD
	Writes a line to the descriptor, displaying a meter.  Appends CR/LF if necessary.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
long
sock_puts_meter_fd(int fd, const unsigned char *buf, long current, long total, const char *desc)
{
	int datalen = strlen(buf);									// Length of data to write
	unsigned char *data = NULL;								// The data to send
	register int  offset = 0;									// Number of bytes written
	int  rv;															// Return value from write()

	if (!strstr(buf, CRLF))										// Append CR/LF if necessary
	{
		data = (unsigned char *)xmalloc((datalen + 3) * sizeof(unsigned char));
		memcpy(data, buf, datalen);
		data[datalen] = CR;
		data[datalen+1] = LF;
		data[datalen+2] = (unsigned char)'\0';
		datalen += 2;
	}
	else
		data = (unsigned char *)xstrdup(buf);

	/* Write the data */
	do
	{
		START_TIMEOUT(_("socket write"));
		rv = write(fd, data + offset, datalen - offset);
		STOP_TIMEOUT;
		if (rv == 0)
			Err(_("connection closed by remote"));
		if (rv < 0)
		{
			if (errno == EAGAIN)
				continue;
			Err(_("error writing to socket"));
		}
		if (opt_debug)
			sockdebug(Y_SOCKDEBUG_SEND, (unsigned char *)(data+offset), rv);
		offset += rv;
		meter(current + offset, total, desc);
	} while (offset < datalen);

	free(data);
	return (offset);
}
/*--- sock_puts_fd() ----------------------------------------------------------------------------*/


/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1