aboutsummaryrefslogtreecommitdiffstats
path: root/smtpscript/smtpscript.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpscript/smtpscript.c')
-rw-r--r--smtpscript/smtpscript.c1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/smtpscript/smtpscript.c b/smtpscript/smtpscript.c
new file mode 100644
index 00000000..a75c4249
--- /dev/null
+++ b/smtpscript/smtpscript.c
@@ -0,0 +1,1009 @@
+/* $OpenBSD: iobuf.h,v 1.1 2012/01/29 00:32:51 eric Exp $ */
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "iobuf.h"
+
+#include "smtpscript.h"
+
+void *ssl_connect(int);
+void ssl_close(void *);
+
+/* XXX */
+#define SMTP_LINE_MAX 4096
+
+enum {
+ OP_BLOCK,
+ OP_REPEAT,
+ OP_RANDOM,
+
+ OP_NOOP,
+
+ OP_FAIL,
+ OP_CALL,
+ OP_CONNECT,
+ OP_DISCONNECT,
+ OP_STARTTLS,
+ OP_SLEEP,
+ OP_WRITE,
+
+ OP_EXPECT_DISCONNECT,
+ OP_EXPECT_SMTP_RESPONSE,
+};
+
+struct op {
+ struct op *next;
+ int type;
+ union {
+ struct {
+ int count;
+ struct op *start;
+ struct op *last;
+ } block;
+ struct {
+ struct op *op;
+ int count;
+ } repeat;
+ struct {
+ struct op *block;
+ } random;
+ struct {
+ char *reason;
+ } fail;
+ struct {
+ struct procedure *proc;
+ } call;
+ struct {
+ char *hostname;
+ int portno;
+ } connect;
+ struct {
+ unsigned int ms;
+ } sleep;
+ struct {
+ const void *buf;
+ size_t len;
+ } write;
+ struct {
+ int flags;
+ } expect_smtp;
+ } u;
+};
+
+#define RES_OK 0
+#define RES_SKIP 1
+#define RES_FAIL 2
+#define RES_ERROR 3
+
+struct ctx {
+ int sock;
+ void *ssl;
+ struct iobuf iobuf;
+ int lvl;
+
+ int result;
+ char *reason;
+};
+
+static struct op * _op_connect;
+
+int verbose;
+int randomdelay; /* between each testcase */
+int tapout;
+size_t rundelay; /* between each testcase */
+
+static size_t test_pass;
+static size_t test_skip;
+static size_t test_fail;
+static size_t test_error;
+static size_t test_total = 0;
+
+static struct op *op_add_child(struct op *, const struct op *);
+static void run_testcase(struct procedure *);
+static void print_testcase(char *status, char *name, char *reason, char *directive, size_t number);
+static void process_op(struct ctx *, struct op *);
+static const char * parse_smtp_response(char *, size_t, char **, int *);
+
+struct procedure *
+procedure_create(struct script *scr, char *name)
+{
+ struct procedure *p;
+
+ if (procedure_get_by_name(scr, name)) {
+ warnx("procedure \"%s\" already exists", name);
+ return (NULL);
+ }
+
+ p = calloc(1, sizeof *p);
+ TAILQ_INIT(&p->vars);
+ p->name = strdup(name);
+
+ TAILQ_INSERT_TAIL(&scr->procs, p, entry);
+
+ return (p);
+}
+
+struct procedure *
+procedure_get_by_name(struct script *scr, const char *name)
+{
+ struct procedure *p;
+
+ TAILQ_FOREACH(p, &scr->procs, entry)
+ if (!strcmp(name, p->name))
+ return (p);
+
+ return (NULL);
+}
+
+int
+proc_getvaridx(struct procedure *proc, char *name)
+{
+ struct variable *v;
+ int n;
+
+ n = 0;
+ TAILQ_FOREACH(v, &proc->vars, entry) {
+ if (!strcmp(name, v->name))
+ return (n);
+ n++;
+ }
+
+ return (-1);
+}
+
+int
+proc_addvar(struct procedure *proc, char *name)
+{
+ struct variable *v;
+
+ printf("adding variable \"%s\"\n", name);
+
+ if (proc_getvaridx(proc, name) != -1)
+ return (-1);
+ v = calloc(1, sizeof *v);
+ v->name = name;
+ TAILQ_INSERT_TAIL(&proc->vars, v, entry);
+
+ return (proc->varcount++);
+}
+
+struct op *
+op_block(struct op *parent)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_BLOCK;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_repeat(struct op *parent, int count, struct op *op)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_REPEAT;
+ o.u.repeat.count = count;
+ o.u.repeat.op = op;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_random(struct op *parent, struct op *op)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_RANDOM;
+ o.u.random.block = op;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_noop(struct op *parent)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_NOOP;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_call(struct op *parent, struct procedure *proc)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_CALL;
+ o.u.call.proc = proc;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_fail(struct op *parent, char *reason)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_FAIL;
+ o.u.fail.reason = reason;
+
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_connect(struct op *parent, const char *hostname, int portno)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_CONNECT;
+ o.u.connect.hostname = strdup(hostname);
+ o.u.connect.portno = portno;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_disconnect(struct op *parent)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_DISCONNECT;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_starttls(struct op *parent)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_STARTTLS;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_sleep(struct op *parent, unsigned int ms)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_SLEEP;
+ o.u.sleep.ms = ms;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_write(struct op *parent, const void *buf, size_t len)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_WRITE;
+ o.u.write.buf = buf;
+ o.u.write.len = len;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_printf(struct op *parent, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+ int len;
+
+ va_start(ap, fmt);
+ if ((len = vasprintf(&buf, fmt, ap)) == -1)
+ err(1, "vasprintf");
+ va_end(ap);
+
+ return op_write(parent, buf, len);
+}
+
+struct op *
+op_expect_disconnect(struct op *parent)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_EXPECT_DISCONNECT;
+ return (op_add_child(parent, &o));
+}
+
+struct op *
+op_expect_smtp_response(struct op *parent, int flags)
+{
+ struct op o;
+
+ bzero(&o, sizeof o);
+ o.type = OP_EXPECT_SMTP_RESPONSE;
+ o.u.expect_smtp.flags = flags;
+ return (op_add_child(parent, &o));
+}
+
+static void
+usage(void)
+{
+ extern const char *__progname;
+ errx(1, "Usage: %s [-rvt] [-d delay] script", __progname);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct script *s;
+ struct procedure *p;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "d:rvt")) != -1) {
+ switch(ch) {
+ case 'v':
+ verbose += 1;
+ break;
+ case 'd':
+ rundelay = atoi(optarg) * 1000;
+ break;
+ case 'r':
+ randomdelay = 1;
+ break;
+ case 't':
+ tapout = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ s = parse_script(argv[0]);
+ if (s == NULL)
+ errx(1, "error reading script file");
+
+ _op_connect = op_connect(NULL, "127.0.0.1", 25);
+
+ if (tapout) {
+ printf("# smtpscript is an SMTP testing framework\n\n");
+ printf("TAP version 13\n");
+ }
+
+ TAILQ_FOREACH(p, &s->procs, entry)
+ if (p->flags & PROC_TESTCASE)
+ run_testcase(p);
+
+ if (tapout)
+ printf("1..%zu\n", test_total);
+ else {
+ printf("passed: %zu/%zu (skipped: %zu, failed: %zu, error: %zu)\n",
+ test_pass,
+ test_total,
+ test_skip,
+ test_fail,
+ test_error);
+ }
+ return (0);
+}
+
+static struct op *
+op_add_child(struct op *parent, const struct op *op)
+{
+ struct op *n;
+
+ n = malloc(sizeof *n);
+ if (n == NULL)
+ err(1, "malloc");
+
+ memmove(n, op, sizeof *n);
+ n->next = NULL;
+
+ /* printf("op:%p type:%i parent: %p\n", n, n->type, parent); */
+
+ if (parent) {
+ if (parent->u.block.start == NULL)
+ parent->u.block.start = n;
+ if (parent->u.block.last)
+ parent->u.block.last->next = n;
+ parent->u.block.last = n;
+ parent->u.block.count += 1;
+ }
+
+ return (n);
+}
+
+static void
+run_testcase(struct procedure *proc)
+{
+ struct ctx c;
+ uint32_t rdelay;
+
+ bzero(&c, sizeof c);
+ c.sock = -1;
+ c.lvl = 1;
+
+ if (rundelay) {
+ if (randomdelay)
+ rdelay = arc4random_uniform(rundelay);
+ else
+ rdelay = rundelay;
+ usleep(rdelay);
+ }
+
+ fflush(stdout);
+
+ if (verbose > 1)
+ printf("\n");
+
+ if (!(proc->flags & PROC_NOCONNECT))
+ process_op(&c, _op_connect);
+ process_op(&c, proc->root);
+
+ if (c.sock != -1)
+ close(c.sock);
+ if (c.ssl)
+ ssl_close(c.ssl);
+ iobuf_clear(&c.iobuf);
+
+ if (verbose > 1) {
+ printf("# Done with test-case \"%s\": ", proc->name);
+ }
+
+ switch (c.result) {
+ case RES_OK:
+ test_total += 1;
+ if (proc->flags & PROC_EXPECTFAIL) {
+ print_testcase("not ok", proc->name, c.reason, "TODO", test_total); // XPass
+ test_fail += 1;
+ } else if (proc->flags & PROC_SKIP) {
+ test_skip += 1;
+ print_testcase("ok", proc->name, c.reason, "SKIP", test_total);
+ }
+ else {
+ print_testcase("ok", proc->name, c.reason, NULL, test_total);
+ test_pass += 1;
+ }
+
+ break;
+
+ case RES_SKIP:
+ test_skip += 1;
+ test_total += 1;
+ print_testcase("not ok", proc->name, c.reason, "SKIP", test_total);
+ break;
+
+ case RES_FAIL:
+ test_total += 1;
+ if (proc->flags & PROC_EXPECTFAIL) {
+ print_testcase("not ok", proc->name, c.reason, "TODO", test_total); // XFail
+ test_pass += 1;
+ } else if (proc->flags & PROC_SKIP) {
+ test_skip += 1;
+ print_testcase("ok", proc->name, c.reason, "SKIP", test_total);
+ }
+ else {
+ print_testcase("not ok", proc->name, c.reason, NULL, test_total);
+ test_fail += 1;
+ }
+
+ break;
+
+ case RES_ERROR:
+ test_error += 1;
+ test_total += 1;
+ print_testcase("not ok", proc->name, c.reason, NULL, test_total);
+ break;
+ }
+
+ if (verbose > 1) {
+ printf("\n");
+ }
+
+}
+
+void print_testcase(char *status, char *name, char *reason, char *directive, size_t number)
+{
+ printf("%s %zu", status, number);
+ if (directive)
+ printf(" - %s # %s\n", name, directive);
+ else
+ if (reason)
+ printf(" - %s # %s\n", name, reason);
+ else
+ printf(" - %s\n", name);
+}
+
+static size_t
+strvisx2(char *dst, const char *src, size_t srclen, int flag)
+{
+ size_t n, r, i;
+
+ n = strvisx(dst, src, srclen, flag);
+ if (n == 0)
+ return (0);
+
+ r = n;
+ for (i = n - 1; i; i--) {
+ if (dst[i] == '\r') {
+ memmove(dst + i + 2, dst + i + 1, n + 1 - i);
+ dst[i+1] = 'r';
+ dst[i] = '\\';
+ r++;
+ } else if (dst[i] == '"') {
+ memmove(dst + i + 2, dst + i + 1, n + 1 - i);
+ dst[i+1] = '"';
+ dst[i] = '\\';
+ r++;
+ }
+ }
+
+ return (r);
+}
+
+static const char *
+show_data(const char *src, size_t len, size_t max)
+{
+ static char buf[8192 + 3];
+ char tmp[256];
+ size_t l, n;
+
+ l = len;
+ if (len > 2048)
+ l = 2048;
+
+ buf[0] = '"';
+ n = strvisx2(&buf[1], src, l, VIS_SAFE | VIS_NL | VIS_TAB | VIS_CSTYLE);
+ if (n >= max) {
+ snprintf(tmp, sizeof tmp, "...\" [%zu]", l);
+ buf[max - strlen(tmp)] = '\0';
+ strlcat(buf, tmp, sizeof(buf));
+ } else {
+ strlcat(buf, "\"", sizeof(buf));
+ }
+
+ return (buf);
+}
+
+static void
+print_op(struct op *op, int lvl)
+{
+
+
+ if (op->type == OP_BLOCK)
+ return;
+
+ while (lvl--)
+ printf(" ");
+
+ switch(op->type) {
+
+ case OP_REPEAT:
+ printf("=> repeat: %i\n", op->u.repeat.count);
+ break;
+
+ case OP_RANDOM:
+ printf("=> random: %i\n", op->u.random.block->u.block.count);
+ break;
+
+ case OP_NOOP:
+ printf("=> noop\n");
+ break;
+
+ case OP_FAIL:
+ printf("=> fail: %s\n", op->u.fail.reason);
+ break;
+
+ case OP_CALL:
+ printf("=> call: %s\n", op->u.call.proc->name);
+ break;
+
+ case OP_CONNECT:
+ printf("=> connect %s:%i\n",
+ op->u.connect.hostname,
+ op->u.connect.portno);
+ break;
+
+ case OP_DISCONNECT:
+ printf("=> disconnect\n");
+ break;
+
+ case OP_STARTTLS:
+ printf("=> starttls\n");
+ break;
+
+ case OP_SLEEP:
+ printf("=> sleep %ims\n", op->u.sleep.ms);
+ break;
+
+ case OP_WRITE:
+ printf("=> write %s\n",
+ show_data(op->u.write.buf, op->u.write.len, 70));
+ break;
+
+ case OP_EXPECT_DISCONNECT:
+ printf("<= disconnect\n");
+ break;
+
+ case OP_EXPECT_SMTP_RESPONSE:
+ printf("<= smtp-response 0x%04x\n", op->u.expect_smtp.flags);
+ break;
+
+ default:
+ printf("<> ??? %i;\n", op->type);
+ break;
+ }
+}
+
+
+static void
+set_failure(struct ctx *ctx, int res, const char *fmt, ...)
+{
+ va_list ap;
+ int len;
+
+ ctx->result = res;
+ va_start(ap, fmt);
+ if ((len = vasprintf(&ctx->reason, fmt, ap)) == -1)
+ err(1, "vasprintf");
+ va_end(ap);
+}
+
+static void
+process_op(struct ctx *ctx, struct op *op)
+{
+ struct addrinfo hints, *a, *ai;
+ struct op *o;
+ struct iobuf *iobuf;
+ int i, r, s, save_errno, cont;
+ const char *cause;
+ char buf[16], *servname, *line;
+ ssize_t n;
+ size_t len;
+ const char *e;
+
+ if (verbose > 1)
+ print_op(op, ctx->lvl);
+
+ iobuf = &ctx->iobuf;
+
+ switch(op->type) {
+
+ case OP_BLOCK:
+ ctx->lvl += 1;
+ for (o = op->u.block.start; o; o = o->next) {
+ process_op(ctx, o);
+ if (ctx->result)
+ break;
+ }
+ ctx->lvl -= 1;
+ break;
+
+ case OP_REPEAT:
+ ctx->lvl += 1;
+ for (i = 0; i < op->u.repeat.count; i++) {
+ process_op(ctx, op->u.repeat.op);
+ if (ctx->result)
+ break;
+ }
+ ctx->lvl -= 1;
+ break;
+
+ case OP_RANDOM:
+
+ if (op->u.random.block->u.block.count == 0)
+ return;
+
+ ctx->lvl += 1;
+
+ i = arc4random_uniform(op->u.random.block->u.block.count);
+ for (o = op->u.random.block->u.block.start; i; i--, o = o->next)
+ ;
+ process_op(ctx, o);
+ if (ctx->result)
+ break;
+ ctx->lvl -= 1;
+ break;
+
+ case OP_NOOP:
+ break;
+
+ case OP_FAIL:
+ set_failure(ctx, RES_FAIL, op->u.fail.reason);
+ break;
+
+ case OP_CALL:
+ process_op(ctx, op->u.call.proc->root);
+ break;
+
+ case OP_CONNECT:
+ if (ctx->sock != -1)
+ close(ctx->sock);
+ ctx->sock = -1;
+ iobuf_clear(iobuf);
+
+ servname = NULL;
+ if (op->u.connect.portno) {
+ snprintf(buf, sizeof buf, "%i", op->u.connect.portno);
+ servname = buf;
+ }
+ bzero(&hints, sizeof hints);
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ r = getaddrinfo(op->u.connect.hostname, servname, &hints, &ai);
+ if (r) {
+ set_failure(ctx, RES_ERROR,
+ "failed to connect to %s:%s: %s",
+ op->u.connect.hostname, servname, gai_strerror(r));
+ return;
+ }
+
+ s = -1;
+ for(a = ai; a; a = a->ai_next) {
+ s = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
+ if (s == -1) {
+ cause = "socket";
+ continue;
+ }
+ if (connect(s, a->ai_addr, a->ai_addrlen) == -1) {
+ cause = "connect";
+ save_errno = errno;
+ close(s);
+ errno = save_errno;
+ s = -1;
+ continue;
+ }
+ break; /* okay we got one */
+ }
+ freeaddrinfo(ai);
+ if (s == -1) {
+ set_failure(ctx, RES_ERROR,
+ "failed to connect to %s:%s: %s",
+ op->u.connect.hostname, servname, cause);
+ } else {
+ ctx->sock = s;
+ iobuf_init(iobuf, 0, 0);
+ }
+ break;
+
+ case OP_DISCONNECT:
+ if (ctx->sock != -1)
+ close(ctx->sock);
+ ctx->sock = -1;
+ iobuf_clear(iobuf);
+ break;
+
+ case OP_STARTTLS:
+ if (ctx->ssl)
+ set_failure(ctx, RES_ERROR, "SSL context already here");
+ else if ((ctx->ssl = ssl_connect(ctx->sock)) == NULL)
+ set_failure(ctx, RES_ERROR, "SSL connection failed");
+ break;
+
+ case OP_SLEEP:
+ usleep(op->u.sleep.ms * 1000);
+ break;
+
+ case OP_WRITE:
+ iobuf_queue(iobuf, op->u.write.buf, op->u.write.len);
+ if (ctx->ssl)
+ r = iobuf_flush_ssl(iobuf, ctx->ssl);
+ else
+ r = iobuf_flush(iobuf, ctx->sock);
+ switch (r) {
+ case 0:
+ break;
+ case IOBUF_CLOSED:
+ set_failure(ctx, RES_FAIL, "connection closed");
+ break;
+ case IOBUF_WANT_WRITE:
+ set_failure(ctx, RES_ERROR, "iobuf_write(): WANT_WRITE");
+ break;
+ case IOBUF_ERROR:
+ set_failure(ctx, RES_ERROR, "IO error");
+ break;
+ case IOBUF_SSLERROR:
+ set_failure(ctx, RES_ERROR, "SSL error");
+ break;
+ default:
+ set_failure(ctx, RES_ERROR, "iobuf_write(): bad value");
+ break;
+ }
+ break;
+
+ case OP_EXPECT_DISCONNECT:
+ if (iobuf_len(iobuf)) {
+ set_failure(ctx, RES_ERROR, "%zu bytes of input left",
+ iobuf_len(iobuf));
+ break;
+ }
+ if (ctx->ssl)
+ n = iobuf_read_ssl(iobuf, ctx->ssl);
+ else
+ n = iobuf_read(iobuf, ctx->sock);
+ switch (n) {
+ case IOBUF_CLOSED:
+ close(ctx->sock);
+ ctx->sock = -1;
+ if (ctx->ssl)
+ ssl_close(ctx->ssl);
+ break;
+ case IOBUF_WANT_READ:
+ set_failure(ctx, RES_ERROR, "iobuf_read(): WANT_READ");
+ break;
+ case IOBUF_ERROR:
+ set_failure(ctx, RES_ERROR, "IO error");
+ break;
+ case IOBUF_SSLERROR:
+ set_failure(ctx, RES_ERROR, "SSL error");
+ break;
+ default:
+ set_failure(ctx, RES_FAIL, "data read: %s",
+ show_data(iobuf_data(iobuf), iobuf_len(iobuf), 70));
+ break;
+ }
+ break;
+
+ case OP_EXPECT_SMTP_RESPONSE:
+ line = NULL;
+ while (1) {
+ line = iobuf_getline(iobuf, &len);
+ if (line) {
+ e = parse_smtp_response(line, len, NULL, &cont);
+ if (e) {
+ set_failure(ctx, RES_FAIL, e);
+ return;
+ }
+ if (!cont) {
+ iobuf_normalize(iobuf);
+ break;
+ }
+ if (!(op->u.expect_smtp.flags
+ & RESP_SMTP_MULTILINE)) {
+ set_failure(ctx, RES_FAIL,
+ "single line response expected");
+ return;
+ }
+ continue;
+ }
+
+ if (iobuf_len(iobuf) >= SMTP_LINE_MAX) {
+ set_failure(ctx, RES_FAIL, "line too long");
+ return;
+ }
+
+ iobuf_normalize(iobuf);
+
+ again:
+ if (ctx->ssl)
+ n = iobuf_read_ssl(iobuf, ctx->ssl);
+ else
+ n = iobuf_read(iobuf, ctx->sock);
+ switch (n) {
+ case IOBUF_CLOSED:
+ set_failure(ctx, RES_FAIL, "connection closed");
+ return;
+ case IOBUF_WANT_READ:
+ goto again;
+ case IOBUF_ERROR:
+ set_failure(ctx, RES_ERROR, "io error");
+ return;
+ case IOBUF_SSLERROR:
+ set_failure(ctx, RES_ERROR, "SSL error");
+ return;
+ default:
+ break;
+ }
+ }
+
+ /* got our response */
+
+ if (verbose > 1) {
+ len = ctx->lvl;
+ while (len--)
+ printf(" ");
+ printf(" >>> %s\n", show_data(line, strlen(line), 70));
+ }
+
+ switch (line[0]) {
+ case '2':
+ case '3':
+ if (!(op->u.expect_smtp.flags & RESP_SMTP_OK))
+ set_failure(ctx, RES_FAIL,
+ "unexpected response code0: %s", line);
+ break;
+ case '4':
+ if (!(op->u.expect_smtp.flags & RESP_SMTP_TEMPFAIL))
+ set_failure(ctx, RES_FAIL,
+ "unexpected response code1: %s", line);
+ break;
+ case '5':
+ if (!(op->u.expect_smtp.flags & RESP_SMTP_PERMFAIL))
+ set_failure(ctx, RES_FAIL,
+ "unexpected response code2: %s", line);
+ break;
+ default:
+ set_failure(ctx, RES_FAIL,
+ "unexpected response code???: %s", line);
+ break;
+ }
+ break;
+
+ default:
+ ctx->result = RES_ERROR;
+ ctx->reason = "invalid operator";
+ break;
+ }
+}
+
+static const char *
+parse_smtp_response(char *line, size_t len, char **msg, int *cont)
+{
+ size_t i;
+
+ if (len >= SMTP_LINE_MAX)
+ return "line too long";
+
+ if (len > 3) {
+ if (msg)
+ *msg = line + 4;
+ if (cont)
+ *cont = (line[3] == '-');
+ } else if (len == 3) {
+ if (msg)
+ *msg = line + 3;
+ if (cont)
+ *cont = 0;
+ } else
+ return "line too short";
+
+ /* validate reply code */
+ if (line[0] < '2' || line[0] > '5' || !isdigit(line[1]) ||
+ !isdigit(line[2]))
+ return "reply code out of range";
+
+ /* validate reply message */
+ for (i = 0; i < len; i++)
+ if (!isprint(line[i]))
+ return "non-printable character in reply";
+
+ return NULL;
+}