diff options
author | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-08-27 11:41:27 +0000 |
---|---|---|
committer | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-08-27 11:41:27 +0000 |
commit | dfabc426d37833f596a08a2a5bfa7943afaab28e (patch) | |
tree | c90583f66c9f150dd7da350777f5e4f06be2424c | |
parent | 76ef2b123533bc36f7481ba9f9560953a6a1ba18 (diff) |
Implement client side of the SMTP protocol in a library-like module.
Make bounce code and /usr/sbin/sendmail interface use this new API.
The mta process continues to use its own implementation, but
eventually will be switched to use this shared module.
Buffer routines are taken from buffer.c rather than from evbuffer.
This is one step forward to using a single buffer API across the
program.
"it looks sexy" gilles@
-rw-r--r-- | usr.sbin/smtpd/client.c | 529 | ||||
-rw-r--r-- | usr.sbin/smtpd/client.h | 59 |
2 files changed, 588 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/client.c b/usr.sbin/smtpd/client.c new file mode 100644 index 00000000000..abe261fbc12 --- /dev/null +++ b/usr.sbin/smtpd/client.c @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2002, 2003 Niels Provos <provos@citi.umich.edu> + * + * 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/queue.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/uio.h> + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "imsg.h" +#include "client.h" + +int client_readln(struct smtp_client *, char **); +int client_writeln(struct smtp_client *, char *, ...); +int client_error(struct smtp_client *, char **, char *); +int client_data_add(struct smtp_client *, char *, size_t); +char *buf_getln(struct buf_read *); + +struct smtp_client * +client_init(int fd, char *ehlo) +{ + struct smtp_client *sp = NULL; + int rv = -1; + + if ((sp = calloc(1, sizeof(*sp))) == NULL) + goto done; + if ((sp->ehlo = strdup(ehlo)) == NULL) + goto done; + if ((sp->data = buf_dynamic(0, SIZE_T_MAX)) == NULL) + goto done; + sp->state = CLIENT_INIT; + msgbuf_init(&sp->w); + sp->w.fd = fd; + TAILQ_INIT(&sp->recipients); + + rv = 0; +done: + if (rv < 0) { + free(sp->ehlo); + if (sp->data) + buf_free(sp->data); + free(sp); + sp = NULL; + } + return (sp); +} + +void +client_verbose(struct smtp_client *sp, int fd) +{ + sp->verbose = fdopen(fd, "w"); +} + +int +client_sender(struct smtp_client *sp, char *fmt, ...) +{ + va_list ap; + char *mbox = NULL; + int rv = -1; + + va_start(ap, fmt); + + /* check if too late */ + switch (sp->state) { + case CLIENT_INIT: + case CLIENT_EHLO: + case CLIENT_HELO: + break; + default: + goto done; + } + + if (vasprintf(&mbox, fmt, ap) == -1) + goto done; + if (sp->sender) + free(sp->sender); + sp->sender = mbox; + + rv = 0; +done: + va_end(ap); + if (rv < 0) + free(mbox); + return (rv); +} + +int +client_rcpt(struct smtp_client *sp, char *fmt, ...) +{ + va_list ap; + struct rcpt *rp = NULL; + int rv = -1; + + va_start(ap, fmt); + + /* check if too late */ + switch (sp->state) { + case CLIENT_INIT: + case CLIENT_EHLO: + case CLIENT_HELO: + case CLIENT_MAILFROM: + break; + default: + goto done; + } + + if ((rp = calloc(1, sizeof(*rp))) == NULL) + goto done; + if (vasprintf(&rp->mbox, fmt, ap) == -1) + goto done; + TAILQ_INSERT_TAIL(&sp->recipients, rp, entry); + + rv = 0; +done: + va_end(ap); + if (rv < 0) { + if (rp) + free(rp->mbox); + free(rp); + } + return (rv); +} + +int +client_data_fd(struct smtp_client *sp, int fd) +{ + struct stat sb; + char *map = NULL; + int rv = -1; + + if (fstat(fd, &sb) == -1) + goto done; + if (sb.st_size > SIZE_T_MAX) + goto done; + if (!S_ISREG(sb.st_mode)) + goto done; + map = mmap(NULL, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, + (off_t)0); + if (map == MAP_FAILED) + goto done; + madvise(map, sb.st_size, MADV_SEQUENTIAL); + + if (client_data_add(sp, map, sb.st_size) < 0) + goto done; + + rv = 0; +done: + close(fd); + if (map) + munmap(map, sb.st_size); + return (rv); +} + +int +client_data_printf(struct smtp_client *sp, char *fmt, ...) +{ + va_list ap; + char *p = NULL; + int len, rv = -1; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + goto done; + + if (client_data_add(sp, p, len) < 0) + goto done; + + rv = 0; +done: + va_end(ap); + free(p); + return (rv); +} + +int +client_read(struct smtp_client *sp, char **ep) +{ + char *reply; + + *ep = NULL; + + /* read server reply */ + if (client_readln(sp, &reply) < 0) + return client_error(sp, ep, reply); + if (reply == NULL) + return (CLIENT_WANT_READ); + + /* send command */ + switch (sp->state) { + case CLIENT_INIT: + if (*reply != '2') + client_error(sp, ep, reply); + else + sp->state = CLIENT_EHLO; + break; + + case CLIENT_EHLO: + if (*reply != '2') + sp->state = CLIENT_HELO; + else + sp->state = CLIENT_MAILFROM; + break; + + case CLIENT_HELO: + if (*reply != '2') + client_error(sp, ep, reply); + else + sp->state = CLIENT_MAILFROM; + break; + + case CLIENT_MAILFROM: + if (*reply != '2') + client_error(sp, ep, reply); + else + sp->state = CLIENT_RCPTTO; + break; + + case CLIENT_RCPTTO: + if (*reply != '2') + client_error(sp, ep, reply); + else if (TAILQ_EMPTY(&sp->recipients)) + sp->state = CLIENT_DATA; + break; + + case CLIENT_DATA: + if (*reply != '3') + client_error(sp, ep, reply); + else + sp->state = CLIENT_DATA_BODY; + break; + + case CLIENT_DATA_BODY: + if (*reply != '2') + client_error(sp, ep, reply); + else + return (CLIENT_DONE); + break; + + default: + client_error(sp, ep, "invalid state"); + } + free(reply); + + /* may have been set by client_error */ + if (*ep) + return (CLIENT_ERROR); + + return (CLIENT_WANT_WRITE); +} + +int +client_write(struct smtp_client *sp, char **ep) +{ + struct rcpt *rp; + + *ep = NULL; + + /* complete any pending write */ + if (sp->w.queued) + goto write; + + switch (sp->state) { + case CLIENT_EHLO: + if (client_writeln(sp, "EHLO %s", sp->ehlo) < 0) + return client_error(sp, ep, NULL); + break; + + case CLIENT_HELO: + if (client_writeln(sp, "HELO %s", sp->ehlo) < 0) + return client_error(sp, ep, NULL); + break; + + case CLIENT_MAILFROM: + if (client_writeln(sp, "MAIL FROM:<%s>", sp->sender) < 0) + return client_error(sp, ep, NULL); + break; + + case CLIENT_RCPTTO: + rp = TAILQ_FIRST(&sp->recipients); + TAILQ_REMOVE(&sp->recipients, rp, entry); + if (client_writeln(sp, "RCPT TO:<%s>", rp->mbox) < 0) + return client_error(sp, ep, NULL); + free(rp->mbox); + free(rp); + break; + + case CLIENT_DATA: + if (client_writeln(sp, "DATA") < 0) + return client_error(sp, ep, NULL); + break; + + case CLIENT_DATA_BODY: + if (buf_add(sp->data, ".\r\n", 3) == -1) + return client_error(sp, ep, NULL); + buf_close(&sp->w, sp->data); + sp->data = NULL; + break; + + default: + return client_error(sp, ep, "invalid state"); + } + +write: + if (buf_write(&sp->w) < 0) + return client_error(sp, ep, NULL); + + return (sp->w.queued ? CLIENT_WANT_WRITE : CLIENT_WANT_READ); +} + +void +client_close(struct smtp_client *sp) +{ + struct rcpt *rp; + + free(sp->ehlo); + free(sp->sender); + if (sp->data) + buf_free(sp->data); + msgbuf_clear(&sp->w); + while ((rp = TAILQ_FIRST(&sp->recipients))) { + TAILQ_REMOVE(&sp->recipients, rp, entry); + free(rp->mbox); + free(rp); + } + close(sp->w.fd); + free(sp); +} + +int +client_readln(struct smtp_client *sp, char **reply) +{ + struct buf_read *r = &sp->r; + size_t i; + char *ln = NULL; + int n, rv = -1; + + *reply = NULL; + + /* read data from the socket */ + n = read(sp->w.fd, r->buf + r->wpos, sizeof(r->buf) - r->wpos); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + rv = 0; + else + *reply = strerror(errno); + goto done; + } + r->wpos += n; + + /* get a reply, dealing with multiline responses */ + for (;;) { + errno = 0; + if ((ln = buf_getln(r)) == NULL) { + if (errno) + *reply = strerror(errno); + else if (r->wpos >= sizeof(r->buf)) + *reply = "reply too big"; + else + rv = 0; + goto done; + } + + if (sp->verbose) + fprintf(sp->verbose, "<<< %s\n", ln); + + if (strlen(ln) == 3 || ln[3] == ' ') + break; + else if (ln[3] != '-') { + *reply = "garbled multiline reply"; + goto done; + } + } + + if (ln[0] < '2' || ln[0] > '5' || !isdigit(ln[1]) || !isdigit(ln[2])) { + *reply = "reply code out of range"; + goto done; + } + + for (i = 0; ln[i] != '\0'; i++) { + if (!isprint(ln[i])) { + *reply = "non-printable character in server reply"; + goto done; + } + } + + *reply = ln; + + rv = 0; +done: + if (rv < 0) + free(ln); + return (rv); +} + +int +client_writeln(struct smtp_client *sp, char *fmt, ...) +{ + struct buf *cmd = NULL; + char *p = NULL; + int len, rv = -1; + va_list ap; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + goto done; + + if (sp->verbose) + fprintf(sp->verbose, ">>> %s\n", p); + + if ((cmd = buf_open(len + 2)) == NULL) + goto done; + if (buf_add(cmd, p, len) < 0) + goto done; + if (buf_add(cmd, "\r\n", 2) < 0) + goto done; + buf_close(&sp->w, cmd); + cmd = NULL; + + + rv = 0; +done: + va_end(ap); + free(p); + if (cmd) + buf_free(cmd); + return (rv); +} + +int +client_data_add(struct smtp_client *sp, char *buf, size_t len) +{ + char *ln; + + /* check if too late */ + switch (sp->state) { + case CLIENT_INIT: + case CLIENT_EHLO: + case CLIENT_HELO: + case CLIENT_MAILFROM: + case CLIENT_RCPTTO: + break; + default: + return (-1); + } + + /* must end with a newline */ + if (len == 0 || buf[len - 1] != '\n') + return (-1); + buf[len - 1] = '\0'; + + /* split into lines, deal with dot escaping etc. */ + while ((ln = strsep(&buf, "\n"))) { + if (*ln == '.' && buf_add(sp->data, ".", 1) < 0) + return (-1); + if (buf_add(sp->data, ln, strlen(ln)) < 0) + return (-1); + if (buf_add(sp->data, "\r\n", 2) < 0) + return (-1); + } + + return (0); +} + +int +client_error(struct smtp_client *sp, char **ep, char *errormsg) +{ + if (errormsg == NULL) + errormsg = strerror(errno); + strlcpy(sp->errbuf, errormsg, sizeof(sp->errbuf)); + *ep = sp->errbuf; + sp->state = -1; + + return (CLIENT_ERROR); +} + +char * +buf_getln(struct buf_read *r) +{ + char *line; + size_t i; + + /* look for terminating newline */ + for (i = 0; i < r->wpos; i++) + if (r->buf[i] == '\r' || r->buf[i] == '\n') + break; + if (i == r->wpos) + return (NULL); + + /* make a copy of the line */ + if ((line = malloc(i + 1)) == NULL) + return (NULL); + memcpy(line, r->buf, i); + line[i] = '\0'; + + /* drain the buffer */ + if (i < r->wpos - 1) { + char fch = r->buf[i], sch = r->buf[i + 1]; + + if ((sch == '\r' || sch == '\n') && sch != fch) + i += 1; + } + memmove(r->buf, r->buf + i + 1, r->wpos - i - 1); + r->wpos -= i + 1; + + return (line); +} diff --git a/usr.sbin/smtpd/client.h b/usr.sbin/smtpd/client.h new file mode 100644 index 00000000000..348e5294820 --- /dev/null +++ b/usr.sbin/smtpd/client.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * 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. + */ + +struct smtp_client; + +/* return codes for io routines */ +#define CLIENT_DONE 0 /* finished ok */ +#define CLIENT_ERROR -1 /* generic error */ +#define CLIENT_WANT_READ -2 /* need more data */ +#define CLIENT_WANT_WRITE -3 /* have to send sth */ + +/* client states */ +#define CLIENT_INIT 0x1 +#define CLIENT_EHLO 0x2 +#define CLIENT_HELO 0x3 +#define CLIENT_MAILFROM 0x4 +#define CLIENT_RCPTTO 0x5 +#define CLIENT_DATA 0x6 +#define CLIENT_DATA_BODY 0x7 + +struct rcpt { + TAILQ_ENTRY(rcpt) entry; + char *mbox; +}; + +struct smtp_client { + int state; + char *ehlo; + char *sender; + TAILQ_HEAD(,rcpt) recipients; + struct buf_read r; + struct msgbuf w; + struct buf *data; + FILE *verbose; + char errbuf[1024]; +}; + +struct smtp_client *client_init(int, char *); +void client_verbose(struct smtp_client *, int); +int client_sender(struct smtp_client *, char *, ...); +int client_rcpt(struct smtp_client *, char *, ...); +int client_data_fd(struct smtp_client *, int); +int client_data_printf(struct smtp_client *, char *, ...); +int client_read(struct smtp_client *, char **); +int client_write(struct smtp_client *, char **); +void client_close(struct smtp_client *); |