root/usr/src/cmd/sendmail/libmilter/listener.c
/*
 *  Copyright (c) 1999-2007 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/gen.h>
SM_RCSID("@(#)$Id: listener.c,v 8.126 2009/12/16 16:40:23 ca Exp $")

/*
**  listener.c -- threaded network listener
*/

#include "libmilter.h"
#include <sm/errstring.h>

#include <sys/types.h>
#include <sys/stat.h>


# if NETINET || NETINET6
#  include <arpa/inet.h>
# endif /* NETINET || NETINET6 */
# if SM_CONF_POLL
#  undef SM_FD_OK_SELECT
#  define SM_FD_OK_SELECT(fd)           true
# endif /* SM_CONF_POLL */

static smutex_t L_Mutex;
static int L_family;
static SOCKADDR_LEN_T L_socksize;
static socket_t listenfd = INVALID_SOCKET;

static socket_t mi_milteropen __P((char *, int, bool, char *));
#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:
**              MI_SUCCESS/MI_FAILURE
*/

int
mi_opensocket(conn, backlog, dbg, rmsocket, smfi)
        char *conn;
        int backlog;
        int dbg;
        bool rmsocket;
        smfiDesc_ptr smfi;
{
        if (smfi == NULL || conn == NULL)
                return MI_FAILURE;

        if (ValidSocket(listenfd))
                return MI_SUCCESS;

        if (dbg > 0)
        {
                smi_log(SMI_LOG_DEBUG,
                        "%s: Opening listen socket on conn %s",
                        smfi->xxfi_name, conn);
        }
        (void) smutex_init(&L_Mutex);
        (void) smutex_lock(&L_Mutex);
        listenfd = mi_milteropen(conn, backlog, rmsocket, smfi->xxfi_name);
        if (!ValidSocket(listenfd))
        {
                smi_log(SMI_LOG_FATAL,
                        "%s: Unable to create listening socket on conn %s",
                        smfi->xxfi_name, conn);
                (void) smutex_unlock(&L_Mutex);
                return MI_FAILURE;
        }
        if (!SM_FD_OK_SELECT(listenfd))
        {
                smi_log(SMI_LOG_ERR, "%s: fd %d is larger than FD_SETSIZE %d",
                        smfi->xxfi_name, listenfd, FD_SETSIZE);
                (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
static char     *sockpath = NULL;
#endif /* NETUNIX */

static socket_t
mi_milteropen(conn, backlog, rmsocket, name)
        char *conn;
        int backlog;
        bool rmsocket;
        char *name;
{
        socket_t sock;
        int sockopt = 1;
        int fdflags;
        size_t len = 0;
        char *p;
        char *colon;
        char *at;
        SOCKADDR addr;

        if (conn == NULL || conn[0] == '\0')
        {
                smi_log(SMI_LOG_ERR, "%s: empty or missing socket information",
                        name);
                return INVALID_SOCKET;
        }
        (void) memset(&addr, '\0', sizeof addr);

        /* protocol:filename or protocol:port@host */
        p = conn;
        colon = strchr(p, ':');
        if (colon != NULL)
        {
                *colon = '\0';

                if (*p == '\0')
                {
#if NETUNIX
                        /* default to AF_UNIX */
                        addr.sa.sa_family = AF_UNIX;
                        L_socksize = sizeof (struct sockaddr_un);
#else /* NETUNIX */
# if NETINET
                        /* default to AF_INET */
                        addr.sa.sa_family = AF_INET;
                        L_socksize = sizeof addr.sin;
# else /* NETINET */
#  if NETINET6
                        /* default to AF_INET6 */
                        addr.sa.sa_family = AF_INET6;
                        L_socksize = sizeof addr.sin6;
#  else /* NETINET6 */
                        /* no protocols available */
                        smi_log(SMI_LOG_ERR,
                                "%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)
                {
                        addr.sa.sa_family = AF_UNIX;
                        L_socksize = sizeof (struct sockaddr_un);
                }
#endif /* NETUNIX */
#if NETINET
                else if (strcasecmp(p, "inet") == 0)
                {
                        addr.sa.sa_family = AF_INET;
                        L_socksize = sizeof addr.sin;
                }
#endif /* NETINET */
#if NETINET6
                else if (strcasecmp(p, "inet6") == 0)
                {
                        addr.sa.sa_family = AF_INET6;
                        L_socksize = sizeof addr.sin6;
                }
#endif /* NETINET6 */
                else
                {
                        smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
                                name, p);
                        return INVALID_SOCKET;
                }
                *colon++ = ':';
        }
        else
        {
                colon = p;
#if NETUNIX
                /* default to AF_UNIX */
                addr.sa.sa_family = AF_UNIX;
                L_socksize = sizeof (struct sockaddr_un);
#else /* NETUNIX */
# if NETINET
                /* default to AF_INET */
                addr.sa.sa_family = AF_INET;
                L_socksize = sizeof addr.sin;
# else /* NETINET */
#  if NETINET6
                /* default to AF_INET6 */
                addr.sa.sa_family = AF_INET6;
                L_socksize = sizeof addr.sin6;
#  else /* NETINET6 */
                smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
                        name, p);
                return INVALID_SOCKET;
#  endif /* NETINET6 */
# endif /* NETINET */
#endif /* NETUNIX */
        }

#if NETUNIX
        if (addr.sa.sa_family == AF_UNIX)
        {
# if 0
                long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
# endif /* 0 */

                at = colon;
                len = strlen(colon) + 1;
                if (len >= sizeof addr.sunix.sun_path)
                {
                        errno = EINVAL;
                        smi_log(SMI_LOG_ERR, "%s: UNIX socket name %s too long",
                                name, colon);
                        return INVALID_SOCKET;
                }
                (void) sm_strlcpy(addr.sunix.sun_path, colon,
                                sizeof addr.sunix.sun_path);
# if 0
                errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
                                 S_IRUSR|S_IWUSR, NULL);

                /* if not safe, don't create */
                if (errno != 0)
                {
                        smi_log(SMI_LOG_ERR,
                                "%s: UNIX socket name %s unsafe",
                                name, colon);
                        return INVALID_SOCKET;
                }
# endif /* 0 */
        }
#endif /* NETUNIX */

#if NETINET || NETINET6
        if (
# if NETINET
            addr.sa.sa_family == AF_INET
# endif /* NETINET */
# if NETINET && NETINET6
            ||
# endif /* NETINET && NETINET6 */
# if NETINET6
            addr.sa.sa_family == AF_INET6
# endif /* NETINET6 */
           )
        {
                unsigned short port;

                /* Parse port@host */
                at = strchr(colon, '@');
                if (at == NULL)
                {
                        switch (addr.sa.sa_family)
                        {
# if NETINET
                          case AF_INET:
                                addr.sin.sin_addr.s_addr = INADDR_ANY;
                                break;
# endif /* NETINET */

# if NETINET6
                          case AF_INET6:
                                addr.sin6.sin6_addr = in6addr_any;
                                break;
# endif /* NETINET6 */
                        }
                }
                else
                        *at = '\0';

                if (isascii(*colon) && isdigit(*colon))
                        port = htons((unsigned short) atoi(colon));
                else
                {
# ifdef NO_GETSERVBYNAME
                        smi_log(SMI_LOG_ERR, "%s: invalid port number %s",
                                name, colon);
                        return INVALID_SOCKET;
# else /* NO_GETSERVBYNAME */
                        register struct servent *sp;

                        sp = getservbyname(colon, "tcp");
                        if (sp == NULL)
                        {
                                smi_log(SMI_LOG_ERR,
                                        "%s: unknown port name %s",
                                        name, colon);
                                return INVALID_SOCKET;
                        }
                        port = sp->s_port;
# endif /* NO_GETSERVBYNAME */
                }
                if (at != NULL)
                {
                        *at++ = '@';
                        if (*at == '[')
                        {
                                char *end;

                                end = strchr(at, ']');
                                if (end != NULL)
                                {
                                        bool found = false;
# if NETINET
                                        unsigned long hid = INADDR_NONE;
# endif /* NETINET */
# if NETINET6
                                        struct sockaddr_in6 hid6;
# endif /* NETINET6 */

                                        *end = '\0';
# if NETINET
                                        if (addr.sa.sa_family == AF_INET &&
                                            (hid = inet_addr(&at[1])) != INADDR_NONE)
                                        {
                                                addr.sin.sin_addr.s_addr = hid;
                                                addr.sin.sin_port = port;
                                                found = true;
                                        }
# endif /* NETINET */
# if NETINET6
                                        (void) memset(&hid6, '\0', sizeof hid6);
                                        if (addr.sa.sa_family == AF_INET6 &&
                                            mi_inet_pton(AF_INET6, &at[1],
                                                         &hid6.sin6_addr) == 1)
                                        {
                                                addr.sin6.sin6_addr = hid6.sin6_addr;
                                                addr.sin6.sin6_port = port;
                                                found = true;
                                        }
# endif /* NETINET6 */
                                        *end = ']';
                                        if (!found)
                                        {
                                                smi_log(SMI_LOG_ERR,
                                                        "%s: Invalid numeric domain spec \"%s\"",
                                                        name, at);
                                                return INVALID_SOCKET;
                                        }
                                }
                                else
                                {
                                        smi_log(SMI_LOG_ERR,
                                                "%s: Invalid numeric domain spec \"%s\"",
                                                name, at);
                                        return INVALID_SOCKET;
                                }
                        }
                        else
                        {
                                struct hostent *hp = NULL;

                                hp = mi_gethostbyname(at, addr.sa.sa_family);
                                if (hp == NULL)
                                {
                                        smi_log(SMI_LOG_ERR,
                                                "%s: Unknown host name %s",
                                                name, at);
                                        return INVALID_SOCKET;
                                }
                                addr.sa.sa_family = hp->h_addrtype;
                                switch (hp->h_addrtype)
                                {
# if NETINET
                                  case AF_INET:
                                        (void) memmove(&addr.sin.sin_addr,
                                                       hp->h_addr,
                                                       INADDRSZ);
                                        addr.sin.sin_port = port;
                                        break;
# endif /* NETINET */

# if NETINET6
                                  case AF_INET6:
                                        (void) memmove(&addr.sin6.sin6_addr,
                                                       hp->h_addr,
                                                       IN6ADDRSZ);
                                        addr.sin6.sin6_port = port;
                                        break;
# endif /* NETINET6 */

                                  default:
                                        smi_log(SMI_LOG_ERR,
                                                "%s: Unknown protocol for %s (%d)",
                                                name, at, hp->h_addrtype);
                                        return INVALID_SOCKET;
                                }
# if NETINET6
                                freehostent(hp);
# endif /* NETINET6 */
                        }
                }
                else
                {
                        switch (addr.sa.sa_family)
                        {
# if NETINET
                          case AF_INET:
                                addr.sin.sin_port = port;
                                break;
# endif /* NETINET */
# if NETINET6
                          case AF_INET6:
                                addr.sin6.sin6_port = port;
                                break;
# endif /* NETINET6 */
                        }
                }
        }
#endif /* NETINET || NETINET6 */

        sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
        if (!ValidSocket(sock))
        {
                smi_log(SMI_LOG_ERR,
                        "%s: Unable to create new socket: %s",
                        name, sm_errstring(errno));
                return INVALID_SOCKET;
        }

        if ((fdflags = fcntl(sock, F_GETFD, 0)) == -1 ||
            fcntl(sock, F_SETFD, fdflags | FD_CLOEXEC) == -1)
        {
                smi_log(SMI_LOG_ERR,
                        "%s: Unable to set close-on-exec: %s", name,
                        sm_errstring(errno));
                (void) closesocket(sock);
                return INVALID_SOCKET;
        }

        if (
#if NETUNIX
            addr.sa.sa_family != AF_UNIX &&
#endif /* NETUNIX */
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt,
                       sizeof(sockopt)) == -1)
        {
                smi_log(SMI_LOG_ERR,
                        "%s: set reuseaddr failed (%s)", name,
                        sm_errstring(errno));
                (void) closesocket(sock);
                return INVALID_SOCKET;
        }

#if NETUNIX
        if (addr.sa.sa_family == AF_UNIX && rmsocket)
        {
                struct stat s;

                if (stat(colon, &s) != 0)
                {
                        if (errno != ENOENT)
                        {
                                smi_log(SMI_LOG_ERR,
                                        "%s: Unable to stat() %s: %s",
                                        name, colon, sm_errstring(errno));
                                (void) closesocket(sock);
                                return INVALID_SOCKET;
                        }
                }
                else if (!S_ISSOCK(s.st_mode))
                {
                        smi_log(SMI_LOG_ERR,
                                "%s: %s is not a UNIX domain socket",
                                name, colon);
                        (void) closesocket(sock);
                        return INVALID_SOCKET;
                }
                else if (unlink(colon) != 0)
                {
                        smi_log(SMI_LOG_ERR,
                                "%s: Unable to remove %s: %s",
                                name, colon, sm_errstring(errno));
                        (void) closesocket(sock);
                        return INVALID_SOCKET;
                }
        }
#endif /* NETUNIX */

        if (bind(sock, &addr.sa, L_socksize) < 0)
        {
                smi_log(SMI_LOG_ERR,
                        "%s: Unable to bind to port %s: %s",
                        name, conn, sm_errstring(errno));
                (void) closesocket(sock);
                return INVALID_SOCKET;
        }

        if (listen(sock, backlog) < 0)
        {
                smi_log(SMI_LOG_ERR,
                        "%s: listen call failed: %s", name,
                        sm_errstring(errno));
                (void) closesocket(sock);
                return INVALID_SOCKET;
        }

#if NETUNIX
        if (addr.sa.sa_family == AF_UNIX && len > 0)
        {
                /*
                **  Set global variable sockpath so the UNIX socket can be
                **  unlink()ed at exit.
                */

                sockpath = (char *) malloc(len);
                if (sockpath != NULL)
                        (void) sm_strlcpy(sockpath, colon, len);
                else
                {
                        smi_log(SMI_LOG_ERR,
                                "%s: can't malloc(%d) for sockpath: %s",
                                name, (int) len, sm_errstring(errno));
                        (void) closesocket(sock);
                        return INVALID_SOCKET;
                }
        }
#endif /* NETUNIX */
        L_family = addr.sa.sa_family;
        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 *
mi_thread_handle_wrapper(arg)
        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
mi_closener()
{
        (void) smutex_lock(&L_Mutex);
        if (ValidSocket(listenfd))
        {
#if NETUNIX
                bool removable;
                struct stat sockinfo;
                struct stat fileinfo;

                removable = sockpath != NULL &&
                            geteuid() != 0 &&
                            fstat(listenfd, &sockinfo) == 0 &&
                            (S_ISFIFO(sockinfo.st_mode)
# ifdef S_ISSOCK
                             || S_ISSOCK(sockinfo.st_mode)
# endif /* S_ISSOCK */
                            );
#endif /* NETUNIX */

                (void) closesocket(listenfd);
                listenfd = INVALID_SOCKET;

#if NETUNIX
                /* XXX sleep() some time before doing this? */
                if (sockpath != NULL)
                {
                        if (removable &&
                            stat(sockpath, &fileinfo) == 0 &&
                            ((fileinfo.st_dev == sockinfo.st_dev &&
                              fileinfo.st_ino == sockinfo.st_ino)
# ifdef S_ISSOCK
                             || S_ISSOCK(fileinfo.st_mode)
# endif /* S_ISSOCK */
                            )
                            &&
                            (S_ISFIFO(fileinfo.st_mode)
# ifdef S_ISSOCK
                             || S_ISSOCK(fileinfo.st_mode)
# endif /* S_ISSOCK */
                             ))
                                (void) unlink(sockpath);
                        free(sockpath);
                        sockpath = NULL;
                }
#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
**              timeout -- timeout for reads/writes
**              backlog -- listen queue backlog size
**
**      Returns:
**              MI_SUCCESS -- Exited normally
**                         (session finished or we were told to exit)
**              MI_FAILURE -- Network initialization failed.
*/

#if BROKEN_PTHREAD_SLEEP

/*
**  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;                                                     \
        struct timeval st;                                              \
                                                                        \
        st.tv_sec = (s);                                                \
        st.tv_usec = 0;                                                 \
        if (st.tv_sec > 0)                                              \
        {                                                               \
                for (;;)                                                \
                {                                                       \
                        rs = select(0, NULL, NULL, NULL, &st);          \
                        if (rs < 0 && errno == EINTR)                   \
                                continue;                               \
                        if (rs != 0)                                    \
                        {                                               \
                                smi_log(SMI_LOG_ERR,                    \
                                        "MI_SLEEP(): select() returned non-zero result %d, errno = %d", \
                                        rs, errno);                     \
                        }                                               \
                        break;                                          \
                }                                                       \
        }                                                               \
}
#else /* BROKEN_PTHREAD_SLEEP */
# define MI_SLEEP(s)    sleep((s))
#endif /* BROKEN_PTHREAD_SLEEP */

int
mi_listener(conn, dbg, smfi, timeout, backlog)
        char *conn;
        int dbg;
        smfiDesc_ptr smfi;
        time_t timeout;
        int backlog;
{
        socket_t connfd = INVALID_SOCKET;
#if _FFR_DUP_FD
        socket_t dupfd = INVALID_SOCKET;
#endif /* _FFR_DUP_FD */
        int sockopt = 1;
        int r, mistop;
        int ret = MI_SUCCESS;
        int mcnt = 0;   /* error count for malloc() failures */
        int tcnt = 0;   /* error count for thread_create() failures */
        int acnt = 0;   /* error count for accept() failures */
        int scnt = 0;   /* error count for select() failures */
        int save_errno = 0;
#if !_FFR_WORKERS_POOL
        sthread_t thread_id;
#endif /* !_FFR_WORKERS_POOL */
        _SOCK_ADDR cliaddr;
        SOCKADDR_LEN_T clilen;
        SMFICTX_PTR ctx;
        FD_RD_VAR(rds, excs);
        struct timeval chktime;

        if (mi_opensocket(conn, backlog, dbg, false, smfi) == MI_FAILURE)
                return MI_FAILURE;

#if _FFR_WORKERS_POOL
        if (mi_pool_controller_init() == MI_FAILURE)
                return MI_FAILURE;
#endif /* _FFR_WORKERS_POOL */

        clilen = L_socksize;
        while ((mistop = mi_stop()) == MILTER_CONT)
        {
                (void) smutex_lock(&L_Mutex);
                if (!ValidSocket(listenfd))
                {
                        ret = MI_FAILURE;
                        smi_log(SMI_LOG_ERR,
                                "%s: listenfd=%d corrupted, terminating, errno=%d",
                                smfi->xxfi_name, listenfd, errno);
                        (void) smutex_unlock(&L_Mutex);
                        break;
                }

                /* select on interface ports */
                FD_RD_INIT(listenfd, rds, excs);
                chktime.tv_sec = MI_CHK_TIME;
                chktime.tv_usec = 0;
                r = FD_RD_READY(listenfd, rds, excs, &chktime);
                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++;
                        smi_log(SMI_LOG_ERR,
                                "%s: %s() failed (%s), %s",
                                smfi->xxfi_name, MI_POLLSELECT,
                                sm_errstring(save_errno),
                                scnt >= MAX_FAILS_S ? "abort" : "try again");
                        MI_SLEEP(scnt);
                        if (scnt >= MAX_FAILS_S)
                        {
                                ret = MI_FAILURE;
                                break;
                        }
                        continue;
                }
                if (!FD_IS_RD_RDY(listenfd, rds, excs))
                {
                        /* some error: just stop for now... */
                        ret = MI_FAILURE;
                        (void) smutex_unlock(&L_Mutex);
                        smi_log(SMI_LOG_ERR,
                                "%s: %s() returned exception for socket, abort",
                                smfi->xxfi_name, MI_POLLSELECT);
                        break;
                }
                scnt = 0;       /* reset error counter for select() */

                (void) memset(&cliaddr, '\0', sizeof cliaddr);
                connfd = accept(listenfd, (struct sockaddr *) &cliaddr,
                                &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
                     cliaddr.sa.sa_len == 0 ||
# endif /* BSD4_4_SOCKADDR */
                     cliaddr.sa.sa_family != L_family))
                {
                        (void) closesocket(connfd);
                        connfd = INVALID_SOCKET;
                        save_errno = EINVAL;
                }

                /* check if acceptable for select() */
                if (ValidSocket(connfd) && !SM_FD_OK_SELECT(connfd))
                {
                        (void) closesocket(connfd);
                        connfd = INVALID_SOCKET;
                        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++;
                        smi_log(SMI_LOG_ERR,
                                "%s: accept() returned invalid socket (%s), %s",
                                smfi->xxfi_name, sm_errstring(save_errno),
                                acnt >= MAX_FAILS_A ? "abort" : "try again");
                        MI_SLEEP(acnt);
                        if (acnt >= MAX_FAILS_A)
                        {
                                ret = MI_FAILURE;
                                break;
                        }
                        continue;
                }
                acnt = 0;       /* reset error counter for accept() */
#if _FFR_DUP_FD
                dupfd = fcntl(connfd, F_DUPFD, 256);
                if (ValidSocket(dupfd) && SM_FD_OK_SELECT(dupfd))
                {
                        close(connfd);
                        connfd = dupfd;
                        dupfd = INVALID_SOCKET;
                }
#endif /* _FFR_DUP_FD */

                if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE,
                                (void *) &sockopt, sizeof sockopt) < 0)
                {
                        smi_log(SMI_LOG_WARN,
                                "%s: set keepalive failed (%s)",
                                smfi->xxfi_name, sm_errstring(errno));
                        /* XXX: continue? */
                }
                if ((ctx = (SMFICTX_PTR) malloc(sizeof *ctx)) == NULL)
                {
                        (void) closesocket(connfd);
                        mcnt++;
                        smi_log(SMI_LOG_ERR, "%s: malloc(ctx) failed (%s), %s",
                                smfi->xxfi_name, sm_errstring(save_errno),
                                mcnt >= MAX_FAILS_M ? "abort" : "try again");
                        MI_SLEEP(mcnt);
                        if (mcnt >= MAX_FAILS_M)
                        {
                                ret = MI_FAILURE;
                                break;
                        }
                        continue;
                }
                mcnt = 0;       /* reset error counter for malloc() */
                (void) memset(ctx, '\0', sizeof *ctx);
                ctx->ctx_sd = connfd;
                ctx->ctx_dbg = dbg;
                ctx->ctx_timeout = timeout;
                ctx->ctx_smfi = smfi;
                if (smfi->xxfi_connect == NULL)
                        ctx->ctx_pflags |= SMFIP_NOCONNECT;
                if (smfi->xxfi_helo == NULL)
                        ctx->ctx_pflags |= SMFIP_NOHELO;
                if (smfi->xxfi_envfrom == NULL)
                        ctx->ctx_pflags |= SMFIP_NOMAIL;
                if (smfi->xxfi_envrcpt == NULL)
                        ctx->ctx_pflags |= SMFIP_NORCPT;
                if (smfi->xxfi_header == NULL)
                        ctx->ctx_pflags |= SMFIP_NOHDRS;
                if (smfi->xxfi_eoh == NULL)
                        ctx->ctx_pflags |= SMFIP_NOEOH;
                if (smfi->xxfi_body == NULL)
                        ctx->ctx_pflags |= SMFIP_NOBODY;
                if (smfi->xxfi_version <= 3 || smfi->xxfi_data == NULL)
                        ctx->ctx_pflags |= SMFIP_NODATA;
                if (smfi->xxfi_version <= 2 || smfi->xxfi_unknown == NULL)
                        ctx->ctx_pflags |= SMFIP_NOUNKNOWN;

#if _FFR_WORKERS_POOL
# define LOG_CRT_FAIL   "%s: mi_start_session() failed: %d, %s"
                if ((r = mi_start_session(ctx)) != MI_SUCCESS)
#else /* _FFR_WORKERS_POOL */
# define LOG_CRT_FAIL   "%s: thread_create() failed: %d, %s"
                if ((r = thread_create(&thread_id,
                                        mi_thread_handle_wrapper,
                                        (void *) ctx)) != 0)
#endif /* _FFR_WORKERS_POOL */
                {
                        tcnt++;
                        smi_log(SMI_LOG_ERR,
                                LOG_CRT_FAIL,
                                smfi->xxfi_name,  r,
                                tcnt >= MAX_FAILS_T ? "abort" : "try again");
                        MI_SLEEP(tcnt);
                        (void) closesocket(connfd);
                        free(ctx);
                        if (tcnt >= MAX_FAILS_T)
                        {
                                ret = MI_FAILURE;
                                break;
                        }
                        continue;
                }
                tcnt = 0;
        }
        if (ret != MI_SUCCESS)
                mi_stop_milters(MILTER_ABRT);
        else
        {
                if (mistop != MILTER_CONT)
                        smi_log(SMI_LOG_INFO, "%s: mi_stop=%d",
                                smfi->xxfi_name, mistop);
                mi_closener();
        }
        (void) smutex_destroy(&L_Mutex);
        return ret;
}