/*
 * 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.
 */

/* XXX HACK XXX */
#define XP_UNIX	1

#include "sm/generic.h"
SM_RCSID("@(#)$Id: smi-net.c,v 1.15 2005/07/12 23:23:36 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/fcntl.h"

#include <stdio.h>
#include <ctype.h>

#include "smi-net.h"

static bool net_initialized = false;

bool
net_is_ip_addr(const char * str)
{
	int i = 0, oct = 1;

	SM_ASSERT(str);

	if (strlen(str) > 15)
		/* Too many characters */
		return false;

	for (i = 0; str[i] != '\0'; i++)
	{
		if (! (isdigit(str[i]) || str[i] == '.'))
			return false;
		if (str[i] == '.')
		{
			if (i > 0 && str[i-1] == '.')
				return false;
			else
				oct += 1;
		}
	}

	if (oct != 4)
		return false;

	if (! isdigit(str[i-1]))
		return false;

	return true;
}

sm_ret_T
net_startup()
{
#ifdef XP_NT
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD( 2, 0 );
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
		return FAILURE;
#endif /* XP_NT */

#ifdef XP_UNIX
	/* If a client closes a connection, we definitly don't want to die */
	signal(SIGPIPE, SIG_IGN);
#endif /* XP_UNIX */

	net_initialized = true;
	return SM_SUCCESS;
}

void
net_shutdown()
{
#ifdef XP_NT
	WSACleanup();
#endif /* XP_NT */

	net_initialized = false;
}

int
netserverlistenaddr(sockaddr_T *my_addr, int addrlen, int backlog)
{
	int listenfd;
	int sockopt;

	SM_ASSERT(my_addr != NULL);
	SM_ASSERT(backlog > 0);

	sockopt = 1;
	listenfd = INVALID_SOCKET;
	SM_ASSERT(net_initialized);

	/* Open listen port */
	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
		return sm_err_perm(errno);

	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt,
		       sizeof(sockopt)) == -1)
	{
		closesocket(listenfd);
		return sm_err_perm(errno);
	}

	if (bind(listenfd, (struct sockaddr *) my_addr, addrlen) == -1)
	{
		closesocket(listenfd);
		return sm_err_perm(errno);
	}

	if (listen(listenfd, backlog) == -1)
	{
		closesocket(listenfd);
		return sm_err_perm(errno);
	}

	return listenfd;
}

int
netserverlisten(char *ip, int port, int backlog)
{
	struct sockaddr_in servaddr;

	SM_ASSERT(port > 0);

	sm_memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	if (ip == NULL || *ip == '\0')
		ip = "127.0.0.1";
	servaddr.sin_addr.s_addr = inet_addr(ip);
	servaddr.sin_port = htons(port);

	return netserverlistenaddr((sockaddr_T *)&servaddr,
				sizeof(servaddr), backlog);
}

int
net_server_accept(int listenfd, struct sockaddr *addr, sockaddr_len_T *addrlen)
{
	int connfd;
	int sockopt;

	SM_ASSERT(listenfd >= 0);
	SM_ASSERT(addr != NULL);
	SM_ASSERT(addrlen != NULL);

	sockopt = 1;
	if ((connfd = accept(listenfd, addr, addrlen)) == -1)
	{
		if (errno != EAGAIN)
			return sm_err_perm(errno);
	}
	else
	{
		if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE,
			       (void *) &sockopt, sizeof(sockopt)) == -1)
			return sm_err_perm(errno);
	}
	return connfd;
}

int
net_socket_create(int fd)
{
	int sock;
#ifdef XP_UNIX
	int flags;
#endif /* XP_UNIX */
#ifdef XP_NT
	ulong ioctlarg = 1;
#endif /* XP_NT */

	SM_ASSERT(fd >= 0);

	sock = fd;

#ifdef XP_UNIX
	if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
		return sm_err_perm(errno);
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
		return sm_err_perm(errno);
#endif /* XP_UNIX */

#ifdef XP_NT
	if (ioctlsocket(fd, FIONBIO, &ioctlarg) == SOCKET_ERROR)
		return sm_err_perm(errno);
#endif /* XP_NT */

	return sock;
}

void
net_socket_close(int sock)
{
	closesocket(sock);
}

#if 0
/*
**  net_daemons_detach borrows from Stevens Networking Vol.1 pp 336
**  (Chapter 12, Daemon Processes and the inetd Superserver),
**  and from the MTA source main.c:disconnect()
*/

retcode
net_daemon_detach()
{

#ifdef XP_UNIX
	int fd;
	pid_t pid;

	/* First fork off and terminate the parent */
	pid = fork();
	if (pid == -1)
		return sm_err_perm(errno);
	else if (pid > 0)
	{
		/* We're the parent, exit */
		exit (EX_OK);
	}

	/* Become session leader */
	if (setsid() == -1)
		return sm_err_perm(errno);

	/*
	**  Ignore SIGHUP and fork again.  From Steven's page 336:
	**  "The purpose of this second fork is to guarantee that the daemon
	**  cannot automatically acquire a controlling terminal should it
	**  open a terminal device in the future.  Under SVR4, when a session
	**  leader without a controlling terminal opens a terminal device
	**  (that is not currently some other session's controlling terminal),
	**  the terminal becomes the controlling terminal of the session
	**  leader.  But by calling fork a second time we guarantee that the
	**  second child is no longer a session leader, so it cannot acquire
	**  a controlling terminal.  We must ignore SIGHUP because when the
	**  session leader terminates (the first child), all processes in the
	**  session (our second child) are sent the SIGHUP signal."
	*/

	signal(SIGHUP, SIG_IGN);
	pid = fork();
	if (pid == -1)
		return sm_err_perm(errno);
	else if (pid > 0)
	{
		/* We're the parent (err, 1st child), exit */
		exit (EX_OK);
	}

	/*
	**  chdir to root filesystem so that cores are in a known location
	**  and so that we don't prevent any filesystems from being unmounted
	*/
	if (chdir("/") == -1)
		return sm_err_perm(errno);

	/* Clear our umask */
	umask(0);

	/* Reopen stdin, stdout, and stderr to /dev/null */
	freopen("/dev/null", "r", stdin);
	fd = open("/dev/null", O_WRONLY, 0666);
	(void) fflush(stdout);
	dup2(fd, STDOUT_FILENO);
	dup2(fd, STDERR_FILENO);
	close(fd);

#endif /* XP_UNIX */

	return SM_SUCCESS;
}
#endif /* 0 */

int
netclientconnect(char *ip, int port)
{
	int sockfd;
	struct sockaddr_in servaddr;
	ulong hostaddr;

	SM_ASSERT(ip != NULL);
	SM_ASSERT(port > 0);

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		return -1;

	sm_memset(&servaddr, '\0', sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	if ((hostaddr = inet_addr(ip)) == INADDR_NONE)
		return -1;
	sm_memcpy(&servaddr.sin_addr, &hostaddr, sizeof(hostaddr));

	if ((connect(sockfd, (struct sockaddr *) &servaddr,
		     sizeof(servaddr))) == -1)
		return -1;

	return sockfd;
}

int
net_client_connect_hostname(char *host, int port)
{
	int sockfd;
	struct sockaddr_in servaddr;
	struct hostent * hostptr;

	SM_ASSERT(host != NULL);
	SM_ASSERT(port > 0);

	if ((hostptr = gethostbyname(host)) == NULL)
		return sm_err_perm(errno);

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		return sm_err_perm(errno);

	while (*(hostptr->h_addr_list) && hostptr->h_addrtype != AF_INET)
		++hostptr->h_addr_list;
	if (*(hostptr->h_addr_list) == NULL)
		return sm_err_perm(errno);

	sm_memset(&servaddr, '\0', sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	sm_memcpy(&servaddr.sin_addr, *(hostptr->h_addr_list), hostptr->h_length);

	if ((connect(sockfd, (struct sockaddr *) &servaddr,
		     sizeof(servaddr))) == -1)
		return -1;

	return sockfd;
}

#if 0
/*
**  NT does send so that it blocks until all data is away, Unix doesn't, so
**  we have to check the return on Unix.
*/

ssize_t
net_out(int outfd, const uchar *src, size_t len)
{
	ssize_t l, ret;

	SM_ASSERT(outfd >= 0);
	SM_ASSERT(src != NULL);

	l = 0;
#ifdef XP_UNIX
	/*
	**  Unix optimization:
	**  Since Unix properly notices sends to closed sockets (vs NT
	**  which blocks forever on a closed socket).
	**  For Unix send it, notice if it's block, and make sure it all
	**  went (this cuts the code down by 75% on 75% of the calls).
	*/

	ret = send(outfd, (char *) src, len, 0);

	if (ret == -1 && errno == EPIPE)
	{
		/* Connection is closed */
		return -1;
	}

	if (ret >= 0)
		l += ret;
#endif /* XP_UNIX */

	while (l < len)
	{
/* XXX use write_wait() */
		/* Use select to wait for the socket to open up */
		fd_set writefds;

		SM_ASSERT(outfd < FD_SETSIZE);
		FD_ZERO(&writefds);
		FD_SET(outfd, &writefds);

		/* use poll if outfd too large?? */
		if (select(outfd + 1, NULL, &writefds, NULL, NULL) != 1)
			return sm_err_perm(errno);

		/* It should now be clear to write some more */
		ret = send(outfd, (char *) (src + l), len - l, 0);
		if (ret == -1)
			return sm_err_perm(errno);

		if (ret > 0)
			l += ret;
	}

	SM_ASSERT(l != -1);
	return l;
}

/*
**  net_voutf is "good enough" for now but needs to be replaced in the future
*/

static size_t
net_voutf(outfd, format, ap)
	int outfd;
	const char *format;
	va_list ap;
{
	size_t len;

	/* XXX */
	char buf[1024];

	SM_ASSERT(outfd >= 0);
	SM_ASSERT(format != NULL);

	if ((len = vsnprintf(buf, sizeof(buf), format, ap)) >= sizeof(buf))
		return sm_err_perm(EINVAL);

	return net_out(outfd, buf, len);
}

size_t
#ifdef __STDC__
net_outf(int outfd, const char *format, ...)
#else /* __STDC__ */
net_outf(outfd, format, va_alist)
	int outfd;
	const char *format;
	va_dcl
#endif /* __STDC__ */
{
	size_t len;
	va_list ap;

#ifdef __STDC__
	va_start(ap, format);
#else /* __STDC__ */
	va_start(va);
#endif /* __STDC__ */
	len = net_voutf(outfd, format, ap);
	va_end(ap);

	return len;
}

size_t
net_socket_out(sock, src, len)
	NetSocket sock;
	const char *src;
	size_t len;
{
	return net_out(sock->fd, src, len);
}

/*
**  net_socket_voutf is "good enough" for now but needs to be
**  replaced in the future
*/

static size_t
net_socket_voutf(sock, format, ap)
	NetSocket sock;
	const char *format;
	va_list ap;
{
	size_t len;

	/* XXX */
	char buf[1024];

	SM_ASSERT(sock != NULL);
	SM_ASSERT(format != NULL);

	if ((len = vsnprintf(buf, sizeof(buf), format, ap)) >= sizeof(buf))
		return sm_err_perm(EINVAL);

	return net_socket_out(sock, buf, len);
}

size_t
#ifdef __STDC__
net_socket_outf(NetSocket sock, const char *format, ...)
#else /* __STDC__ */
net_socket_outf(sock, format, va_alist)
	NetSocket sock;
	const char *format;
	va_dcl
#endif /* __STDC__ */
{
	size_t len;
	va_list ap;

#ifdef __STDC__
	va_start(ap, format);
#else /* __STDC__ */
	va_start(va);
#endif /* __STDC__ */
	len = net_socket_voutf(sock, format, ap);
	va_end(ap);

	return len;
}
#endif /* 0 */


syntax highlighted by Code2HTML, v. 0.9.1