/*
* Copyright (c) 1999-2007 Proofpoint, 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.
*
*/
SM_RCSID("@(#)$Id: listener.c,v 8.127 2013-11-22 20:51:36 ca Exp $")
/*
** listener.c -- threaded network listener
*/
#include "libmilter.h"
#include <sm/errstring.h>
# endif /* NETINET || NETINET6 */
# if SM_CONF_POLL
# endif /* SM_CONF_POLL */
static int L_family;
#if !_FFR_WORKERS_POOL
static void *mi_thread_handle_wrapper __P((void *));
#endif /* !_FFR_WORKERS_POOL */
/*
** MI_OPENSOCKET -- create the socket where this filter and the MTA will meet
**
** Parameters:
** conn -- connection description
** backlog -- listen backlog
** dbg -- debug level
** rmsocket -- if true, try to unlink() the socket first
** (UNIX domain sockets only)
** smfi -- filter structure to use
**
** Return value:
*/
int
char *conn;
int backlog;
int dbg;
bool rmsocket;
{
return MI_FAILURE;
if (ValidSocket(listenfd))
return MI_SUCCESS;
if (dbg > 0)
{
"%s: Opening listen socket on conn %s",
}
(void) smutex_init(&L_Mutex);
(void) smutex_lock(&L_Mutex);
if (!ValidSocket(listenfd))
{
"%s: Unable to create listening socket on conn %s",
(void) smutex_unlock(&L_Mutex);
return MI_FAILURE;
}
if (!SM_FD_OK_SELECT(listenfd))
{
(void) smutex_unlock(&L_Mutex);
return MI_FAILURE;
}
(void) smutex_unlock(&L_Mutex);
return MI_SUCCESS;
}
/*
** MI_MILTEROPEN -- setup socket to listen on
**
** Parameters:
** conn -- connection description
** backlog -- listen backlog
** rmsocket -- if true, try to unlink() the socket first
** (UNIX domain sockets only)
** name -- name for logging
**
** Returns:
** socket upon success, error code otherwise.
**
** Side effect:
** sets sockpath if UNIX socket.
*/
#if NETUNIX
#endif /* NETUNIX */
static socket_t
char *conn;
int backlog;
bool rmsocket;
char *name;
{
int fdflags;
char *p;
char *colon;
char *at;
{
name);
return INVALID_SOCKET;
}
/* protocol:filename or protocol:port@host */
p = conn;
{
*colon = '\0';
if (*p == '\0')
{
#if NETUNIX
/* default to AF_UNIX */
L_socksize = sizeof (struct sockaddr_un);
#else /* NETUNIX */
# if NETINET
/* default to AF_INET */
# else /* NETINET */
# if NETINET6
/* default to AF_INET6 */
# else /* NETINET6 */
/* no protocols available */
"%s: no valid socket protocols available",
name);
return INVALID_SOCKET;
# endif /* NETINET6 */
# endif /* NETINET */
#endif /* NETUNIX */
}
#if NETUNIX
else if (strcasecmp(p, "unix") == 0 ||
strcasecmp(p, "local") == 0)
{
L_socksize = sizeof (struct sockaddr_un);
}
#endif /* NETUNIX */
#if NETINET
else if (strcasecmp(p, "inet") == 0)
{
}
#endif /* NETINET */
#if NETINET6
else if (strcasecmp(p, "inet6") == 0)
{
}
#endif /* NETINET6 */
else
{
name, p);
return INVALID_SOCKET;
}
*colon++ = ':';
}
else
{
colon = p;
#if NETUNIX
/* default to AF_UNIX */
L_socksize = sizeof (struct sockaddr_un);
#else /* NETUNIX */
# if NETINET
/* default to AF_INET */
# else /* NETINET */
# if NETINET6
/* default to AF_INET6 */
# else /* NETINET6 */
name, p);
return INVALID_SOCKET;
# endif /* NETINET6 */
# endif /* NETINET */
#endif /* NETUNIX */
}
#if NETUNIX
{
# if 0
# endif /* 0 */
{
return INVALID_SOCKET;
}
# if 0
/* if not safe, don't create */
if (errno != 0)
{
"%s: UNIX socket name %s unsafe",
return INVALID_SOCKET;
}
# endif /* 0 */
}
#endif /* NETUNIX */
if (
# if NETINET
# endif /* NETINET */
||
# endif /* NETINET && NETINET6 */
# if NETINET6
# endif /* NETINET6 */
)
{
unsigned short port;
/* Parse port@host */
{
{
# if NETINET
case AF_INET:
break;
# endif /* NETINET */
# if NETINET6
case AF_INET6:
break;
# endif /* NETINET6 */
}
}
else
*at = '\0';
else
{
# ifdef NO_GETSERVBYNAME
return INVALID_SOCKET;
# else /* NO_GETSERVBYNAME */
{
"%s: unknown port name %s",
return INVALID_SOCKET;
}
# endif /* NO_GETSERVBYNAME */
}
{
*at++ = '@';
if (*at == '[')
{
char *end;
{
bool found = false;
# if NETINET
# endif /* NETINET */
# if NETINET6
# endif /* NETINET6 */
*end = '\0';
# if NETINET
{
found = true;
}
# endif /* NETINET */
# if NETINET6
{
found = true;
}
# endif /* NETINET6 */
*end = ']';
if (!found)
{
"%s: Invalid numeric domain spec \"%s\"",
return INVALID_SOCKET;
}
}
else
{
"%s: Invalid numeric domain spec \"%s\"",
return INVALID_SOCKET;
}
}
else
{
{
"%s: Unknown host name %s",
return INVALID_SOCKET;
}
switch (hp->h_addrtype)
{
# if NETINET
case AF_INET:
INADDRSZ);
break;
# endif /* NETINET */
# if NETINET6
case AF_INET6:
break;
# endif /* NETINET6 */
default:
"%s: Unknown protocol for %s (%d)",
return INVALID_SOCKET;
}
# if NETINET6
# endif /* NETINET6 */
}
}
else
{
{
# if NETINET
case AF_INET:
break;
# endif /* NETINET */
# if NETINET6
case AF_INET6:
break;
# endif /* NETINET6 */
}
}
}
#endif /* NETINET || NETINET6 */
if (!ValidSocket(sock))
{
"%s: Unable to create new socket: %s",
return INVALID_SOCKET;
}
{
"%s: Unable to set close-on-exec: %s", name,
(void) closesocket(sock);
return INVALID_SOCKET;
}
if (
#if NETUNIX
#endif /* NETUNIX */
sizeof(sockopt)) == -1)
{
"%s: set reuseaddr failed (%s)", name,
(void) closesocket(sock);
return INVALID_SOCKET;
}
#if NETUNIX
{
struct stat s;
{
{
"%s: Unable to stat() %s: %s",
(void) closesocket(sock);
return INVALID_SOCKET;
}
}
{
"%s: %s is not a UNIX domain socket",
(void) closesocket(sock);
return INVALID_SOCKET;
}
{
"%s: Unable to remove %s: %s",
(void) closesocket(sock);
return INVALID_SOCKET;
}
}
#endif /* NETUNIX */
{
"%s: Unable to bind to port %s: %s",
(void) closesocket(sock);
return INVALID_SOCKET;
}
{
"%s: listen call failed: %s", name,
(void) closesocket(sock);
return INVALID_SOCKET;
}
#if NETUNIX
{
/*
** Set global variable sockpath so the UNIX socket can be
** unlink()ed at exit.
*/
else
{
"%s: can't malloc(%d) for sockpath: %s",
(void) closesocket(sock);
return INVALID_SOCKET;
}
}
#endif /* NETUNIX */
return sock;
}
#if !_FFR_WORKERS_POOL
/*
** MI_THREAD_HANDLE_WRAPPER -- small wrapper to handle session
**
** Parameters:
** arg -- argument to pass to mi_handle_session()
**
** Returns:
** results from mi_handle_session()
*/
static void *
void *arg;
{
/*
** Note: on some systems this generates a compiler warning:
** cast to pointer from integer of different size
** You can safely ignore this warning as the result of this function
** is not used anywhere.
*/
return (void *) mi_handle_session(arg);
}
#endif /* _FFR_WORKERS_POOL */
/*
** MI_CLOSENER -- close listen socket
**
** Parameters:
** none.
**
** Returns:
** none.
*/
void
{
(void) smutex_lock(&L_Mutex);
if (ValidSocket(listenfd))
{
#if NETUNIX
bool removable;
geteuid() != 0 &&
# ifdef S_ISSOCK
# endif /* S_ISSOCK */
);
#endif /* NETUNIX */
(void) closesocket(listenfd);
#if NETUNIX
/* XXX sleep() some time before doing this? */
{
if (removable &&
# ifdef S_ISSOCK
# endif /* S_ISSOCK */
)
&&
# ifdef S_ISSOCK
# endif /* S_ISSOCK */
))
}
#endif /* NETUNIX */
}
(void) smutex_unlock(&L_Mutex);
}
/*
** MI_LISTENER -- Generic listener harness
**
** Open up listen port
** Wait for connections
**
** Parameters:
** conn -- connection description
** dbg -- debug level
** smfi -- filter structure to use
** backlog -- listen queue backlog size
**
** Returns:
** MI_SUCCESS -- Exited normally
** (session finished or we were told to exit)
** MI_FAILURE -- Network initialization failed.
*/
/*
** Solaris 2.6, perhaps others, gets an internal threads library panic
** when sleep() is used:
**
** thread_create() failed, returned 11 (EINVAL)
** co_enable, thr_create() returned error = 24
** libthread panic: co_enable failed (PID: 17793 LWP 1)
** stacktrace:
** ef526b10
** ef52646c
** ef534cbc
** 156a4
** 14644
** 1413c
** 135e0
** 0
*/
# define MI_SLEEP(s) \
{ \
int rs = 0; \
\
{ \
for (;;) \
{ \
continue; \
if (rs != 0) \
{ \
"MI_SLEEP(): select() returned non-zero result %d, errno = %d", \
} \
break; \
} \
} \
}
#else /* BROKEN_PTHREAD_SLEEP */
#endif /* BROKEN_PTHREAD_SLEEP */
int
char *conn;
int dbg;
int backlog;
{
#if _FFR_DUP_FD
#endif /* _FFR_DUP_FD */
int r, mistop;
int save_errno = 0;
#if !_FFR_WORKERS_POOL
#endif /* !_FFR_WORKERS_POOL */
return MI_FAILURE;
if (mi_pool_controller_init() == MI_FAILURE)
return MI_FAILURE;
#endif /* _FFR_WORKERS_POOL */
clilen = L_socksize;
{
(void) smutex_lock(&L_Mutex);
if (!ValidSocket(listenfd))
{
ret = MI_FAILURE;
"%s: listenfd=%d corrupted, terminating, errno=%d",
(void) smutex_unlock(&L_Mutex);
break;
}
/* select on interface ports */
if (r == 0) /* timeout */
{
(void) smutex_unlock(&L_Mutex);
continue; /* just check mi_stop() */
}
if (r < 0)
{
save_errno = errno;
(void) smutex_unlock(&L_Mutex);
if (save_errno == EINTR)
continue;
scnt++;
"%s: %s() failed (%s), %s",
if (scnt >= MAX_FAILS_S)
{
ret = MI_FAILURE;
break;
}
continue;
}
{
/* some error: just stop for now... */
ret = MI_FAILURE;
(void) smutex_unlock(&L_Mutex);
"%s: %s() returned exception for socket, abort",
break;
}
scnt = 0; /* reset error counter for select() */
&clilen);
save_errno = errno;
(void) smutex_unlock(&L_Mutex);
/*
** If remote side closes before accept() finishes,
** sockaddr might not be fully filled in.
*/
if (ValidSocket(connfd) &&
(clilen == 0 ||
# ifdef BSD4_4_SOCKADDR
# endif /* BSD4_4_SOCKADDR */
{
(void) closesocket(connfd);
save_errno = EINVAL;
}
/* check if acceptable for select() */
{
(void) closesocket(connfd);
save_errno = ERANGE;
}
if (!ValidSocket(connfd))
{
if (save_errno == EINTR
#ifdef EAGAIN
|| save_errno == EAGAIN
#endif /* EAGAIN */
#ifdef ECONNABORTED
|| save_errno == ECONNABORTED
#endif /* ECONNABORTED */
#ifdef EMFILE
|| save_errno == EMFILE
#endif /* EMFILE */
#ifdef ENFILE
|| save_errno == ENFILE
#endif /* ENFILE */
#ifdef ENOBUFS
|| save_errno == ENOBUFS
#endif /* ENOBUFS */
#ifdef ENOMEM
|| save_errno == ENOMEM
#endif /* ENOMEM */
#ifdef ENOSR
|| save_errno == ENOSR
#endif /* ENOSR */
#ifdef EWOULDBLOCK
|| save_errno == EWOULDBLOCK
#endif /* EWOULDBLOCK */
)
continue;
acnt++;
"%s: accept() returned invalid socket (%s), %s",
if (acnt >= MAX_FAILS_A)
{
ret = MI_FAILURE;
break;
}
continue;
}
acnt = 0; /* reset error counter for accept() */
#if _FFR_DUP_FD
{
}
#endif /* _FFR_DUP_FD */
{
"%s: set keepalive failed (%s)",
/* XXX: continue? */
}
{
(void) closesocket(connfd);
mcnt++;
if (mcnt >= MAX_FAILS_M)
{
ret = MI_FAILURE;
break;
}
continue;
}
mcnt = 0; /* reset error counter for malloc() */
#else /* _FFR_WORKERS_POOL */
# define LOG_CRT_FAIL "%s: thread_create() failed: %d, %s"
if ((r = thread_create(&thread_id,
(void *) ctx)) != 0)
#endif /* _FFR_WORKERS_POOL */
{
tcnt++;
(void) closesocket(connfd);
if (tcnt >= MAX_FAILS_T)
{
ret = MI_FAILURE;
break;
}
continue;
}
tcnt = 0;
}
if (ret != MI_SUCCESS)
else
{
if (mistop != MILTER_CONT)
mi_closener();
}
(void) smutex_destroy(&L_Mutex);
return ret;
}