/* * 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_RCSID("@(#)$Id: listener.c,v 8.126 2009/12/16 16:40:23 ca Exp $") /* ** listener.c -- threaded network listener */ #include "libmilter.h" #include #include #include # if NETINET || NETINET6 # include # 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; }