1N/A/*
1N/A * Copyright (c) 1999-2004, 2009 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: comm.c,v 8.70 2009/12/16 16:33:48 ca Exp $")
1N/A
1N/A#include "libmilter.h"
1N/A#include <sm/errstring.h>
1N/A#include <sys/uio.h>
1N/A
1N/Astatic ssize_t retry_writev __P((socket_t, struct iovec *, int, struct timeval *));
1N/Astatic size_t Maxdatasize = MILTER_MAX_DATA_SIZE;
1N/A
1N/A/*
1N/A** SMFI_SETMAXDATASIZE -- set limit for milter data read/write.
1N/A**
1N/A** Parameters:
1N/A** sz -- new limit.
1N/A**
1N/A** Returns:
1N/A** old limit
1N/A*/
1N/A
1N/Asize_t
1N/Asmfi_setmaxdatasize(sz)
1N/A size_t sz;
1N/A{
1N/A size_t old;
1N/A
1N/A old = Maxdatasize;
1N/A Maxdatasize = sz;
1N/A return old;
1N/A}
1N/A
1N/A/*
1N/A** MI_RD_CMD -- read a command
1N/A**
1N/A** Parameters:
1N/A** sd -- socket descriptor
1N/A** timeout -- maximum time to wait
1N/A** cmd -- single character command read from sd
1N/A** rlen -- pointer to length of result
1N/A** name -- name of milter
1N/A**
1N/A** Returns:
1N/A** buffer with rest of command
1N/A** (malloc()ed here, should be free()d)
1N/A** hack: encode error in cmd
1N/A*/
1N/A
1N/Achar *
1N/Ami_rd_cmd(sd, timeout, cmd, rlen, name)
1N/A socket_t sd;
1N/A struct timeval *timeout;
1N/A char *cmd;
1N/A size_t *rlen;
1N/A char *name;
1N/A{
1N/A ssize_t len;
1N/A mi_int32 expl;
1N/A ssize_t i;
1N/A FD_RD_VAR(rds, excs);
1N/A int ret;
1N/A int save_errno;
1N/A char *buf;
1N/A char data[MILTER_LEN_BYTES + 1];
1N/A
1N/A *cmd = '\0';
1N/A *rlen = 0;
1N/A
1N/A i = 0;
1N/A for (;;)
1N/A {
1N/A FD_RD_INIT(sd, rds, excs);
1N/A ret = FD_RD_READY(sd, rds, excs, timeout);
1N/A if (ret == 0)
1N/A break;
1N/A else if (ret < 0)
1N/A {
1N/A if (errno == EINTR)
1N/A continue;
1N/A break;
1N/A }
1N/A if (FD_IS_RD_EXC(sd, rds, excs))
1N/A {
1N/A *cmd = SMFIC_SELECT;
1N/A return NULL;
1N/A }
1N/A
1N/A len = MI_SOCK_READ(sd, data + i, sizeof data - i);
1N/A if (MI_SOCK_READ_FAIL(len))
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s, mi_rd_cmd: read returned %d: %s",
1N/A name, (int) len, sm_errstring(errno));
1N/A *cmd = SMFIC_RECVERR;
1N/A return NULL;
1N/A }
1N/A if (len == 0)
1N/A {
1N/A *cmd = SMFIC_EOF;
1N/A return NULL;
1N/A }
1N/A if (len >= (ssize_t) sizeof data - i)
1N/A break;
1N/A i += len;
1N/A }
1N/A if (ret == 0)
1N/A {
1N/A *cmd = SMFIC_TIMEOUT;
1N/A return NULL;
1N/A }
1N/A else if (ret < 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: mi_rd_cmd: %s() returned %d: %s",
1N/A name, MI_POLLSELECT, ret, sm_errstring(errno));
1N/A *cmd = SMFIC_RECVERR;
1N/A return NULL;
1N/A }
1N/A
1N/A *cmd = data[MILTER_LEN_BYTES];
1N/A data[MILTER_LEN_BYTES] = '\0';
1N/A (void) memcpy((void *) &expl, (void *) &(data[0]), MILTER_LEN_BYTES);
1N/A expl = ntohl(expl) - 1;
1N/A if (expl <= 0)
1N/A return NULL;
1N/A if (expl > Maxdatasize)
1N/A {
1N/A *cmd = SMFIC_TOOBIG;
1N/A return NULL;
1N/A }
1N/A#if _FFR_ADD_NULL
1N/A buf = malloc(expl + 1);
1N/A#else /* _FFR_ADD_NULL */
1N/A buf = malloc(expl);
1N/A#endif /* _FFR_ADD_NULL */
1N/A if (buf == NULL)
1N/A {
1N/A *cmd = SMFIC_MALLOC;
1N/A return NULL;
1N/A }
1N/A
1N/A i = 0;
1N/A for (;;)
1N/A {
1N/A FD_RD_INIT(sd, rds, excs);
1N/A ret = FD_RD_READY(sd, rds, excs, timeout);
1N/A if (ret == 0)
1N/A break;
1N/A else if (ret < 0)
1N/A {
1N/A if (errno == EINTR)
1N/A continue;
1N/A break;
1N/A }
1N/A if (FD_IS_RD_EXC(sd, rds, excs))
1N/A {
1N/A *cmd = SMFIC_SELECT;
1N/A free(buf);
1N/A return NULL;
1N/A }
1N/A len = MI_SOCK_READ(sd, buf + i, expl - i);
1N/A if (MI_SOCK_READ_FAIL(len))
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: mi_rd_cmd: read returned %d: %s",
1N/A name, (int) len, sm_errstring(errno));
1N/A ret = -1;
1N/A break;
1N/A }
1N/A if (len == 0)
1N/A {
1N/A *cmd = SMFIC_EOF;
1N/A free(buf);
1N/A return NULL;
1N/A }
1N/A if (len > expl - i)
1N/A {
1N/A *cmd = SMFIC_RECVERR;
1N/A free(buf);
1N/A return NULL;
1N/A }
1N/A if (len >= expl - i)
1N/A {
1N/A *rlen = expl;
1N/A#if _FFR_ADD_NULL
1N/A /* makes life simpler for common string routines */
1N/A buf[expl] = '\0';
1N/A#endif /* _FFR_ADD_NULL */
1N/A return buf;
1N/A }
1N/A i += len;
1N/A }
1N/A
1N/A save_errno = errno;
1N/A free(buf);
1N/A
1N/A /* select returned 0 (timeout) or < 0 (error) */
1N/A if (ret == 0)
1N/A {
1N/A *cmd = SMFIC_TIMEOUT;
1N/A return NULL;
1N/A }
1N/A if (ret < 0)
1N/A {
1N/A smi_log(SMI_LOG_ERR,
1N/A "%s: mi_rd_cmd: %s() returned %d: %s",
1N/A name, MI_POLLSELECT, ret, sm_errstring(save_errno));
1N/A *cmd = SMFIC_RECVERR;
1N/A return NULL;
1N/A }
1N/A *cmd = SMFIC_UNKNERR;
1N/A return NULL;
1N/A}
1N/A
1N/A/*
1N/A** RETRY_WRITEV -- Keep calling the writev() system call
1N/A** until all the data is written out or an error occurs.
1N/A**
1N/A** Parameters:
1N/A** fd -- socket descriptor
1N/A** iov -- io vector
1N/A** iovcnt -- number of elements in io vector
1N/A** must NOT exceed UIO_MAXIOV.
1N/A** timeout -- maximum time to wait
1N/A**
1N/A** Returns:
1N/A** success: number of bytes written
1N/A** otherwise: MI_FAILURE
1N/A*/
1N/A
1N/Astatic ssize_t
1N/Aretry_writev(fd, iov, iovcnt, timeout)
1N/A socket_t fd;
1N/A struct iovec *iov;
1N/A int iovcnt;
1N/A struct timeval *timeout;
1N/A{
1N/A int i;
1N/A ssize_t n, written;
1N/A FD_WR_VAR(wrs);
1N/A
1N/A written = 0;
1N/A for (;;)
1N/A {
1N/A while (iovcnt > 0 && iov[0].iov_len == 0)
1N/A {
1N/A iov++;
1N/A iovcnt--;
1N/A }
1N/A if (iovcnt <= 0)
1N/A return written;
1N/A
1N/A /*
1N/A ** We don't care much about the timeout here,
1N/A ** it's very long anyway; correct solution would be
1N/A ** to take the time before the loop and reduce the
1N/A ** timeout after each invocation.
1N/A ** FD_SETSIZE is checked when socket is created.
1N/A */
1N/A
1N/A FD_WR_INIT(fd, wrs);
1N/A i = FD_WR_READY(fd, wrs, timeout);
1N/A if (i == 0)
1N/A return MI_FAILURE;
1N/A if (i < 0)
1N/A {
1N/A if (errno == EINTR || errno == EAGAIN)
1N/A continue;
1N/A return MI_FAILURE;
1N/A }
1N/A n = writev(fd, iov, iovcnt);
1N/A if (n == -1)
1N/A {
1N/A if (errno == EINTR || errno == EAGAIN)
1N/A continue;
1N/A return MI_FAILURE;
1N/A }
1N/A
1N/A written += n;
1N/A for (i = 0; i < iovcnt; i++)
1N/A {
1N/A if (iov[i].iov_len > (unsigned int) n)
1N/A {
1N/A iov[i].iov_base = (char *)iov[i].iov_base + n;
1N/A iov[i].iov_len -= (unsigned int) n;
1N/A break;
1N/A }
1N/A n -= (int) iov[i].iov_len;
1N/A iov[i].iov_len = 0;
1N/A }
1N/A if (i == iovcnt)
1N/A return written;
1N/A }
1N/A}
1N/A
1N/A/*
1N/A** MI_WR_CMD -- write a cmd to sd
1N/A**
1N/A** Parameters:
1N/A** sd -- socket descriptor
1N/A** timeout -- maximum time to wait
1N/A** cmd -- single character command to write
1N/A** buf -- buffer with further data
1N/A** len -- length of buffer (without cmd!)
1N/A**
1N/A** Returns:
1N/A** MI_SUCCESS/MI_FAILURE
1N/A*/
1N/A
1N/Aint
1N/Ami_wr_cmd(sd, timeout, cmd, buf, len)
1N/A socket_t sd;
1N/A struct timeval *timeout;
1N/A int cmd;
1N/A char *buf;
1N/A size_t len;
1N/A{
1N/A size_t sl;
1N/A ssize_t l;
1N/A mi_int32 nl;
1N/A int iovcnt;
1N/A struct iovec iov[2];
1N/A char data[MILTER_LEN_BYTES + 1];
1N/A
1N/A if (len > Maxdatasize || (len > 0 && buf == NULL))
1N/A return MI_FAILURE;
1N/A
1N/A nl = htonl(len + 1); /* add 1 for the cmd char */
1N/A (void) memcpy(data, (void *) &nl, MILTER_LEN_BYTES);
1N/A data[MILTER_LEN_BYTES] = (char) cmd;
1N/A sl = MILTER_LEN_BYTES + 1;
1N/A
1N/A /* set up the vector for the size / command */
1N/A iov[0].iov_base = (void *) data;
1N/A iov[0].iov_len = sl;
1N/A iovcnt = 1;
1N/A if (len >= 0 && buf != NULL)
1N/A {
1N/A iov[1].iov_base = (void *) buf;
1N/A iov[1].iov_len = len;
1N/A iovcnt = 2;
1N/A }
1N/A
1N/A l = retry_writev(sd, iov, iovcnt, timeout);
1N/A if (l == MI_FAILURE)
1N/A return MI_FAILURE;
1N/A return MI_SUCCESS;
1N/A}