1N/A/*
1N/A * Copyright (c) 1999-2007 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A *
1N/A */
1N/A
1N/A#include <sm/gen.h>
1N/ASM_RCSID("@(#)$Id: listener.c,v 8.126 2009/12/16 16:40:23 ca Exp $")
1N/A
1N/A/*
1N/A** listener.c -- threaded network listener
1N/A*/
1N/A
1N/A#include "libmilter.h"
1N/A#include <sm/errstring.h>
1N/A
1N/A#include <sys/types.h>
1N/A#include <sys/stat.h>
1N/A
1N/A
1N/A# if NETINET || NETINET6
1N/A# include <arpa/inet.h>
1N/A# endif /* NETINET || NETINET6 */
1N/A# if SM_CONF_POLL
1N/A# undef SM_FD_OK_SELECT
1N/A# define SM_FD_OK_SELECT(fd) true
1N/A# endif /* SM_CONF_POLL */
1N/A
1N/Astatic smutex_t L_Mutex;
1N/Astatic int L_family;
1N/Astatic SOCKADDR_LEN_T L_socksize;
1N/Astatic socket_t listenfd = INVALID_SOCKET;
1N/A
1N/Astatic socket_t mi_milteropen __P((char *, int, bool, char *));
1N/A#if !_FFR_WORKERS_POOL
1N/Astatic void *mi_thread_handle_wrapper __P((void *));
1N/A#endif /* !_FFR_WORKERS_POOL */
1N/A
1N/A/*
1N/A** MI_OPENSOCKET -- create the socket where this filter and the MTA will meet
1N/A**
1N/A** Parameters:
1N/A** conn -- connection description
1N/A** backlog -- listen backlog
1N/A** dbg -- debug level
1N/A** rmsocket -- if true, try to unlink() the socket first
1N/A** (UNIX domain sockets only)
1N/A** smfi -- filter structure to use
1N/A**
1N/A** Return value:
1N/A** MI_SUCCESS/MI_FAILURE
1N/A*/
1N/A
1N/Aint
1N/Ami_opensocket(conn, backlog, dbg, rmsocket, smfi)
1N/A char *conn;
1N/A int backlog;
1N/A int dbg;
1N/A bool rmsocket;
1N/A smfiDesc_ptr smfi;
1N/A{
1N/A if (smfi == NULL || conn == NULL)
1N/A return MI_FAILURE;
1N/A
1N/A if (ValidSocket(listenfd))
1N/A return MI_SUCCESS;
1N/A
1N/A if (dbg > 0)
1N/A {
1N/A smi_log(SMI_LOG_DEBUG,
1N/A "%s: Opening listen socket on conn %s",
1N/A smfi->xxfi_name, conn);
1N/A }
1N/A (void) smutex_init(&L_Mutex);
1N/A (void) smutex_lock(&L_Mutex);
1N/A listenfd = mi_milteropen(conn, backlog, rmsocket, smfi->xxfi_name);
1N/A if (!ValidSocket(listenfd))
1N/A {
1N/A smi_log(SMI_LOG_FATAL,
1N/A "%s: Unable to create listening socket on conn %s",
1N/A smfi->xxfi_name, conn);
1N/A (void) smutex_unlock(&L_Mutex);
1N/A return MI_FAILURE;
1N/A }
1N/A if (!SM_FD_OK_SELECT(listenfd))
1N/A {
1N/A smi_log(SMI_LOG_ERR, "%s: fd %d is larger than FD_SETSIZE %d",
1N/A smfi->xxfi_name, listenfd, FD_SETSIZE);
1N/A (void) smutex_unlock(&L_Mutex);
1N/A return MI_FAILURE;
1N/A }
1N/A (void) smutex_unlock(&L_Mutex);
1N/A return MI_SUCCESS;
1N/A}
1N/A
1N/A/*
1N/A** MI_MILTEROPEN -- setup socket to listen on
1N/A**
1N/A** Parameters:
1N/A** conn -- connection description
1N/A** backlog -- listen backlog
1N/A** rmsocket -- if true, try to unlink() the socket first
1N/A** (UNIX domain sockets only)
1N/A** name -- name for logging
1N/A**
1N/A** Returns:
1N/A** socket upon success, error code otherwise.
1N/A**
1N/A** Side effect:
1N/A** sets sockpath if UNIX socket.
1N/A*/
1N/A
1N/A#if NETUNIX
1N/Astatic char *sockpath = NULL;
1N/A#endif /* NETUNIX */
1N/A
1N/Astatic socket_t
1N/Ami_milteropen(conn, backlog, rmsocket, name)
1N/A char *conn;
1N/A int backlog;
1N/A bool rmsocket;
1N/A char *name;
1N/A{
1N/A socket_t sock;
1N/A int sockopt = 1;
1N/A int fdflags;
1N/A size_t len = 0;
1N/A char *p;
1N/A char *colon;
1N/A char *at;
1N/A SOCKADDR addr;
1N/A
1N/A if (conn == NULL || conn[0] == '\0')
1N/A {
1N/A smi_log(SMI_LOG_ERR, "%s: empty or missing socket information",
1N/A name);
1N/A return INVALID_SOCKET;
1N/A }
1N/A (void) memset(&addr, '\0', sizeof addr);
1N/A
1N/A /* protocol:filename or protocol:port@host */
1N/A p = conn;
1N/A colon = strchr(p, ':');
1N/A if (colon != NULL)
1N/A {
1N/A *colon = '\0';
1N/A
1N/A if (*p == '\0')
1N/A {
1N/A#if NETUNIX
1N/A /* default to AF_UNIX */
1N/A addr.sa.sa_family = AF_UNIX;
1N/A L_socksize = sizeof (struct sockaddr_un);
1N/A#else /* NETUNIX */
1N/A# if NETINET
1N/A /* default to AF_INET */
1N/A addr.sa.sa_family = AF_INET;
1N/A L_socksize = sizeof addr.sin;
1N/A# else /* NETINET */
1N/A# if NETINET6
1N/A /* default to AF_INET6 */
1N/A addr.sa.sa_family = AF_INET6;
1N/A L_socksize = sizeof addr.sin6;
1N/A# else /* NETINET6 */
1N/A /* no protocols available */
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: no valid socket protocols available",
1N/A name);
1N/A return INVALID_SOCKET;
1N/A# endif /* NETINET6 */
1N/A# endif /* NETINET */
1N/A#endif /* NETUNIX */
1N/A }
1N/A#if NETUNIX
1N/A else if (strcasecmp(p, "unix") == 0 ||
1N/A strcasecmp(p, "local") == 0)
1N/A {
1N/A addr.sa.sa_family = AF_UNIX;
1N/A L_socksize = sizeof (struct sockaddr_un);
1N/A }
1N/A#endif /* NETUNIX */
1N/A#if NETINET
1N/A else if (strcasecmp(p, "inet") == 0)
1N/A {
1N/A addr.sa.sa_family = AF_INET;
1N/A L_socksize = sizeof addr.sin;
1N/A }
1N/A#endif /* NETINET */
1N/A#if NETINET6
1N/A else if (strcasecmp(p, "inet6") == 0)
1N/A {
1N/A addr.sa.sa_family = AF_INET6;
1N/A L_socksize = sizeof addr.sin6;
1N/A }
1N/A#endif /* NETINET6 */
1N/A else
1N/A {
1N/A smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
1N/A name, p);
1N/A return INVALID_SOCKET;
1N/A }
1N/A *colon++ = ':';
1N/A }
1N/A else
1N/A {
1N/A colon = p;
1N/A#if NETUNIX
1N/A /* default to AF_UNIX */
1N/A addr.sa.sa_family = AF_UNIX;
1N/A L_socksize = sizeof (struct sockaddr_un);
1N/A#else /* NETUNIX */
1N/A# if NETINET
1N/A /* default to AF_INET */
1N/A addr.sa.sa_family = AF_INET;
1N/A L_socksize = sizeof addr.sin;
1N/A# else /* NETINET */
1N/A# if NETINET6
1N/A /* default to AF_INET6 */
1N/A addr.sa.sa_family = AF_INET6;
1N/A L_socksize = sizeof addr.sin6;
1N/A# else /* NETINET6 */
1N/A smi_log(SMI_LOG_ERR, "%s: unknown socket type %s",
1N/A name, p);
1N/A return INVALID_SOCKET;
1N/A# endif /* NETINET6 */
1N/A# endif /* NETINET */
1N/A#endif /* NETUNIX */
1N/A }
1N/A
1N/A#if NETUNIX
1N/A if (addr.sa.sa_family == AF_UNIX)
1N/A {
1N/A# if 0
1N/A long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
1N/A# endif /* 0 */
1N/A
1N/A at = colon;
1N/A len = strlen(colon) + 1;
1N/A if (len >= sizeof addr.sunix.sun_path)
1N/A {
1N/A errno = EINVAL;
1N/A smi_log(SMI_LOG_ERR, "%s: UNIX socket name %s too long",
1N/A name, colon);
1N/A return INVALID_SOCKET;
1N/A }
1N/A (void) sm_strlcpy(addr.sunix.sun_path, colon,
1N/A sizeof addr.sunix.sun_path);
1N/A# if 0
1N/A errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
1N/A S_IRUSR|S_IWUSR, NULL);
1N/A
1N/A /* if not safe, don't create */
1N/A if (errno != 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: UNIX socket name %s unsafe",
1N/A name, colon);
1N/A return INVALID_SOCKET;
1N/A }
1N/A# endif /* 0 */
1N/A }
1N/A#endif /* NETUNIX */
1N/A
1N/A#if NETINET || NETINET6
1N/A if (
1N/A# if NETINET
1N/A addr.sa.sa_family == AF_INET
1N/A# endif /* NETINET */
1N/A# if NETINET && NETINET6
1N/A ||
1N/A# endif /* NETINET && NETINET6 */
1N/A# if NETINET6
1N/A addr.sa.sa_family == AF_INET6
1N/A# endif /* NETINET6 */
1N/A )
1N/A {
1N/A unsigned short port;
1N/A
1N/A /* Parse port@host */
1N/A at = strchr(colon, '@');
1N/A if (at == NULL)
1N/A {
1N/A switch (addr.sa.sa_family)
1N/A {
1N/A# if NETINET
1N/A case AF_INET:
1N/A addr.sin.sin_addr.s_addr = INADDR_ANY;
1N/A break;
1N/A# endif /* NETINET */
1N/A
1N/A# if NETINET6
1N/A case AF_INET6:
1N/A addr.sin6.sin6_addr = in6addr_any;
1N/A break;
1N/A# endif /* NETINET6 */
1N/A }
1N/A }
1N/A else
1N/A *at = '\0';
1N/A
1N/A if (isascii(*colon) && isdigit(*colon))
1N/A port = htons((unsigned short) atoi(colon));
1N/A else
1N/A {
1N/A# ifdef NO_GETSERVBYNAME
1N/A smi_log(SMI_LOG_ERR, "%s: invalid port number %s",
1N/A name, colon);
1N/A return INVALID_SOCKET;
1N/A# else /* NO_GETSERVBYNAME */
1N/A register struct servent *sp;
1N/A
1N/A sp = getservbyname(colon, "tcp");
1N/A if (sp == NULL)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: unknown port name %s",
1N/A name, colon);
1N/A return INVALID_SOCKET;
1N/A }
1N/A port = sp->s_port;
1N/A# endif /* NO_GETSERVBYNAME */
1N/A }
1N/A if (at != NULL)
1N/A {
1N/A *at++ = '@';
1N/A if (*at == '[')
1N/A {
1N/A char *end;
1N/A
1N/A end = strchr(at, ']');
1N/A if (end != NULL)
1N/A {
1N/A bool found = false;
1N/A# if NETINET
1N/A unsigned long hid = INADDR_NONE;
1N/A# endif /* NETINET */
1N/A# if NETINET6
1N/A struct sockaddr_in6 hid6;
1N/A# endif /* NETINET6 */
1N/A
1N/A *end = '\0';
1N/A# if NETINET
1N/A if (addr.sa.sa_family == AF_INET &&
1N/A (hid = inet_addr(&at[1])) != INADDR_NONE)
1N/A {
1N/A addr.sin.sin_addr.s_addr = hid;
1N/A addr.sin.sin_port = port;
1N/A found = true;
1N/A }
1N/A# endif /* NETINET */
1N/A# if NETINET6
1N/A (void) memset(&hid6, '\0', sizeof hid6);
1N/A if (addr.sa.sa_family == AF_INET6 &&
1N/A mi_inet_pton(AF_INET6, &at[1],
1N/A &hid6.sin6_addr) == 1)
1N/A {
1N/A addr.sin6.sin6_addr = hid6.sin6_addr;
1N/A addr.sin6.sin6_port = port;
1N/A found = true;
1N/A }
1N/A# endif /* NETINET6 */
1N/A *end = ']';
1N/A if (!found)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Invalid numeric domain spec \"%s\"",
1N/A name, at);
1N/A return INVALID_SOCKET;
1N/A }
1N/A }
1N/A else
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Invalid numeric domain spec \"%s\"",
1N/A name, at);
1N/A return INVALID_SOCKET;
1N/A }
1N/A }
1N/A else
1N/A {
1N/A struct hostent *hp = NULL;
1N/A
1N/A hp = mi_gethostbyname(at, addr.sa.sa_family);
1N/A if (hp == NULL)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unknown host name %s",
1N/A name, at);
1N/A return INVALID_SOCKET;
1N/A }
1N/A addr.sa.sa_family = hp->h_addrtype;
1N/A switch (hp->h_addrtype)
1N/A {
1N/A# if NETINET
1N/A case AF_INET:
1N/A (void) memmove(&addr.sin.sin_addr,
1N/A hp->h_addr,
1N/A INADDRSZ);
1N/A addr.sin.sin_port = port;
1N/A break;
1N/A# endif /* NETINET */
1N/A
1N/A# if NETINET6
1N/A case AF_INET6:
1N/A (void) memmove(&addr.sin6.sin6_addr,
1N/A hp->h_addr,
1N/A IN6ADDRSZ);
1N/A addr.sin6.sin6_port = port;
1N/A break;
1N/A# endif /* NETINET6 */
1N/A
1N/A default:
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unknown protocol for %s (%d)",
1N/A name, at, hp->h_addrtype);
1N/A return INVALID_SOCKET;
1N/A }
1N/A# if NETINET6
1N/A freehostent(hp);
1N/A# endif /* NETINET6 */
1N/A }
1N/A }
1N/A else
1N/A {
1N/A switch (addr.sa.sa_family)
1N/A {
1N/A# if NETINET
1N/A case AF_INET:
1N/A addr.sin.sin_port = port;
1N/A break;
1N/A# endif /* NETINET */
1N/A# if NETINET6
1N/A case AF_INET6:
1N/A addr.sin6.sin6_port = port;
1N/A break;
1N/A# endif /* NETINET6 */
1N/A }
1N/A }
1N/A }
1N/A#endif /* NETINET || NETINET6 */
1N/A
1N/A sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
1N/A if (!ValidSocket(sock))
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unable to create new socket: %s",
1N/A name, sm_errstring(errno));
1N/A return INVALID_SOCKET;
1N/A }
1N/A
1N/A if ((fdflags = fcntl(sock, F_GETFD, 0)) == -1 ||
1N/A fcntl(sock, F_SETFD, fdflags | FD_CLOEXEC) == -1)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unable to set close-on-exec: %s", name,
1N/A sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A
1N/A if (
1N/A#if NETUNIX
1N/A addr.sa.sa_family != AF_UNIX &&
1N/A#endif /* NETUNIX */
1N/A setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt,
1N/A sizeof(sockopt)) == -1)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: set reuseaddr failed (%s)", name,
1N/A sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A
1N/A#if NETUNIX
1N/A if (addr.sa.sa_family == AF_UNIX && rmsocket)
1N/A {
1N/A struct stat s;
1N/A
1N/A if (stat(colon, &s) != 0)
1N/A {
1N/A if (errno != ENOENT)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unable to stat() %s: %s",
1N/A name, colon, sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A }
1N/A else if (!S_ISSOCK(s.st_mode))
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: %s is not a UNIX domain socket",
1N/A name, colon);
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A else if (unlink(colon) != 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unable to remove %s: %s",
1N/A name, colon, sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A }
1N/A#endif /* NETUNIX */
1N/A
1N/A if (bind(sock, &addr.sa, L_socksize) < 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: Unable to bind to port %s: %s",
1N/A name, conn, sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A
1N/A if (listen(sock, backlog) < 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: listen call failed: %s", name,
1N/A sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A
1N/A#if NETUNIX
1N/A if (addr.sa.sa_family == AF_UNIX && len > 0)
1N/A {
1N/A /*
1N/A ** Set global variable sockpath so the UNIX socket can be
1N/A ** unlink()ed at exit.
1N/A */
1N/A
1N/A sockpath = (char *) malloc(len);
1N/A if (sockpath != NULL)
1N/A (void) sm_strlcpy(sockpath, colon, len);
1N/A else
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: can't malloc(%d) for sockpath: %s",
1N/A name, (int) len, sm_errstring(errno));
1N/A (void) closesocket(sock);
1N/A return INVALID_SOCKET;
1N/A }
1N/A }
1N/A#endif /* NETUNIX */
1N/A L_family = addr.sa.sa_family;
1N/A return sock;
1N/A}
1N/A
1N/A#if !_FFR_WORKERS_POOL
1N/A/*
1N/A** MI_THREAD_HANDLE_WRAPPER -- small wrapper to handle session
1N/A**
1N/A** Parameters:
1N/A** arg -- argument to pass to mi_handle_session()
1N/A**
1N/A** Returns:
1N/A** results from mi_handle_session()
1N/A*/
1N/A
1N/Astatic void *
1N/Ami_thread_handle_wrapper(arg)
1N/A void *arg;
1N/A{
1N/A /*
1N/A ** Note: on some systems this generates a compiler warning:
1N/A ** cast to pointer from integer of different size
1N/A ** You can safely ignore this warning as the result of this function
1N/A ** is not used anywhere.
1N/A */
1N/A
1N/A return (void *) mi_handle_session(arg);
1N/A}
1N/A#endif /* _FFR_WORKERS_POOL */
1N/A
1N/A/*
1N/A** MI_CLOSENER -- close listen socket
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Avoid
1N/Ami_closener()
1N/A{
1N/A (void) smutex_lock(&L_Mutex);
1N/A if (ValidSocket(listenfd))
1N/A {
1N/A#if NETUNIX
1N/A bool removable;
1N/A struct stat sockinfo;
1N/A struct stat fileinfo;
1N/A
1N/A removable = sockpath != NULL &&
1N/A geteuid() != 0 &&
1N/A fstat(listenfd, &sockinfo) == 0 &&
1N/A (S_ISFIFO(sockinfo.st_mode)
1N/A# ifdef S_ISSOCK
1N/A || S_ISSOCK(sockinfo.st_mode)
1N/A# endif /* S_ISSOCK */
1N/A );
1N/A#endif /* NETUNIX */
1N/A
1N/A (void) closesocket(listenfd);
1N/A listenfd = INVALID_SOCKET;
1N/A
1N/A#if NETUNIX
1N/A /* XXX sleep() some time before doing this? */
1N/A if (sockpath != NULL)
1N/A {
1N/A if (removable &&
1N/A stat(sockpath, &fileinfo) == 0 &&
1N/A ((fileinfo.st_dev == sockinfo.st_dev &&
1N/A fileinfo.st_ino == sockinfo.st_ino)
1N/A# ifdef S_ISSOCK
1N/A || S_ISSOCK(fileinfo.st_mode)
1N/A# endif /* S_ISSOCK */
1N/A )
1N/A &&
1N/A (S_ISFIFO(fileinfo.st_mode)
1N/A# ifdef S_ISSOCK
1N/A || S_ISSOCK(fileinfo.st_mode)
1N/A# endif /* S_ISSOCK */
1N/A ))
1N/A (void) unlink(sockpath);
1N/A free(sockpath);
1N/A sockpath = NULL;
1N/A }
1N/A#endif /* NETUNIX */
1N/A }
1N/A (void) smutex_unlock(&L_Mutex);
1N/A}
1N/A
1N/A/*
1N/A** MI_LISTENER -- Generic listener harness
1N/A**
1N/A** Open up listen port
1N/A** Wait for connections
1N/A**
1N/A** Parameters:
1N/A** conn -- connection description
1N/A** dbg -- debug level
1N/A** smfi -- filter structure to use
1N/A** timeout -- timeout for reads/writes
1N/A** backlog -- listen queue backlog size
1N/A**
1N/A** Returns:
1N/A** MI_SUCCESS -- Exited normally
1N/A** (session finished or we were told to exit)
1N/A** MI_FAILURE -- Network initialization failed.
1N/A*/
1N/A
1N/A#if BROKEN_PTHREAD_SLEEP
1N/A
1N/A/*
1N/A** Solaris 2.6, perhaps others, gets an internal threads library panic
1N/A** when sleep() is used:
1N/A**
1N/A** thread_create() failed, returned 11 (EINVAL)
1N/A** co_enable, thr_create() returned error = 24
1N/A** libthread panic: co_enable failed (PID: 17793 LWP 1)
1N/A** stacktrace:
1N/A** ef526b10
1N/A** ef52646c
1N/A** ef534cbc
1N/A** 156a4
1N/A** 14644
1N/A** 1413c
1N/A** 135e0
1N/A** 0
1N/A*/
1N/A
1N/A# define MI_SLEEP(s) \
1N/A{ \
1N/A int rs = 0; \
1N/A struct timeval st; \
1N/A \
1N/A st.tv_sec = (s); \
1N/A st.tv_usec = 0; \
1N/A if (st.tv_sec > 0) \
1N/A { \
1N/A for (;;) \
1N/A { \
1N/A rs = select(0, NULL, NULL, NULL, &st); \
1N/A if (rs < 0 && errno == EINTR) \
1N/A continue; \
1N/A if (rs != 0) \
1N/A { \
1N/A smi_log(SMI_LOG_ERR, \
1N/A "MI_SLEEP(): select() returned non-zero result %d, errno = %d", \
1N/A rs, errno); \
1N/A } \
1N/A break; \
1N/A } \
1N/A } \
1N/A}
1N/A#else /* BROKEN_PTHREAD_SLEEP */
1N/A# define MI_SLEEP(s) sleep((s))
1N/A#endif /* BROKEN_PTHREAD_SLEEP */
1N/A
1N/Aint
1N/Ami_listener(conn, dbg, smfi, timeout, backlog)
1N/A char *conn;
1N/A int dbg;
1N/A smfiDesc_ptr smfi;
1N/A time_t timeout;
1N/A int backlog;
1N/A{
1N/A socket_t connfd = INVALID_SOCKET;
1N/A#if _FFR_DUP_FD
1N/A socket_t dupfd = INVALID_SOCKET;
1N/A#endif /* _FFR_DUP_FD */
1N/A int sockopt = 1;
1N/A int r, mistop;
1N/A int ret = MI_SUCCESS;
1N/A int mcnt = 0; /* error count for malloc() failures */
1N/A int tcnt = 0; /* error count for thread_create() failures */
1N/A int acnt = 0; /* error count for accept() failures */
1N/A int scnt = 0; /* error count for select() failures */
1N/A int save_errno = 0;
1N/A#if !_FFR_WORKERS_POOL
1N/A sthread_t thread_id;
1N/A#endif /* !_FFR_WORKERS_POOL */
1N/A _SOCK_ADDR cliaddr;
1N/A SOCKADDR_LEN_T clilen;
1N/A SMFICTX_PTR ctx;
1N/A FD_RD_VAR(rds, excs);
1N/A struct timeval chktime;
1N/A
1N/A if (mi_opensocket(conn, backlog, dbg, false, smfi) == MI_FAILURE)
1N/A return MI_FAILURE;
1N/A
1N/A#if _FFR_WORKERS_POOL
1N/A if (mi_pool_controller_init() == MI_FAILURE)
1N/A return MI_FAILURE;
1N/A#endif /* _FFR_WORKERS_POOL */
1N/A
1N/A clilen = L_socksize;
1N/A while ((mistop = mi_stop()) == MILTER_CONT)
1N/A {
1N/A (void) smutex_lock(&L_Mutex);
1N/A if (!ValidSocket(listenfd))
1N/A {
1N/A ret = MI_FAILURE;
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: listenfd=%d corrupted, terminating, errno=%d",
1N/A smfi->xxfi_name, listenfd, errno);
1N/A (void) smutex_unlock(&L_Mutex);
1N/A break;
1N/A }
1N/A
1N/A /* select on interface ports */
1N/A FD_RD_INIT(listenfd, rds, excs);
1N/A chktime.tv_sec = MI_CHK_TIME;
1N/A chktime.tv_usec = 0;
1N/A r = FD_RD_READY(listenfd, rds, excs, &chktime);
1N/A if (r == 0) /* timeout */
1N/A {
1N/A (void) smutex_unlock(&L_Mutex);
1N/A continue; /* just check mi_stop() */
1N/A }
1N/A if (r < 0)
1N/A {
1N/A save_errno = errno;
1N/A (void) smutex_unlock(&L_Mutex);
1N/A if (save_errno == EINTR)
1N/A continue;
1N/A scnt++;
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: %s() failed (%s), %s",
1N/A smfi->xxfi_name, MI_POLLSELECT,
1N/A sm_errstring(save_errno),
1N/A scnt >= MAX_FAILS_S ? "abort" : "try again");
1N/A MI_SLEEP(scnt);
1N/A if (scnt >= MAX_FAILS_S)
1N/A {
1N/A ret = MI_FAILURE;
1N/A break;
1N/A }
1N/A continue;
1N/A }
1N/A if (!FD_IS_RD_RDY(listenfd, rds, excs))
1N/A {
1N/A /* some error: just stop for now... */
1N/A ret = MI_FAILURE;
1N/A (void) smutex_unlock(&L_Mutex);
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: %s() returned exception for socket, abort",
1N/A smfi->xxfi_name, MI_POLLSELECT);
1N/A break;
1N/A }
1N/A scnt = 0; /* reset error counter for select() */
1N/A
1N/A (void) memset(&cliaddr, '\0', sizeof cliaddr);
1N/A connfd = accept(listenfd, (struct sockaddr *) &cliaddr,
1N/A &clilen);
1N/A save_errno = errno;
1N/A (void) smutex_unlock(&L_Mutex);
1N/A
1N/A /*
1N/A ** If remote side closes before accept() finishes,
1N/A ** sockaddr might not be fully filled in.
1N/A */
1N/A
1N/A if (ValidSocket(connfd) &&
1N/A (clilen == 0 ||
1N/A# ifdef BSD4_4_SOCKADDR
1N/A cliaddr.sa.sa_len == 0 ||
1N/A# endif /* BSD4_4_SOCKADDR */
1N/A cliaddr.sa.sa_family != L_family))
1N/A {
1N/A (void) closesocket(connfd);
1N/A connfd = INVALID_SOCKET;
1N/A save_errno = EINVAL;
1N/A }
1N/A
1N/A /* check if acceptable for select() */
1N/A if (ValidSocket(connfd) && !SM_FD_OK_SELECT(connfd))
1N/A {
1N/A (void) closesocket(connfd);
1N/A connfd = INVALID_SOCKET;
1N/A save_errno = ERANGE;
1N/A }
1N/A
1N/A if (!ValidSocket(connfd))
1N/A {
1N/A if (save_errno == EINTR
1N/A#ifdef EAGAIN
1N/A || save_errno == EAGAIN
1N/A#endif /* EAGAIN */
1N/A#ifdef ECONNABORTED
1N/A || save_errno == ECONNABORTED
1N/A#endif /* ECONNABORTED */
1N/A#ifdef EMFILE
1N/A || save_errno == EMFILE
1N/A#endif /* EMFILE */
1N/A#ifdef ENFILE
1N/A || save_errno == ENFILE
1N/A#endif /* ENFILE */
1N/A#ifdef ENOBUFS
1N/A || save_errno == ENOBUFS
1N/A#endif /* ENOBUFS */
1N/A#ifdef ENOMEM
1N/A || save_errno == ENOMEM
1N/A#endif /* ENOMEM */
1N/A#ifdef ENOSR
1N/A || save_errno == ENOSR
1N/A#endif /* ENOSR */
1N/A#ifdef EWOULDBLOCK
1N/A || save_errno == EWOULDBLOCK
1N/A#endif /* EWOULDBLOCK */
1N/A )
1N/A continue;
1N/A acnt++;
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: accept() returned invalid socket (%s), %s",
1N/A smfi->xxfi_name, sm_errstring(save_errno),
1N/A acnt >= MAX_FAILS_A ? "abort" : "try again");
1N/A MI_SLEEP(acnt);
1N/A if (acnt >= MAX_FAILS_A)
1N/A {
1N/A ret = MI_FAILURE;
1N/A break;
1N/A }
1N/A continue;
1N/A }
1N/A acnt = 0; /* reset error counter for accept() */
1N/A#if _FFR_DUP_FD
1N/A dupfd = fcntl(connfd, F_DUPFD, 256);
1N/A if (ValidSocket(dupfd) && SM_FD_OK_SELECT(dupfd))
1N/A {
1N/A close(connfd);
1N/A connfd = dupfd;
1N/A dupfd = INVALID_SOCKET;
1N/A }
1N/A#endif /* _FFR_DUP_FD */
1N/A
1N/A if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE,
1N/A (void *) &sockopt, sizeof sockopt) < 0)
1N/A {
1N/A smi_log(SMI_LOG_WARN,
1N/A "%s: set keepalive failed (%s)",
1N/A smfi->xxfi_name, sm_errstring(errno));
1N/A /* XXX: continue? */
1N/A }
1N/A if ((ctx = (SMFICTX_PTR) malloc(sizeof *ctx)) == NULL)
1N/A {
1N/A (void) closesocket(connfd);
1N/A mcnt++;
1N/A smi_log(SMI_LOG_ERR, "%s: malloc(ctx) failed (%s), %s",
1N/A smfi->xxfi_name, sm_errstring(save_errno),
1N/A mcnt >= MAX_FAILS_M ? "abort" : "try again");
1N/A MI_SLEEP(mcnt);
1N/A if (mcnt >= MAX_FAILS_M)
1N/A {
1N/A ret = MI_FAILURE;
1N/A break;
1N/A }
1N/A continue;
1N/A }
1N/A mcnt = 0; /* reset error counter for malloc() */
1N/A (void) memset(ctx, '\0', sizeof *ctx);
1N/A ctx->ctx_sd = connfd;
1N/A ctx->ctx_dbg = dbg;
1N/A ctx->ctx_timeout = timeout;
1N/A ctx->ctx_smfi = smfi;
1N/A if (smfi->xxfi_connect == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOCONNECT;
1N/A if (smfi->xxfi_helo == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOHELO;
1N/A if (smfi->xxfi_envfrom == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOMAIL;
1N/A if (smfi->xxfi_envrcpt == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NORCPT;
1N/A if (smfi->xxfi_header == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOHDRS;
1N/A if (smfi->xxfi_eoh == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOEOH;
1N/A if (smfi->xxfi_body == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOBODY;
1N/A if (smfi->xxfi_version <= 3 || smfi->xxfi_data == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NODATA;
1N/A if (smfi->xxfi_version <= 2 || smfi->xxfi_unknown == NULL)
1N/A ctx->ctx_pflags |= SMFIP_NOUNKNOWN;
1N/A
1N/A#if _FFR_WORKERS_POOL
1N/A# define LOG_CRT_FAIL "%s: mi_start_session() failed: %d, %s"
1N/A if ((r = mi_start_session(ctx)) != MI_SUCCESS)
1N/A#else /* _FFR_WORKERS_POOL */
1N/A# define LOG_CRT_FAIL "%s: thread_create() failed: %d, %s"
1N/A if ((r = thread_create(&thread_id,
1N/A mi_thread_handle_wrapper,
1N/A (void *) ctx)) != 0)
1N/A#endif /* _FFR_WORKERS_POOL */
1N/A {
1N/A tcnt++;
1N/A smi_log(SMI_LOG_ERR,
1N/A LOG_CRT_FAIL,
1N/A smfi->xxfi_name, r,
1N/A tcnt >= MAX_FAILS_T ? "abort" : "try again");
1N/A MI_SLEEP(tcnt);
1N/A (void) closesocket(connfd);
1N/A free(ctx);
1N/A if (tcnt >= MAX_FAILS_T)
1N/A {
1N/A ret = MI_FAILURE;
1N/A break;
1N/A }
1N/A continue;
1N/A }
1N/A tcnt = 0;
1N/A }
1N/A if (ret != MI_SUCCESS)
1N/A mi_stop_milters(MILTER_ABRT);
1N/A else
1N/A {
1N/A if (mistop != MILTER_CONT)
1N/A smi_log(SMI_LOG_INFO, "%s: mi_stop=%d",
1N/A smfi->xxfi_name, mistop);
1N/A mi_closener();
1N/A }
1N/A (void) smutex_destroy(&L_Mutex);
1N/A return ret;
1N/A}