diff options
Diffstat (limited to 'mfd/savclt.c')
-rw-r--r-- | mfd/savclt.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/mfd/savclt.c b/mfd/savclt.c new file mode 100644 index 00000000..447f6857 --- /dev/null +++ b/mfd/savclt.c @@ -0,0 +1,275 @@ +/* This file is part of Mailfromd. + Copyright (C) 2005, 2006, 2007, 2008, 2009 Sergey Poznyakoff + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define MF_SOURCE_NAME MF_SOURCE_SAVCLT +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "mailfromd.h" + +struct sockaddr *callout_server_sa; +socklen_t callout_server_sa_len; +time_t savclt_timeout = 3; + +/* Ideally, I'd want to use smtp_io functions here. But they are + based on Mailutils' TCP streams, which so far do not support + UNIX sockets. So, for the time being, I use the straightforward + I/O approach. */ +int +savclt_getline(int fd, char **pbuf, size_t *pbufsize) +{ + char *buf = *pbuf; + size_t bufsize = *pbufsize; + int off = 0; +#define DELTA 128 + time_t start = time(NULL); + + if (buf == NULL) { + bufsize = DELTA; + buf = xmalloc(bufsize); + } + + do { + struct timeval tv; + int rc, flags; + time_t diff = time(NULL) - start; + + if (diff > savclt_timeout) { + errno = ETIMEDOUT; + return -1; + } + + tv.tv_sec = savclt_timeout - diff; + tv.tv_usec = 0; + flags = MU_STREAM_READY_RD; + rc = mu_fd_wait(fd, &flags, &tv); + if (rc) { + debug1(1, "mu_fd_wait: %s", mu_strerror(rc)); + errno = rc; + return -1; + } + if (!(flags & MU_STREAM_READY_RD)) { + errno = ETIMEDOUT; + return -1; + } + + if (off + 1 == bufsize) { + bufsize += DELTA; + buf = xrealloc(buf, bufsize); + } + + rc = read(fd, buf + off, 1); + if (rc == -1) + return -1; + if (rc == 0) + break; + off++; + } while (buf[off - 1] != '\n'); + + if (off + 1 == bufsize) { + bufsize++; + buf = xrealloc(buf, bufsize); + } + buf[off] = 0; + *pbuf = buf; + *pbufsize = bufsize; + transcript("savclt", "RECV:", buf); + return off; +} + +static int +send_line(int fd, const char *txt, size_t len) +{ + time_t start = time(NULL); + + while (len) { + struct timeval tv; + int rc, flags; + time_t diff = time(NULL) - start; + + if (diff > savclt_timeout) { + errno = ETIMEDOUT; + return -1; + } + + tv.tv_sec = savclt_timeout - diff; + tv.tv_usec = 0; + flags = MU_STREAM_READY_WR; + rc = mu_fd_wait(fd, &flags, &tv); + if (rc) { + debug1(1, "mu_fd_wait: %s", mu_strerror(rc)); + errno = rc; + return -1; + } + if (!(flags & MU_STREAM_READY_WR)) { + errno = ETIMEDOUT; + return -1; + } + + rc = write(fd, txt, len); + if (rc <= 0) { + if (errno == EAGAIN) + continue; + return -1; + } + txt += rc; + len -= rc; + } + return 0; +} + +int +savclt_wrtline(int fd, char **pbuf, size_t *pbufsize, + const char *fmt, ...) +{ + int rc; + va_list ap; + + va_start(ap, fmt); + rc = mu_vasnprintf(pbuf, pbufsize, fmt, ap); + va_end(ap); + if (rc == 0) { + transcript("savclt", "SEND:", *pbuf); + rc = send_line(fd, *pbuf, strlen(*pbuf)); + } + return rc; +} + + +static int +connect_callout_server() +{ + int fd; + int flags; + int rc; + + if (!callout_server_sa) + return -1; + fd = socket(callout_server_sa->sa_family, SOCK_STREAM, 0); + if (fd == -1) { + mu_error("schedule_callout: socket: %s", + mu_strerror(errno)); + return -1; + } + + flags = fcntl(fd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); + + rc = connect(fd, callout_server_sa, callout_server_sa_len); + if (rc == -1) { + if (errno == EINPROGRESS) { + struct timeval tv; + + tv.tv_sec = savclt_timeout; + tv.tv_usec = 0; + flags = MU_STREAM_READY_WR; + rc = mu_fd_wait(fd, &flags, &tv); + if (rc) { + debug1(1, "mu_fd_wait: %s", mu_strerror(rc)); + close(fd); + return -1; + } + + if (flags & MU_STREAM_READY_WR) { + socklen_t len = sizeof(flags); + rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, + &flags, &len); + if (rc) { + mu_error("getsockopt: %s", + mu_strerror(rc)); + close(fd); + return -1; + } + } else + flags = ETIMEDOUT; + } else + flags = errno; + + if (flags) { + mu_error("connect: %s", mu_strerror(flags)); + close(fd); + return -1; + } + } + + if (rc) { + close(fd); + return -1; + } + + return fd; +} + +enum callout_server_state { + css_init, + css_send, + css_quit, +}; + +void +schedule_callout(const char *email, const char *ehlo, const char *mailfrom, + const char *client_addr) +{ + size_t size = 0; + char *buf = NULL; + int fd = connect_callout_server(); + enum callout_server_state state = css_init; + int rc; + + while (state != css_quit) { + if (savclt_getline(fd, &buf, &size) < 0) + break; + trimcrlf(buf); + if (!(strlen(buf) > 2 && memcmp(buf, "OK", 2) == 0 + && isspace(buf[2]))) + break; + + switch (state) { + case css_init: + if (client_addr) + rc = savclt_wrtline(fd, &buf, &size, + "SVRFY \"%s\" \"%s\" \"%s\" \"%s\"\r\n", + email, ehlo, mailfrom, client_addr); + else + rc = savclt_wrtline(fd, &buf, &size, + "VRFY \"%s\" \"%s\" \"%s\"\r\n", + email, ehlo, mailfrom); + state = (rc < 0) ? css_quit : css_send; + break; + + case css_send: + savclt_wrtline(fd, &buf, &size, "QUIT\r\n"); + break; + + case css_quit: + break; + } + } + close(fd); + free(buf); +} + |