/*
 * $Id: com-socket.c,v 1.4 1999/09/21 05:42:28 wiegand Exp $
 *
 * Common functions for TCP/IP sockets
 *
 * Author(s): Jens-Gero Boehm <jens-gero.boehm@suse.de>
 *            Pieter Hollants <pieter.hollants@suse.de>
 *            Marius Tomaschewski <mt@suse.de>
 *            Volker Wiegand <volker.wiegand@suse.de>
 *
 * This file is part of the SuSE Proxy Suite
 *            See also  http://proxy-suite.suse.de/
 *
 * 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.
 *
 * A history log can be found at the end of this file.
 */

#ifndef lint
static char rcsid[] = "$Id: com-socket.c,v 1.4 1999/09/21 05:42:28 wiegand Exp $";
#endif

#include <config.h>

#if defined(STDC_HEADERS)
#  include <stdio.h>
#  include <string.h>
#  include <stdlib.h>
#  include <stdarg.h>
#  include <errno.h>
#endif

#if defined(HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#if defined(TIME_WITH_SYS_TIME)
#  include <sys/time.h>
#  include <time.h>
#else
#  if defined(HAVE_SYS_TIME_H)
#    include <sys/time.h>
#  else
#    include <time.h>
#  endif
#endif

#if defined(HAVE_SYS_SELECT_H)
#  include <sys/select.h>
#endif

#if defined(HAVE_FCNTL_H)
#  include <fcntl.h>
#elif defined(HAVE_SYS_FCNTL_H)
#  include <sys/fcntl.h>
#endif

#include <sys/ioctl.h>

#include <netdb.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#if defined(HAVE_SYS_SOCKIO_H)
#  include <sys/sockio.h>
#endif

#if defined(HAVE_LIBWRAP)
#  if defined(HAVE_SYSLOG_H)
#    include <syslog.h>
#  endif
#  if defined(NEED_SYS_SYSLOG_H)
#    include <sys/syslog.h>
#  endif
#  include <tcpd.h>
#endif

#include "com-config.h"
#include "com-debug.h"
#include "com-misc.h"
#include "com-socket.h"
#include "com-syslog.h"


/* ------------------------------------------------------------ */

#if !defined(SOMAXCONN)
#  define SOMAXCONN	5	/* Default accept queue size	*/
#endif


/* ------------------------------------------------------------ */

static void socket_cleanup (void);
static void socket_accept  (void);

static void socket_ll_read (HLS *hls);
static void socket_ll_write(HLS *hls);


/* ------------------------------------------------------------ */

static int initflag = 0;	/* Have we been initialized?	*/

static int lsock = -1;		/* Daemon: listening socket	*/
static ACPT_CB acpt_fp = NULL;	/* Call back function pointer	*/

static HLS *hlshead = NULL;	/* Chain of HighLevSock's	*/

#if defined(HAVE_LIBWRAP)
int allow_severity = LOG_INFO;	/* TCP Wrapper log levels	*/
int deny_severity  = LOG_WARNING;
#endif


/* ------------------------------------------------------------ **
**
**	Function......:	socket_cleanup
**
**	Parameters....:	(none)
**
**	Return........:	(none)
**
**	Purpose.......: Clean up the socket related data.
**
** ------------------------------------------------------------ */

static void socket_cleanup(void)
{
	socket_lclose(1);

	while (hlshead != NULL)
		socket_kill(hlshead);
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_listen
**
**	Parameters....:	addr		IP address where to listen
**			port		TCP port where to listen
**
**	Return........:	0=success, -1=failure (EADDRINUSE)
**			Other errors make the program die.
**
**	Purpose.......: Opens a listening port.
**
** ------------------------------------------------------------ */

int socket_listen(u_int32_t addr, u_int16_t port, ACPT_CB func)
{
	struct sockaddr_in saddr;

	if (initflag == 0) {
		atexit(socket_cleanup);
		initflag = 1;
	}

	/*
	** Remember whom to call back for accept
	*/
	acpt_fp = func;

	/*
	** Prepare and open the listening socket
	*/
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_addr.s_addr = htonl(addr);
	saddr.sin_family      = AF_INET;
	saddr.sin_port        = htons(port);

#if defined(COMPILE_DEBUG)
	debug(2, "about to listen: %s:%d",
			inet_ntoa(saddr.sin_addr), (int) port);
#endif

	if ((lsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog_error("can't create listener socket");
		exit(EXIT_FAILURE);
	}
	socket_opts(lsock, SK_LISTEN);

	if (bind(lsock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) {
		if (errno == EADDRINUSE) {
			syslog_write(T_WRN,
				"port %d is in use...", (int) port);
			return -1;
		}
		syslog_error("can't bind to %s:%d",
				inet_ntoa(saddr.sin_addr), (int) port);
		exit(EXIT_FAILURE);
	}
	listen(lsock, SOMAXCONN);
	return 0;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_lclose
**
**	Parameters....:	shut		Call shutdown if non-zero
**
**	Return........:	(none)
**
**	Purpose.......: Close the listening socket.
**
** ------------------------------------------------------------ */

void socket_lclose(int shut)
{
	if (lsock != -1) {
		if (shut)
			shutdown(lsock, 2);
		close(lsock);
		lsock = -1;
	}
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_accept
**
**	Parameters....:	(none)
**
**	Return........:	(none)
**
**	Purpose.......: Accept a new client connection.
**
** ------------------------------------------------------------ */

static void socket_accept(void)
{
	char peer[PEER_LEN];
	struct sockaddr_in saddr;
	int nsock, len;

	/*
	** Let the show begin ...
	*/
	memset(&saddr, 0, sizeof(saddr));
	len = sizeof(saddr);
	nsock = accept(lsock, (struct sockaddr *) &saddr, &len);
	if (nsock < 0) {
		syslog_error("can't accept client");
		return;
	}
	strcpy(peer, inet_ntoa(saddr.sin_addr));

#if defined(COMPILE_DEBUG)
	debug(2, "accepted %d=%s", nsock, peer);
#endif

#if defined(HAVE_LIBWRAP)
	/*
	** Use the TCP Wrapper to control access
	*/
	if (config_bool(NULL, "TCPWrapper", 0)) {
		struct request_info req;

		request_init(&req, RQ_DAEMON, misc_getprog(),
					RQ_FILE, nsock, NULL);
		fromhost(&req);
		if (hosts_access(&req) == 0) {
			close(nsock);
			syslog_write(U_ERR,
				"reject: '%s' (Wrap)", peer);
			return;
		}
	}
#endif

	/*
	** Setup some basic socket options
	*/
	socket_opts(nsock, SK_CONTROL);

	/*
	** Perform user level initialization
	*/
	if (acpt_fp)
		(*acpt_fp)(nsock);
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_init
**
**	Parameters....:	sock		Accepted new socket
**					Can be -1 (e.g. for
**					accepting sockets)
**
**	Return........:	Pointer to newly created Channel
**
**	Purpose.......: Allocate and initialize a new High
**			Level Socket (HLS).
**
** ------------------------------------------------------------ */

HLS *socket_init(int sock)
{
	HLS *hls;

	if (initflag == 0) {
		atexit(socket_cleanup);
		initflag = 1;
	}

	hls = (HLS *) misc_alloc(FL, sizeof(HLS));
	hls->next = hlshead;
	hlshead   = hls;

	if ((hls->sock = sock) != -1) {
		hls->addr = socket_sck2addr(sock,
					REM_END, &(hls->port));
		strcpy(hls->peer, socket_addr2str(hls->addr));
	} else {
		hls->addr = 0;
		hls->port = 0;
		memset(hls->peer, 0, sizeof(hls->peer));
	}

	hls->kill = 0;
	hls->flag = 0;
	hls->ctyp = "???-????";

	hls->wbuf = NULL;
	hls->rbuf = NULL;

#if defined(COMPILE_DEBUG)
	debug(2, "created HLS for %d=%s:%d",
			hls->sock, hls->peer, (int) hls->port);
#endif
	return hls;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_opts
**
**	Parameters....:	sock		Socket to be worked upon
**			kind		SK_... value (a "Macro")
**
**	Return........:	(none)
**
**	Purpose.......: Setup socket options according to the
**			intended use (listen, control, data).
**
** ------------------------------------------------------------ */

void socket_opts(int sock, int kind)
{
#if defined(ENABLE_SO_LINGER)
	struct linger lin;
#endif
	int opt, len;

	opt = 1;
	len = sizeof(opt);
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, len);

#if defined(ENABLE_SO_LINGER)
	if (kind == SK_LISTEN) {
		lin.l_onoff  = 0;
		lin.l_linger = 0;
	} else {
		lin.l_onoff  = 1;
		lin.l_linger = 60;
	}
	len = sizeof(lin);
	setsockopt(sock, SOL_SOCKET, SO_LINGER, &lin, len);
#endif

#if defined(SO_OOBINLINE)
	if (kind == SK_CONTROL) {
		opt = 1;
		len = sizeof(opt);
		setsockopt(sock, SOL_SOCKET, SO_OOBINLINE, &opt, len);
	}
#endif

	if (kind != SK_LISTEN) {
		opt = 1;
		len = sizeof(opt);
		setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, len);
	}

#if defined(IPTOS_THROUGHPUT) && defined(IPTOS_LOWDELAY)
	if (kind == SK_DATA)
		opt = IPTOS_THROUGHPUT;
	else
		opt = IPTOS_LOWDELAY;
	len = sizeof(opt);
	setsockopt(sock, IPPROTO_IP, IP_TOS, &opt, len);
#endif
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_kill
**
**	Parameters....:	hls		Pointer to HighLevSock
**
**	Return........:	(none)
**
**	Purpose.......: Destroy a High Level Socket.
**
** ------------------------------------------------------------ */

void socket_kill(HLS *hls)
{
	HLS *curr, *prev;
	BUF *buf;

	if (hls == NULL)		/* Basic sanity check	*/
		misc_die(FL, "socket_kill: ?hls?");

#if defined(COMPILE_DEBUG)
	debug(2, "deleting HLS %s %d=%s:%d", NIL(hls->ctyp),
			hls->sock, hls->peer, (int) hls->port);
#endif

	/*
	** Find and de-chain the socket
	*/
	for (curr = hlshead, prev = NULL; curr != NULL; ) {
		if (curr == hls) {
			if (prev == NULL)
				hlshead = curr->next;
			else
				prev->next = curr->next;
			break;
		}
		prev = curr;
		curr = curr->next;
	}

	/*
	** Now destroy the socket itself
	*/
	if (hls->sock != -1)
		close(hls->sock);
	for (buf = hls->wbuf; buf != NULL; ) {
		hls->wbuf = buf->next;
		misc_free(FL, buf);
		buf = hls->wbuf;
	}
	for (buf = hls->rbuf; buf != NULL; ) {
		hls->rbuf = buf->next;
		misc_free(FL, buf);
		buf = hls->rbuf;
	}
	misc_free(FL, hls);
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_gets
**
**	Parameters....:	hls		Pointer to HighLevSock
**			ptr		Pointer to read buffer
**			len		Size of buf to be filled
**					(includes trailing zero)
**
**	Return........:	Pointer to filled buf
**
**	Purpose.......: Read one line of input from a HighLevSock.
**			This function waits for complete lines with
**			(CR)LF at the end, which will be discarded.
**
** ------------------------------------------------------------ */

char *socket_gets(HLS *hls, char *ptr, int len)
{
	int cnt;
	BUF *buf;

	if (hls == NULL || ptr == NULL || len <= 0)
		misc_die(FL, "socket_gets: ?hls? ?ptr? ?len?");

	if (hls->rbuf == NULL) {
		errno = 0;
		return NULL;
	}
	len--;		/* Account for the trailing null byte */

	/*
	** Transfer at most one line of data
	*/
	for (buf = hls->rbuf, cnt = 0; buf != NULL && cnt < len; ) {
		if (buf->cur >= buf->len) {
			hls->rbuf = buf->next;
			misc_free(FL, buf);
			buf = hls->rbuf;
			continue;
		}
		if (buf->dat[buf->cur] == '\r')
			break;
		if (buf->dat[buf->cur] == '\n')
			break;
		ptr[cnt++] = buf->dat[buf->cur++];
	}
	ptr[cnt] = '\0';	/* Add the trailing null byte */

	/*
	** Remove possible newline and used up buffer
	*/
	if (buf != NULL) {
		while (buf->cur < buf->len &&
				buf->dat[buf->cur] == '\r')
			buf->cur++;
		while (buf->cur < buf->len &&
				buf->dat[buf->cur] == '\n')
			buf->cur++;
		if (buf->cur >= buf->len) {
			hls->rbuf = buf->next;
			misc_free(FL, buf);
			buf = hls->rbuf;
		}
	}

#if defined(COMPILE_DEBUG)
	debug(2, "gets %s %d=%s: %d bytes '%.128s'%s", hls->ctyp,
		hls->sock, hls->peer, cnt, ptr, (cnt > 128) ? "..." : "");
#endif
	return ptr;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_flag
**
**	Parameters....:	hls		Pointer to HighLevSock
**			flag		Flags to be applied
**
**	Return........:	(none)
**
**	Purpose.......: Set the send() flags for the next write.
**			They will be reset with the write/printf
**			function.
**
** ------------------------------------------------------------ */

void socket_flag(HLS *hls, int flag)
{
	if (hls == NULL)		/* Basic sanity check	*/
		misc_die(FL, "socket_flag: ?hls?");

	/*
	** Store for the next write / printf call
	*/
	hls->flag = flag;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_write
**
**	Parameters....:	hls		Pointer to HighLevSock
**			ptr		Pointer to write buffer
**			len		Number of bytes to write
**
**	Return........:	0=success, -1=failure
**
**	Purpose.......: Write to High Level Socket.
**
** ------------------------------------------------------------ */

int socket_write(HLS *hls, char *ptr, int len)
{
	BUF *buf, *tmp;

	if (hls == NULL || ptr == NULL)
		misc_die(FL, "socket_write: ?hls? ?ptr?");

	if (hls->kill != 0)	/* Socket already doomed?	*/
		return 0;

#if defined(COMPILE_DEBUG)
	debug(2, "write %s %d=%s: %d bytes",
			hls->ctyp, hls->sock, hls->peer, len);
#endif

	/*
	** Allocate a new buffer for the data
	*/
	buf = (BUF *) misc_alloc(FL, sizeof(BUF) + len);
	buf->len = len;
	buf->cur = 0;
	memcpy(buf->dat, ptr, len);

	buf->flg  = hls->flag;
	hls->flag = 0;

	/*
	** Chain the newly filled buffer
	*/
	if (hls->wbuf == NULL)
		hls->wbuf = buf;
	else {
		for (tmp = hls->wbuf; tmp->next; tmp = tmp->next)
			;
		tmp->next = buf;
	}
	buf->next = NULL;

	return 0;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_printf
**
**	Parameters....:	hls		Pointer to HighLevSock
**			fmt		Format string for output
**
**	Return........:	0=success, -1=failure
**
**	Purpose.......: Write to High Level Socket.
**
** ------------------------------------------------------------ */

int socket_printf(HLS *hls, char *fmt, ...)
{
	va_list aptr;
	char str[8192];
	int len;
	BUF *buf, *tmp;

	if (hls == NULL || fmt == NULL)
		misc_die(FL, "socket_printf: ?hls? ?fmt?");

	if (hls->kill != 0)	/* Socket already doomed?	*/
		return 0;

	/*
	** Prepare the new stuff to be written
	*/
	va_start(aptr, fmt);
	vsprintf(str, fmt, aptr);
	va_end(aptr);
	len = strlen(str);

#if defined(COMPILE_DEBUG)
	while (len > 0) {
		if (str[len-1] == '\r' || str[len-1] == '\n')
			len--;
		else
			break;
	}
	if (len > 128) {
		fmt = "...";
		len = 128;
	} else
		fmt = "";
	debug(2, "printf %s %d=%s: %d bytes '%.*s'%s", hls->ctyp,
		hls->sock, hls->peer, strlen(str), len, str, fmt);
	len = strlen(str);
#endif

	/*
	** Allocate a new buffer for the data
	*/
	buf = (BUF *) misc_alloc(FL, sizeof(BUF) + len);
	buf->len = len;
	buf->cur = 0;
	memcpy(buf->dat, str, len);

	buf->flg  = hls->flag;
	hls->flag = 0;

	/*
	** Chain the newly filled buffer
	*/
	if (hls->wbuf == NULL)
		hls->wbuf = buf;
	else {
		for (tmp = hls->wbuf; tmp->next; tmp = tmp->next)
			;
		tmp->next = buf;
	}
	buf->next = NULL;

	return 0;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_file
**
**	Parameters....:	hls		Pointer to HighLevSock
**			file		Name of file to print
**			crlf		0=send LF, 1=send CRLF
**
**	Return........:	0=success, -1=failure
**
**	Purpose.......: Output the contents of a file to the
**			High Level Socket. The line end can
**			either be LF or CRLF.
**
** ------------------------------------------------------------ */

int socket_file(HLS *hls, char *file, int crlf)
{
	char buf[1024], *p, *lend;
	FILE *fp;

	if (hls == NULL || file == NULL)
		misc_die(FL, "socket_file: ?hls? ?file?");

	lend = (crlf ? "\r\n" : "\n");

	if ((fp = fopen(file, "r")) == NULL)
		return -1;
	while (fgets(buf, sizeof(buf), fp) != NULL) {
		if ((p = strchr(buf, '\n')) != NULL)
			*p = '\0';
		socket_printf(hls, "%s%s", buf, lend);
	}
	fclose(fp);

	return 0;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_exec
**
**	Parameters....:	timeout		Maximum seconds to wait
**			close_flag	Pointer to close_flag
**
**	Return........:	0=timeout, 1=activity, -1=error
**
**	Purpose.......: Prepare all relevant sockets, call the
**			select function (main waiting point),
**			and handle the outstanding actions.
**
** ------------------------------------------------------------ */

int socket_exec(int timeout, int *close_flag)
{
	HLS *hls;
	fd_set rfds, wfds;
	int fdcnt, i;
	struct timeval tv;

	/*
	** Prepare the select() input structures
	*/
	fdcnt = -1;
	FD_ZERO(&rfds);
	FD_ZERO(&wfds);

	/*
	** Allow the daemon listening socket to accept
	*/
	if (lsock != -1) {
		fdcnt = lsock;
		FD_SET(lsock, &rfds);
	}

	/*
	** Last but not least walk through the connections
	*/
	for (hls = hlshead; hls != NULL; hls = hls->next) {
		if (hls->sock == -1)
			continue;
		if (hls->kill != 0 && hls->wbuf == NULL) {
			close(hls->sock);
			hls->sock = -1;
#if defined(COMPILE_DEBUG)
			debug(4, "FD_CLR %s", hls->ctyp);
#endif
			/*
			** The following return ensures that
			** killed sockets will be detected.
			*/
			return 1;
		}
		if (hls->sock > fdcnt)
			fdcnt = hls->sock;
		if (hls->wbuf != NULL && hls->peer[0] != '\0') {
			FD_SET(hls->sock, &wfds);
#if defined(COMPILE_DEBUG)
			debug(4, "FD_SET %s for W", hls->ctyp);
#endif
		}
		FD_SET(hls->sock, &rfds);
#if defined(COMPILE_DEBUG)
		debug(4, "FD_SET %s for R", hls->ctyp);
#endif
	}

	/*
	** If not a single descriptor remains, we are doomed
	*/
	if (fdcnt == -1) {
		if (close_flag)
			*close_flag = 1;
		return 1;	/* Return as non-defect situation */
	}

	/*
	** Wait for the next event
	*/
	tv.tv_sec  = timeout;
	tv.tv_usec = 0;
	i = select(fdcnt + 1, &rfds, &wfds, NULL, &tv);
	if (i == 0) {
#if defined(COMPILE_DEBUG)
		debug(2, "select: timeout (%d)", (int) time(NULL));
#endif
		return 0;
	}
	if (i < 0) {
		if (errno == EINTR)
			return 1;
		syslog_error("can't execute select");
		return -1;
	}

	/*
	** Check the various sources of events
	*/
	if (lsock != -1 && FD_ISSET(lsock, &rfds))
		socket_accept();
	for (hls = hlshead; hls != NULL; hls = hls->next) {
		if (hls->sock == -1)
			continue;
		if (FD_ISSET(hls->sock, &rfds))
			socket_ll_read(hls);
		if (hls->sock == -1)	/* May be dead by now */
			continue;
		if (FD_ISSET(hls->sock, &wfds))
			socket_ll_write(hls);
		if (hls->sock == -1)	/* May be dead by now */
			continue;
		if (hls->kill != 0 && hls->wbuf == NULL) {
			close(hls->sock);
			hls->sock = -1;
		}
	}
	return 1;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_ll_read
**
**	Parameters....:	hls		Pointer to HighLevSock
**
**	Return........:	(none)
**
**	Purpose.......: Socket low level read routine. If
**			peer is not set, we are listening.
**
** ------------------------------------------------------------ */

static void socket_ll_read(HLS *hls)
{
	int len, cnt, nsock;
	BUF *buf, *tmp;
	struct sockaddr_in saddr;

	if (hls == NULL)
		misc_die(FL, "socket_ll_read: ?hls?");

	/*
	** If the peer is not (yet) filled, this is a listening
	** socket. In this case we need to accept() the (data)
	** connection (e.g. FTP passive client or active server).
	*/
	if (hls->peer[0] == '\0') {
		memset(&saddr, 0, sizeof(saddr));
		len = sizeof(saddr);
		nsock = accept(hls->sock,
				(struct sockaddr *) &saddr, &len);
		if (nsock < 0) {
			syslog_error("can't accept %s", hls->ctyp);
			shutdown(hls->sock, 2);
			close(hls->sock);
			hls->sock = -1;
			return;
		}
		socket_opts(nsock, SK_DATA);

		/*
		** Update the High Level Socket
		*/
		shutdown(hls->sock, 2);		/* the "acceptor" */
		close(hls->sock);
		hls->sock = nsock;
		hls->addr = socket_sck2addr(nsock,
					REM_END, &(hls->port));
		strcpy(hls->peer, socket_addr2str(hls->addr));
#if defined(COMPILE_DEBUG)
		debug(2, "accept %s (%d) from %s",
				hls->ctyp, hls->sock, hls->peer);
#endif
		return;
	}

	/*
	** Get the number of bytes waiting to be read
	*/
	len = 0;
	if (ioctl(hls->sock, FIONREAD, &len) < 0) {
		syslog_error("can't read from %d=%s",
					hls->sock, hls->peer);
		return;
	}

	/*
	** Check if the socket has been closed
	*/
	if (len == 0) {
		close(hls->sock);
		hls->sock = -1;
#if defined(COMPILE_DEBUG)
		debug(1, "closed: %s %d=%s",
				hls->ctyp, hls->sock, hls->peer);
#endif
		return;
	}

	/*
	** Now read the data that is waiting
	*/
	buf = (BUF *) misc_alloc(FL, sizeof(BUF) + len);
	do
		cnt = recv(hls->sock, buf->dat, len, 0);
	while (cnt == -1 && errno == EINTR);

	if (cnt != len) {
		close(hls->sock);
		hls->sock = -1;
		misc_free(FL, buf);
		syslog_error("can't ll_read: %s %d=%s",
				hls->ctyp, hls->sock, hls->peer);
		return;
	}
	buf->len = len;
	buf->cur = 0;
	buf->flg = 0;

	/*
	** Chain the newly filled buffer
	*/
	if (hls->rbuf == NULL)
		hls->rbuf = buf;
	else {
		for (tmp = hls->rbuf; tmp->next; tmp = tmp->next)
			;
		tmp->next = buf;
	}
	buf->next = NULL;

#if defined(COMPILE_DEBUG)
	debug(3, "ll_read %s %d=%s: %d bytes",
			hls->ctyp, hls->sock, hls->peer, len);
#endif
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_ll_write
**
**	Parameters....:	hls		Pointer to HighLevSock
**
**	Return........:	(none)
**
**	Purpose.......: Socket low level write routine.
**
** ------------------------------------------------------------ */

static void socket_ll_write(HLS *hls)
{
	int cnt, tot;
	BUF *buf;

	if (hls == NULL)
		misc_die(FL, "socket_ll_write: ?hls?");

	/*
	** Try to send as much as possible
	*/
	for (buf = hls->wbuf, tot = 0; buf != NULL; ) {
		do
			cnt = send(hls->sock, buf->dat + buf->cur,
					buf->len - buf->cur, buf->flg);
		while (cnt == -1 && errno == EINTR);

		/*
		** Did we write anything?
		*/
		if (cnt < 0) {
			if (tot == 0) {
				close(hls->sock);
				hls->sock = -1;
				syslog_error("can't write to %d=%s",
						hls->sock, hls->peer);
				return;
			}
			break;	/* At least the first write was ok */
		}

		/*
		** Advance the write pointers
		*/
		tot += cnt;
		if ((buf->cur += cnt) < buf->len)
			break;	/* Partly sent, try again later */

		/*
		** This buffer is done, try and send next one
		*/
		hls->wbuf = buf->next;
		misc_free(FL, buf);
		buf = hls->wbuf;
	}

#if defined(COMPILE_DEBUG)
	debug(3, "ll_write %s %d=%s: %d bytes",
			hls->ctyp, hls->sock, hls->peer, tot);
#endif
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_msgline
**
**	Parameters....:	fmt		String to expand
**
**	Return........:	Pointer to expanded string
**			(Gets overwritten by subsequent calls)
**
**	Purpose.......: Compose a message line, by copying the
**			given string and expanding % escapes.
**
** ------------------------------------------------------------ */

char *socket_msgline(char *fmt)
{
	static char str[1024];
	char tmp[1024];
	size_t i, j;
	time_t now;
	struct tm *t;

	if (fmt == NULL)		/* Basic sanity check	*/
		misc_die(FL, "socket_msgline: ?fmt?");

	time(&now);
	t = localtime(&now);

	for (i = 0; (*fmt != '\0') && (i < (sizeof(str) - 64)); fmt++) {
		if (*fmt != '%') {
			str[i++] = *fmt;
			continue;
		}

		/*
		** Escape alert ...
		*/
		memset(tmp, 0, sizeof(tmp));
		switch (*++fmt) {
			case 'b':
			case 'B':
				strcpy(tmp, misc_getdate());
				break;
			case 'd':
			case 'D':
				sprintf(tmp, "%04d/%02d/%02d",
						t->tm_year + 1900,
						t->tm_mon  + 1,
						t->tm_mday);
				break;
			case 'h':
			case 'H':
				if (gethostname(tmp, sizeof(tmp)) < 0)
					strcpy(tmp, "[unknown host]");
				break;
			case 'n':
			case 'N':
				if (getdomainname(tmp, sizeof(tmp)) < 0)
					strcpy(tmp, "[unknown network]");
				break;
			case 't':
			case 'T':
				sprintf(tmp, "%02d:%02d:%02d",
						t->tm_hour,
						t->tm_min,
						t->tm_sec);
				break;
			case 'v':
			case 'V':
				strcpy(tmp, misc_getvers());
				break;
			case '%':
				tmp[0] = '%';
				break;
			default:
		}
		j = strlen(tmp);
		if ((i + j) < (sizeof(str) - 64)) {
			memcpy(str + i, tmp, j);
			i += j;
		}
	}

	return str;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_d_listen
**
**	Parameters....:	addr		IP address we want to bind
**			lrng		Lower TCP port range limit
**			urng		Upper TCP port range limit
**			phls		Pointer where HLS will go
**			ctyp		Desired comms type identifier
**
**	Return........:	Listening port (0=failure)
**
**	Purpose.......: Open a listening socket, suitable for
**			an additional data connection (e.g. FTP).
**
** ------------------------------------------------------------ */

u_int16_t socket_d_listen(u_int32_t addr, u_int16_t lrng,
			u_int16_t urng, HLS **phls, char *ctyp)
{
	struct sockaddr_in saddr;
	int sock;
	u_int16_t port;

	if (phls == NULL || ctyp == NULL)	/* Sanity check	*/
		misc_die(FL, "socket_d_listen: ?phls? ?ctyp?");

	/*
	** Create the socket and prepare it for binding
	*/
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog_error("can't create %s socket", ctyp);
		exit(EXIT_FAILURE);
	}
	socket_opts(sock, SK_LISTEN);

	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_addr.s_addr = htonl(addr);
	saddr.sin_family      = AF_INET;

	/*
	** Bind the socket, taking care of a given port range.
	** Note: this function covers also lrng = urng = 0 ...
	*/
	for (port = lrng; port <= urng; port++) {
		saddr.sin_port = htons(port);
		if (bind(sock, (struct sockaddr *) &saddr,
					sizeof(saddr)) == 0)
			break;
		if (errno != EADDRINUSE)
			port = urng + 1;
	}
	if (port > urng) {		/* nothing found?	*/
		close(sock);
		return 0;
	}
	listen(sock, 1);

	/*
	** Allocate the corresponding High Level Socket
	*/
	if ((*phls = socket_init(-1)) == NULL)
		misc_die(FL, "socket_d_listen: ?*phls?");
	(*phls)->sock = sock;
	(*phls)->ctyp = ctyp;

	addr = socket_sck2addr(sock, LOC_END, &port);
#if defined(COMPILE_DEBUG)
	debug(2, "listen: %s (fd=%d) %s:%d", (*phls)->ctyp,
		(*phls)->sock, socket_addr2str(addr), (int) port);
#endif
	return port;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_d_connect
**
**	Parameters....:	addr		Destination IP address
**			port		Destination TCP port
**			ladr		Local IP address
**			lrng		Lower TCP port range limit
**			urng		Upper TCP port range limit
**			phls		Pointer where HLS will go
**			ctyp		Desired comms type identifier
**
**	Return........:	Local end of connected port (0=failure)
**
**	Purpose.......: Open a connecting socket, suitable for
**			an additional data connection (e.g. FTP).
**
** ------------------------------------------------------------ */

u_int16_t socket_d_connect(u_int32_t addr, u_int16_t port,
			u_int32_t ladr, u_int16_t lrng,
			u_int16_t urng, HLS **phls, char *ctyp)
{
	struct sockaddr_in saddr;
	int sock;
	u_int16_t lprt;

	if (phls == NULL || ctyp == NULL)	/* Sanity check	*/
		misc_die(FL, "socket_d_connect: ?phls? ?ctyp?");

	/*
	** First of all, get a socket
	*/
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog_error("can't create %s socket", ctyp);
		exit(EXIT_FAILURE);
	}
	socket_opts(sock, SK_DATA);

	/*
	** Bind the socket, taking care of a given port range
	*/
	if (lrng > 0 && urng > 0) {
		memset(&saddr, 0, sizeof(saddr));
		saddr.sin_addr.s_addr = htonl(ladr);
		saddr.sin_family      = AF_INET;

		for (lprt = lrng; lprt <= urng; lprt++) {
#if defined(COMPILE_DEBUG)
			debug(2, "try to con-bind %s to %s:%d", ctyp,
				socket_addr2str(ladr), (int) lprt);
#endif
			saddr.sin_port = htons(lprt);
			if (bind(sock, (struct sockaddr *) &saddr,
						sizeof(saddr)) == 0)
				break;
			if (errno != EADDRINUSE)
				lprt = urng + 1;
		}
		if (lprt > urng) {	/* nothing found? */
			close(sock);
			return 0;
		}
	}

	/*
	** Actually connect the socket
	*/
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_addr.s_addr = htonl(addr);
	saddr.sin_family      = AF_INET;
	saddr.sin_port        = htons(port);

	if (connect(sock, (struct sockaddr *) &saddr,
				sizeof(saddr)) < 0) {
		close(sock);
		return 0;
	}

	/*
	** Allocate the corresponding High Level Socket
	*/
	if ((*phls = socket_init(sock)) == NULL)
		misc_die(FL, "socket_d_connect: ?*phls?");
	(*phls)->ctyp = ctyp;

	(void) socket_sck2addr(sock, LOC_END, &port);
#if defined(COMPILE_DEBUG)
	debug(2, "connect: %s fd=%d", (*phls)->ctyp, (*phls)->sock);
#endif
	return port;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_str2addr
**
**	Parameters....:	name		Host name or address
**			dflt		Default value
**
**	Return........:	Resolved address or default
**
**	Purpose.......: Resolver for DNS names / IP addresses.
**
** ------------------------------------------------------------ */

u_int32_t socket_str2addr(char *name, u_int32_t dflt)
{
	struct hostent *hptr;
	struct in_addr iadr;

#if defined(COMPILE_DEBUG)
	debug(3, "str2addr: in='%.1024s'", NIL(name));
#endif

	if (name == NULL)		/* Basic sanity check	*/
		return dflt;
	memset(&iadr, 0, sizeof(iadr));

	/*
	** Try to interpret as dotted decimal
	*/
	if (*name >= '0' && *name <= '9') {
		if (inet_aton(name, &iadr) == 0)
			return dflt;	/* Can't be valid ...	*/
		return ntohl(iadr.s_addr);
	}

	/*
	** Try to resolve the host as a DNS name
	*/
	if ((hptr = gethostbyname(name)) != NULL) {
		memcpy(&iadr.s_addr, hptr->h_addr, sizeof(iadr.s_addr));
		return (u_int32_t) ntohl(iadr.s_addr);
	}

	/*
	** Well, then return the default
	*/
	return dflt;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_str2port
**
**	Parameters....:	name		Host name or address
**			dflt		Default value
**
**	Return........:	Resolved port or default
**
**	Purpose.......: Resolver for TCP ports.
**
** ------------------------------------------------------------ */

u_int16_t socket_str2port(char *name, u_int16_t dflt)
{
	struct servent *sptr;

	if (name == NULL)		/* Basic sanity check	*/
		return dflt;

	/*
	** Try to interpret as numeric port value
	*/
	if (*name >= '0' && *name <= '9')
		return (u_int16_t) atoi(name);

	/*
	** Try to resolve from /etc/services, NIS, etc.
	*/
	if ((sptr = getservbyname(name, "tcp")) != NULL)
		return (u_int16_t) ntohs(sptr->s_port);

	/*
	** Well, then return the default
	*/
	return dflt;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_addr2str
**
**	Parameters....:	addr		IP address in host order
**
**	Return........:	Dotted decimal string for addr
**
**	Purpose.......: Convert IP address to human readable form.
**			The buffer is reused in subsequent calls,
**			so the caller better move the result away.
**
** ------------------------------------------------------------ */

char *socket_addr2str(u_int32_t addr)
{
	struct in_addr iadr;
	static char str[PEER_LEN];

	memset(&iadr, 0, sizeof(iadr));
	iadr.s_addr = htonl(addr);
	strcpy(str, inet_ntoa(iadr));
	return str;
}


/* ------------------------------------------------------------ **
**
**	Function......:	socket_sck2addr
**
**	Parameters....:	sock		Socket descriptor
**			peer		LOC_END or REM_END
**			port		Pointer to port
**
**	Return........:	IP address in host order
**
**	Purpose.......: Retrieve the IP address of a socket,
**			for either the peer or the local end.
**
** ------------------------------------------------------------ */

u_int32_t socket_sck2addr(int sock, int peer, u_int16_t *port)
{
	struct sockaddr_in saddr;
	int len, r;
	char *s;

	/*
	** Retrieve the actual values
	*/
	memset(&saddr, 0, sizeof(saddr));
	len = sizeof(saddr);
	if (peer == LOC_END) {
		r = getsockname(sock, (struct sockaddr *) &saddr, &len);
		s = "sock";
	} else {
		r = getpeername(sock, (struct sockaddr *) &saddr, &len);
		s = "peer";
	}
	if (r < 0) {
		syslog_error("can't get %sname for socket %d", s, sock);
		exit(EXIT_FAILURE);
	}

	/*
	** Return the port if requested
	*/
	if (port != NULL)
		*port = (u_int16_t) htons(saddr.sin_port);

	/*
	** Return the address part
	*/
	return (u_int32_t) ntohl(saddr.sin_addr.s_addr);
}


/* ------------------------------------------------------------
 * $Log: com-socket.c,v $
 * Revision 1.4  1999/09/21 05:42:28  wiegand
 * syslog / abort review
 *
 * Revision 1.3  1999/09/17 06:32:28  wiegand
 * buffer length and overflow protection review
 *
 * Revision 1.2  1999/09/16 14:26:33  wiegand
 * minor code review and cleanup
 *
 * Revision 1.1  1999/09/15 14:05:38  wiegand
 * initial checkin
 *
 * ------------------------------------------------------------ */

