/*
 * Copyright (c) 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: socklisten.c,v 1.2 2005/09/01 23:38:48 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/pwd.h"
#include "sm/grp.h"
#include "sm/fcntl.h"
#include "sm/net.h"
#include "sm/sockcnf.h"
#include "sm/socket.h"

/*
**  SOCK_LISTEN -- Create a socket, bind, and listen.
**
**	Parameters:
**		sock -- address to bind to.
**		backlog -- Backlog of connections to accept (man listen(2))
**		pfd -- (pointer to) file descriptor (output)
**
**	Returns:
**		usual error code
*/

sm_ret_T
sock_listen(sockspec_P sock, int backlog, int *pfd)
{
	sm_ret_T ret;
	int listenfd, sockopt, r, save_errno;
	mode_t mode;
	sockaddr_len_T sock_size;
	sm_sockaddr_T sm_sockaddr;

	SM_ASSERT(sock != NULL);
	SM_ASSERT(pfd != NULL);
	SM_ASSERT(backlog > 0);

	sm_memset(&sm_sockaddr, 0, sizeof(sm_sockaddr));
	mode = (mode_t) -1;
	listenfd = INVALID_SOCKET;
	switch (sock->sckspc_type)
	{
	  case SOCK_TYPE_INET:
		sm_sockaddr.sin.sin_family = AF_INET;
		sm_sockaddr.sin.sin_addr.s_addr = sock->sock_inet.inetsckspc_addr;
		sm_sockaddr.sin.sin_port = sock->sock_inet.inetsckspc_port;
		sock_size = sizeof(sm_sockaddr.sin);
		break;

	  case SOCK_TYPE_UNIX:
		{
		size_t len;

		sm_sockaddr.sunix.sun_family = AF_UNIX;
		len = strlcpy(sm_sockaddr.sunix.sun_path,
			sock->sock_unix.unixsckspc_path,
			sizeof(sm_sockaddr.sunix.sun_path));
		if (len <= 0 || len >= sizeof(sm_sockaddr.sunix.sun_path))
			return sm_error_perm(SM_EM_NET, SM_E_2BIG);
		sock_size = sizeof(sm_sockaddr.sunix);
#if HAVE_SOCK_UN_SUN_LEN
		sm_sockaddr.sunix.sun_len = strlen(sock->sock_unix.unixsckspc_path);
#endif

		/* unlink socket (might not exist) */
		(void) unlink(sock->sock_unix.unixsckspc_path);
		break;
		}
	  default:
		return sm_error_perm(SM_EM_NET, EINVAL);
	}

	sockopt = 1;
	listenfd = socket(sm_sockaddr.sa.sa_family, SOCK_STREAM, 0);
	if (listenfd == INVALID_SOCKET)
		return sm_error_perm(SM_EM_NET, errno);
	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt,
		       sizeof(sockopt)) == -1)
	{
		ret = sm_error_perm(SM_EM_NET, errno);
		goto error;
	}

	if (sm_sockaddr.sa.sa_family == AF_UNIX)
		mode = umask(sock->sock_unix.unixsckspc_umask);
	r = bind(listenfd, (struct sockaddr *) &sm_sockaddr, sock_size);
	save_errno = errno;
	if (mode != (mode_t) -1 && sm_sockaddr.sa.sa_family == AF_UNIX)
		(void) umask(mode);
	if (r == -1)
	{
		ret = sm_error_perm(SM_EM_NET, save_errno);
		goto error;
	}
	if (sm_sockaddr.sa.sa_family == AF_UNIX)
	{
		struct passwd  *pwd;
		struct group   *grp;

		/*
		**  Some OS (e.g., HP UX) don't obey umask for bind().
		**  Of course this has a race condition, hence the socket
		**  MUST be in a "safe" directory (which currently is NOT
		**  checked).
		*/

		mode = 0777 ^ sock->sock_unix.unixsckspc_umask;
		r = chmod(sm_sockaddr.sunix.sun_path, mode);
		save_errno = errno;
		if (r != 0)
		{
			ret = sm_error_perm(SM_EM_NET, save_errno);
			goto error;
/*
			syslog(LOG_ERR,
				"%s: %s: chmod(%d) failed, %d",
				sep->se_prg,
				sm_sockaddr.sunix.sun_path,
				(int) mode, errno);
*/
		}

		pwd = NULL;
		if (sock->sock_unix.unixsckspc_user != NULL &&
		    *sock->sock_unix.unixsckspc_user != '\0' &&
		    (pwd = getpwnam(sock->sock_unix.unixsckspc_user)) == NULL)
		{
			ret = sm_error_perm(SM_EM_NET, EINVAL);
			goto error;
		}
		if (pwd != NULL)
		{
			grp = NULL;
			if (sock->sock_unix.unixsckspc_group != NULL &&
			    *sock->sock_unix.unixsckspc_group != '\0' &&
			    (grp = getgrnam(sock->sock_unix.unixsckspc_group))
				== NULL)
			{
				ret = sm_error_perm(SM_EM_NET, EINVAL);
				goto error;
			}
			if (grp != NULL)
				pwd->pw_gid = grp->gr_gid;

			r = chown(sm_sockaddr.sunix.sun_path,
				pwd->pw_uid, pwd->pw_gid);
			save_errno = errno;
			if (r != 0)
			{
				ret = sm_error_perm(SM_EM_NET, save_errno);
				goto error;
			}
		}
	}

	if (listen(listenfd, backlog) == -1)
	{
		ret = sm_error_perm(SM_EM_NET, errno);
		goto error;
	}
	*pfd = listenfd;
	return SM_SUCCESS;

  error:
	if (listenfd != INVALID_SOCKET)
		closesocket(listenfd);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1