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