summaryrefslogtreecommitdiff
path: root/src/clio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/clio.c')
-rw-r--r--src/clio.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/src/clio.c b/src/clio.c
new file mode 100644
index 0000000..f2d53cd
--- /dev/null
+++ b/src/clio.c
@@ -0,0 +1,367 @@
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+
+#include "usuals.h"
+#include "sock_any.h"
+#include "clamsmtpd.h"
+#include "util.h"
+
+#define MAX_LOG_LINE 79
+#define IO_UNKNOWN "??? "
+
+static void close_raw(int* fd)
+{
+ ASSERT(fd);
+ shutdown(*fd, SHUT_RDWR);
+ close(*fd);
+ *fd = -1;
+}
+
+static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, int read)
+{
+ char buf[MAX_LOG_LINE + 1];
+ int pos, len;
+
+ ASSERT(ctx && io && data);
+
+ for(;;)
+ {
+ data += strspn(data, "\r\n");
+
+ if(!*data)
+ break;
+
+ pos = strcspn(data, "\r\n");
+
+ len = pos < MAX_LOG_LINE ? pos : MAX_LOG_LINE;
+ memcpy(buf, data, len);
+ buf[len] = 0;
+
+ messagex(ctx, LOG_DEBUG, "%s%s%s",
+ io->name ? io->name : IO_UNKNOWN,
+ read ? " < " : " > ", buf);
+
+ data += pos;
+ }
+}
+
+void clio_init(clio_t* io, const char* name)
+{
+ ASSERT(io && name);
+ memset(io, 0, sizeof(*io));
+ io->name = name;
+ io->fd = -1;
+}
+
+int clio_connect(clamsmtp_context_t* ctx, clio_t* io, struct sockaddr_any* sany,
+ const char* addrname)
+{
+ int ret = 0;
+
+ ASSERT(ctx && io && sany && addrname);
+ ASSERT(io->fd == -1);
+
+ if((io->fd = socket(SANY_TYPE(*sany), SOCK_STREAM, 0)) == -1)
+ RETURN(-1);
+
+ if(setsockopt(io->fd, SOL_SOCKET, SO_RCVTIMEO, &g_timeout, sizeof(g_timeout)) == -1 ||
+ setsockopt(io->fd, SOL_SOCKET, SO_SNDTIMEO, &g_timeout, sizeof(g_timeout)) == -1)
+ messagex(ctx, LOG_WARNING, "couldn't set timeouts on connection");
+
+ if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1)
+ RETURN(-1);
+
+cleanup:
+ if(ret < 0)
+ {
+ if(io->fd != -1)
+ close(io->fd);
+
+ message(ctx, LOG_ERR, "couldn't connect to: %s", addrname);
+ return -1;
+ }
+
+ ASSERT(io->fd != -1);
+ messagex(ctx, LOG_DEBUG, "%s connected to: %s",
+ io->name ? io->name : IO_UNKNOWN, addrname);
+ return 0;
+}
+
+void clio_disconnect(clamsmtp_context_t* ctx, clio_t* io)
+{
+ ASSERT(ctx && io);
+
+ if(clio_valid(io))
+ {
+ close_raw(&(io->fd));
+ messagex(ctx, LOG_NOTICE, "%s connection closed",
+ io->name ? io->name : IO_UNKNOWN);
+ }
+}
+
+int clio_select(clamsmtp_context_t* ctx, clio_t** io)
+{
+ fd_set mask;
+
+ ASSERT(ctx && io);
+ FD_ZERO(&mask);
+ *io = NULL;
+
+ /* First check if buffers have any data */
+
+ if(clio_valid(&(ctx->server)))
+ {
+ if(ctx->server.buflen > 0)
+ {
+ *io = &(ctx->server);
+ return 0;
+ }
+
+ FD_SET(ctx->server.fd, &mask);
+ }
+
+ if(clio_valid(&(ctx->client)))
+ {
+ if(ctx->client.buflen > 0)
+ {
+ *io = &(ctx->client);
+ return 0;
+ }
+
+ FD_SET(ctx->client.fd, &mask);
+ }
+
+ /* Select on the above */
+
+ switch(select(FD_SETSIZE, &mask, NULL, NULL, &g_timeout))
+ {
+ case 0:
+ messagex(ctx, LOG_ERR, "network operation timed out");
+ return -1;
+ case -1:
+ message(ctx, LOG_ERR, "couldn't select on sockets");
+ return -1;
+ };
+
+ /* See what came in */
+
+ if(FD_ISSET(ctx->server.fd, &mask))
+ {
+ *io = &(ctx->server);
+ return 0;
+ }
+
+ if(FD_ISSET(ctx->client.fd, &mask))
+ {
+ *io = &(ctx->client);
+ return 0;
+ }
+
+ ASSERT(0 && "invalid result from select");
+ return -1;
+}
+
+int clio_read_line(clamsmtp_context_t* ctx, clio_t* io, int opts)
+{
+ int l, x;
+ int bufused;
+ char* t;
+ unsigned char* p;
+
+ ASSERT(ctx && io);
+
+ if(!clio_valid(io))
+ {
+ messagex(ctx, LOG_WARNING, "tried to read from a closed connection");
+ return 0;
+ }
+
+ ctx->line[0] = 0;
+ t = ctx->line;
+ l = LINE_LENGTH - 1;
+
+ for(;;)
+ {
+ /* refil buffer if necessary */
+ if(io->buflen == 0)
+ {
+ ASSERT(io->fd != -1);
+ io->buflen = read(io->fd, io->buf, sizeof(char) * BUF_LEN);
+
+ if(io->buflen == -1)
+ {
+ io->buflen = 0;
+
+ if(errno == EINTR)
+ {
+ /* When the application is quiting */
+ if(g_quit)
+ return -1;
+
+ /* For any other signal we go again */
+ continue;
+ }
+
+ /*
+ * The basic logic here is that if we've had a fatal error
+ * reading from the socket once then we shut it down as it's
+ * no good trying to read from again later.
+ */
+ close_raw(&(io->fd));
+
+ if(errno == EAGAIN)
+ messagex(ctx, LOG_WARNING, "network read operation timed out");
+ else
+ message(ctx, LOG_ERR, "couldn't read data from socket");
+
+ return -1;
+ }
+ }
+
+ /* End of data */
+ if(io->buflen == 0)
+ break;
+
+ /* Check for a new line */
+ p = (unsigned char*)memchr(io->buf, '\n', io->buflen);
+
+ if(p != NULL)
+ {
+ x = (p - io->buf) + 1;
+ io->buflen -= x;
+ }
+
+ else
+ {
+ x = io->buflen;
+ io->buflen = 0;
+ }
+
+ if(x > l)
+ x = l;
+
+ /* Copy from buffer line */
+ memcpy(t, io->buf, x);
+ t += x;
+ l -= x;
+
+ /* Move whatever we have in the buffer to the front */
+ if(io->buflen > 0)
+ memmove(io->buf, io->buf + x, io->buflen);
+
+ /* Found a new line, done */
+ if(p != NULL)
+ break;
+
+ /* If discarding then don't break when full */
+ if(!(opts && CLIO_DISCARD) && l == 0)
+ break;
+ }
+
+ ctx->linelen = (LINE_LENGTH - l) - 1;
+ ASSERT(ctx->linelen < LINE_LENGTH);
+ ctx->line[ctx->linelen] = 0;
+
+ if(opts & CLIO_TRIM && ctx->linelen > 0)
+ {
+ t = ctx->line;
+
+ while(*t && isspace(*t))
+ t++;
+
+ /* Bump the entire line down */
+ l = t - ctx->line;
+ memmove(ctx->line, t, (ctx->linelen + 1) - l);
+ ctx->linelen -= l;
+
+ /* Now the end */
+ t = ctx->line + ctx->linelen;
+
+ while(t > ctx->line && isspace(*(t - 1)))
+ {
+ *(--t) = 0;
+ ctx->linelen--;
+ }
+ }
+
+ if(!(opts & CLIO_QUIET))
+ log_io_data(ctx, io, ctx->line, 1);
+
+ return ctx->linelen;
+}
+
+int clio_write_data(clamsmtp_context_t* ctx, clio_t* io, const char* data)
+{
+ int len = strlen(data);
+ ASSERT(ctx && io && data);
+
+ if(!clio_valid(io))
+ {
+ message(ctx, LOG_ERR, "connection closed. can't write data.");
+ return -1;
+ }
+
+ log_io_data(ctx, io, data, 0);
+ return clio_write_data_raw(ctx, io, (unsigned char*)data, len);
+}
+
+int clio_write_data_raw(clamsmtp_context_t* ctx, clio_t* io, unsigned char* buf, int len)
+{
+ int r;
+
+ ASSERT(ctx && io && buf);
+
+ if(io->fd == -1)
+ return 0;
+
+ while(len > 0)
+ {
+ r = write(io->fd, buf, len);
+
+ if(r > 0)
+ {
+ buf += r;
+ len -= r;
+ }
+
+ else if(r == -1)
+ {
+ if(errno == EINTR)
+ {
+ /* When the application is quiting */
+ if(g_quit)
+ return -1;
+
+ /* For any other signal we go again */
+ continue;
+ }
+
+ /*
+ * The basic logic here is that if we've had a fatal error
+ * writing to the socket once then we shut it down as it's
+ * no good trying to write to it again later.
+ */
+ close_raw(&(io->fd));
+
+ if(errno == EAGAIN)
+ messagex(ctx, LOG_WARNING, "network write operation timed out");
+ else
+ message(ctx, LOG_ERR, "couldn't write data to socket");
+
+ return -1;
+ }
+ }
+
+ return 0;
+}