diff options
author | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-04-16 15:35:07 +0000 |
---|---|---|
committer | Jacek Masiulaniec <jacekm@cvs.openbsd.org> | 2009-04-16 15:35:07 +0000 |
commit | dd6deb459c9ab0661ccbdc8771cdfe58f41d505d (patch) | |
tree | 1945ddb895a10f363aab71f084b07d38c9a1b703 /usr.sbin/smtpd/enqueue.c | |
parent | 9a62f48680726b99408b4681140b0a804c4596a9 (diff) |
Total rewrite of the sendmail interface. Adds support for -t, -v,
and -F cmdline args. Also, date and Message-Id headers are added
when missing.
The main trouble with the current enqueue code is that it requires
dealing with problems in the control process that are already solved
in the smtp process, ie. duplicating a lot of code which interacts
with untrusted clients. This diff solves this by making sendmail
obtain a SMTP socket from smtp via smtpd.sock, and using that socket
to deliver the message. For smtpd it looks as if connection was
made from the network, only difference being the F_MESSAGE_ENQUEUED
message flag, handy when differentation between local and remote
deliveries is wanted.
Most of the features come from the femail program, created by henning@.
Additional testing by Nigel J. Taylor.
ok gilles@, henning@ happy with smtpd using femail code
Diffstat (limited to 'usr.sbin/smtpd/enqueue.c')
-rw-r--r-- | usr.sbin/smtpd/enqueue.c | 835 |
1 files changed, 590 insertions, 245 deletions
diff --git a/usr.sbin/smtpd/enqueue.c b/usr.sbin/smtpd/enqueue.c index 60e4349ce73..bff3bcb90f6 100644 --- a/usr.sbin/smtpd/enqueue.c +++ b/usr.sbin/smtpd/enqueue.c @@ -1,7 +1,7 @@ -/* $OpenBSD: enqueue.c,v 1.11 2009/04/05 16:10:42 gilles Exp $ */ +/* $OpenBSD: enqueue.c,v 1.12 2009/04/16 15:35:06 jacekm Exp $ */ /* - * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -11,26 +11,25 @@ * 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. + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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/tree.h> #include <sys/param.h> +#include <sys/queue.h> #include <sys/socket.h> -#include <sys/stat.h> - -#include <netinet/in.h> -#include <arpa/inet.h> +#include <sys/tree.h> +#include <sys/types.h> #include <ctype.h> #include <err.h> #include <errno.h> #include <event.h> +#include <netdb.h> #include <pwd.h> +#include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -40,48 +39,136 @@ extern struct imsgbuf *ibuf; -__dead void usage(void); -int enqueue(int, char **); -int enqueue_init(struct message *); -int enqueue_add_recipient(struct message *, char *); -int enqueue_messagefd(struct message *); -int enqueue_write_message(FILE *, FILE *); -int enqueue_commit(struct message *); +void usage(void); +void sighdlr(int); +int main(int, char *[]); +void femail_write(const void *, size_t); +void femail_put(const char *, ...); +void send_cmd(const char *); +void build_from(char *, struct passwd *); +int parse_message(FILE *, int, int); +void parse_addr(char *, size_t, int); +void parse_addr_terminal(int); +char *qualify_addr(char *); +void rcpt_add(char *); +void received(void); +int open_connection(void); +int read_reply(void); +void greeting(int); +void mailfrom(char *); +void rcptto(char *); +void start_data(void); +void send_message(int); +void end_data(void); +int enqueue(int, char **); + +enum headerfields { + HDR_NONE, + HDR_FROM, + HDR_TO, + HDR_CC, + HDR_BCC, + HDR_SUBJECT, + HDR_DATE, + HDR_MSGID +}; + +struct { + char *word; + enum headerfields type; +} keywords[] = { + { "From:", HDR_FROM }, + { "To:", HDR_TO }, + { "Cc:", HDR_CC }, + { "Bcc:", HDR_BCC }, + { "Subject:", HDR_SUBJECT }, + { "Date:", HDR_DATE }, + { "Message-Id:", HDR_MSGID } +}; + +#define STATUS_GREETING 220 +#define STATUS_HELO 250 +#define STATUS_MAILFROM 250 +#define STATUS_RCPTTO 250 +#define STATUS_DATA 354 +#define STATUS_QUEUED 250 +#define STATUS_QUIT 221 +#define SMTP_LINELEN 1000 +#define SMTP_TIMEOUT 120 +#define TIMEOUTMSG "Timeout\n" + +#define WSP(c) (c == ' ' || c == '\t') + +int verbose = 0; +char host[MAXHOSTNAMELEN]; +char *user = NULL; +time_t timestamp; + +struct { + int fd; + char *from; + char *fromname; + char **rcpts; + int rcpt_cnt; + char *data; + size_t len; + int saw_date; + int saw_msgid; + int saw_from; +} msg; + +struct { + u_int quote; + u_int comment; + u_int esc; + u_int brackets; + size_t wpos; + char buf[SMTP_LINELEN]; +} pstate; + +void +sighdlr(int sig) +{ + if (sig == SIGALRM) { + write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG)); + _exit (2); + } +} int enqueue(int argc, char *argv[]) { - int ch; - int fd; - FILE *fpout; - struct message message; - char sender[MAX_PATH_SIZE]; - - uid_t uid; - char *username; - char hostname[MAXHOSTNAMELEN]; - struct passwd *pw; - - uid = getuid(); - pw = safe_getpwuid(uid); - if (pw == NULL) - errx(1, "you don't exist, go away."); - - username = pw->pw_name; - gethostname(hostname, sizeof(hostname)); + int i, ch, tflag = 0, status, noheader; + char *fake_from = NULL; + struct passwd *pw; - if (! bsnprintf(sender, sizeof(sender), "%s@%s", username, hostname)) - errx(1, "sender address too long."); + bzero(&msg, sizeof(msg)); + time(×tamp); - while ((ch = getopt(argc, argv, "f:i")) != -1) { + while ((ch = getopt(argc, argv, "46B:b:E::e:F:f:iJ::mo:p:tvx")) != -1) { switch (ch) { case 'f': - if (strlcpy(sender, optarg, sizeof(sender)) - >= sizeof(sender)) - errx(1, "sender address too long."); + fake_from = optarg; break; - case 'i': /* ignore, interface compatibility */ + case 'F': + msg.fromname = optarg; + break; + case 't': + tflag = 1; + break; + case 'v': + verbose = 1; + break; + /* all remaining: ignored, sendmail compat */ + case 'B': + case 'b': + case 'E': + case 'e': + case 'i': + case 'm': case 'o': + case 'p': + case 'x': break; default: usage(); @@ -91,165 +178,350 @@ enqueue(int argc, char *argv[]) argc -= optind; argv += optind; - bzero(&message, sizeof(struct message)); - - strlcpy(message.session_helo, "localhost", - sizeof(message.session_helo)); - strlcpy(message.session_hostname, hostname, - sizeof(message.session_hostname)); - - /* build sender */ - if (! recipient_to_path(&message.sender, sender)) - errx(1, "invalid sender address."); - - if (! enqueue_init(&message)) - errx(1, "failed to initialize enqueue message."); - - if (argc == 0) - errx(1, "no recipient."); - - while (argc--) { - if (! enqueue_add_recipient(&message, *argv)) - errx(1, "invalid recipient."); - ++argv; + if (gethostname(host, sizeof(host)) == -1) + err(1, "gethostname"); + if ((pw = getpwuid(getuid())) == NULL) + user = "anonymous"; + if (pw != NULL && (user = strdup(pw->pw_name)) == NULL) + err(1, "strdup"); + + build_from(fake_from, pw); + + while(argc > 0) { + rcpt_add(argv[0]); + argv++; + argc--; } - fd = enqueue_messagefd(&message); - if (fd == -1 || (fpout = fdopen(fd, "w")) == NULL) - errx(1, "failed to open message file for writing."); + noheader = parse_message(stdin, fake_from == NULL, tflag); - if (! enqueue_write_message(stdin, fpout)) - errx(1, "failed to write message to message file."); + if (msg.rcpt_cnt == 0) + errx(1, "no recipients"); - if (! safe_fclose(fpout)) - errx(1, "error while writing to message file."); + signal(SIGALRM, sighdlr); + alarm(SMTP_TIMEOUT); - if (! enqueue_commit(&message)) - errx(1, "failed to commit message to queue."); + msg.fd = open_connection(); + if ((status = read_reply()) != STATUS_GREETING) + errx(1, "server greets us with status %d", status); + greeting(1); + mailfrom(msg.from); + for (i = 0; i < msg.rcpt_cnt; i++) + rcptto(msg.rcpts[i]); + start_data(); + send_message(noheader); + end_data(); - return 0; + close(msg.fd); + exit (0); } -int -enqueue_add_recipient(struct message *messagep, char *recipient) +void +femail_write(const void *buf, size_t nbytes) +{ + ssize_t n; + + do { + n = write(msg.fd, buf, nbytes); + } while (n == -1 && errno == EINTR); + + if (n == 0) + errx(1, "write: connection closed"); + if (n == -1) + err(1, "write"); + if ((size_t)n < nbytes) + errx(1, "short write: %ld of %lu bytes written", + (long)n, (u_long)nbytes); +} + +void +femail_put(const char *fmt, ...) +{ + va_list ap; + char buf[SMTP_LINELEN]; + + va_start(ap, fmt); + if (vsnprintf(buf, sizeof(buf), fmt, ap) >= (int)sizeof(buf)) + errx(1, "line length exceeded"); + va_end(ap); + + femail_write(buf, strlen(buf)); +} + +void +send_cmd(const char *cmd) +{ + if (verbose) + printf(">>> %s\n", cmd); + + femail_put("%s\r\n", cmd); +} + +void +build_from(char *fake_from, struct passwd *pw) { - char buffer[MAX_PATH_SIZE]; - struct message_recipient mr; - struct sockaddr_in6 *ssin6; - struct sockaddr_in *ssin; - struct message message; - int done = 0; - int n; - struct imsg imsg; - - bzero(&mr, sizeof(mr)); - - message = *messagep; - - if (strlcpy(buffer, recipient, sizeof(buffer)) >= sizeof(buffer)) - errx(1, "recipient address too long."); - - if (strchr(buffer, '@') == NULL) { - if (! bsnprintf(buffer, sizeof(buffer), "%s@%s", - buffer, messagep->sender.domain)) - errx(1, "recipient address too long."); + char *p; + + if (fake_from == NULL) + msg.from = qualify_addr(user); + else { + if (fake_from[0] == '<') { + if (fake_from[strlen(fake_from) - 1] != '>') + errx(1, "leading < but no trailing >"); + fake_from[strlen(fake_from) - 1] = 0; + if ((p = malloc(strlen(fake_from))) == NULL) + err(1, "malloc"); + strlcpy(p, fake_from + 1, strlen(fake_from)); + + msg.from = qualify_addr(p); + free(p); + } else + msg.from = qualify_addr(fake_from); } - - if (! recipient_to_path(&message.recipient, buffer)) - errx(1, "invalid recipient address."); - - message.session_rcpt = message.recipient; - - mr.ss.ss_family = AF_INET6; - mr.ss.ss_len = sizeof(*ssin6); - ssin6 = (struct sockaddr_in6 *)&mr.ss; - if (inet_pton(AF_INET6, "::1", &ssin6->sin6_addr) != 1) { - mr.ss.ss_family = AF_INET; - mr.ss.ss_len = sizeof(*ssin); - ssin = (struct sockaddr_in *)&mr.ss; - if (inet_pton(AF_INET, "127.0.0.1", &ssin->sin_addr) != 1) - return 0; + + if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { + size_t len; + + len = strcspn(pw->pw_gecos, ","); + len++; /* null termination */ + if ((msg.fromname = malloc(len)) == NULL) + err(1, NULL); + strlcpy(msg.fromname, pw->pw_gecos, len); } - message.session_ss = mr.ss; +} - mr.path = message.recipient; - mr.id = message.session_id; - mr.msg = message; - mr.msg.flags |= F_MESSAGE_ENQUEUED; +int +parse_message(FILE *fin, int get_from, int tflag) +{ + char *buf, *twodots = ".."; + size_t len, new_len; + void *newp; + u_int i, cur = HDR_NONE, dotonly; + u_int header_seen = 0, header_done = 0; + + bzero(&pstate, sizeof(pstate)); + for (;;) { + buf = fgetln(fin, &len); + if (buf == NULL && ferror(fin)) + err(1, "fgetln"); + if (buf == NULL && feof(fin)) + break; - imsg_compose(ibuf, IMSG_MFA_RCPT, 0, 0, -1, &mr, sizeof (mr)); - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) < 0) - err(1, "write error"); + /* account for \r\n linebreaks */ + if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + buf[--len - 1] = '\n'; - while (!done) { - if ((n = imsg_read(ibuf)) == -1) - errx(1, "imsg_read error"); - if (n == 0) - errx(1, "pipe closed"); + if (len == 1 && buf[0] == '\n') /* end of header */ + header_done = 1; - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); + if (buf == NULL || len < 1) + err(1, "fgetln weird"); - if (n == 0) - continue; + if (!WSP(buf[0])) { /* whitespace -> continuation */ + if (cur == HDR_FROM) + parse_addr_terminal(1); + if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) + parse_addr_terminal(0); + cur = HDR_NONE; + } - done = 1; - switch (imsg.hdr.type) { - case IMSG_CTL_OK: { - return 1; + for (i = 0; !header_done && cur == HDR_NONE && + i < (sizeof(keywords) / sizeof(keywords[0])); i++) + if (len > strlen(keywords[i].word) && + !strncasecmp(buf, keywords[i].word, + strlen(keywords[i].word))) + cur = keywords[i].type; + + if (cur != HDR_NONE) + header_seen = 1; + + if (cur != HDR_BCC) { + /* save data, \n -> \r\n, . -> .. */ + if (buf[len - 1] == '\n') + new_len = msg.len + len + 1; + else + new_len = msg.len + len + 2; + + if ((len == 1 && buf[0] == '.') || + (len > 1 && buf[0] == '.' && buf[1] == '\n')) { + dotonly = 1; + new_len++; + } else + dotonly = 0; + + if ((newp = realloc(msg.data, new_len)) == NULL) + err(1, "realloc header"); + msg.data = newp; + if (dotonly) + memcpy(msg.data + msg.len, twodots, 2); + else + memcpy(msg.data + msg.len, buf, len); + msg.len = new_len; + msg.data[msg.len - 2] = '\r'; + msg.data[msg.len - 1] = '\n'; } - case IMSG_CTL_FAIL: - return 0; - default: - errx(1, "unexpected reply (%d)", imsg.hdr.type); + + /* + * using From: as envelope sender is not sendmail compatible, + * but I really want it that way - maybe needs a knob + */ + if (cur == HDR_FROM) { + msg.saw_from++; + if (get_from) + parse_addr(buf, len, 1); } - imsg_free(&imsg); + + if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) + parse_addr(buf, len, 0); + + if (cur == HDR_DATE) + msg.saw_date++; + if (cur == HDR_MSGID) + msg.saw_msgid++; } - return 1; + return (!header_seen); } -int -enqueue_write_message(FILE *fpin, FILE *fpout) +void +parse_addr(char *s, size_t len, int is_from) { - char *buf, *lbuf; - size_t len; - - lbuf = NULL; - while ((buf = fgetln(fpin, &len))) { - if (buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - len--; + size_t pos = 0; + int terminal = 0; + + /* unless this is a continuation... */ + if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { + /* ... skip over everything before the ':' */ + for (; pos < len && s[pos] != ':'; pos++) + ; /* nothing */ + /* ... and check & reset parser state */ + parse_addr_terminal(is_from); + } + + /* skip over ':' ',' ';' and whitespace */ + for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || + s[pos] == ',' || s[pos] == ';'); pos++) + ; /* nothing */ + + for (; pos < len; pos++) { + if (!pstate.esc && !pstate.quote && s[pos] == '(') + pstate.comment++; + if (!pstate.comment && !pstate.esc && s[pos] == '"') + pstate.quote = !pstate.quote; + + if (!pstate.comment && !pstate.quote && !pstate.esc) { + if (s[pos] == ':') { /* group */ + for(pos++; pos < len && WSP(s[pos]); pos++) + ; /* nothing */ + pstate.wpos = 0; + } + if (s[pos] == '\n' || s[pos] == '\r') + break; + if (s[pos] == ',' || s[pos] == ';') { + terminal = 1; + break; + } + if (s[pos] == '<') { + pstate.brackets = 1; + pstate.wpos = 0; + } + if (pstate.brackets && s[pos] == '>') + terminal = 1; } - else { - /* EOF without EOL, copy and add the NUL */ - if ((lbuf = malloc(len + 1)) == NULL) - err(1, NULL); - memcpy(lbuf, buf, len); - lbuf[len] = '\0'; - buf = lbuf; + + if (!pstate.comment && !terminal && (!(!(pstate.quote || + pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos++] = s[pos]; } - if (fprintf(fpout, "%s\n", buf) != (int)len + 1) - return 0; + + if (!pstate.quote && pstate.comment && s[pos] == ')') + pstate.comment--; + + if (!pstate.esc && !pstate.comment && !pstate.quote && + s[pos] == '\\') + pstate.esc = 1; + else + pstate.esc = 0; } - free(lbuf); - return 1; + + if (terminal) + parse_addr_terminal(is_from); + + for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) + ; /* nothing */ + + if (pos < len) + parse_addr(s + pos, len - pos, is_from); +} + +void +parse_addr_terminal(int is_from) +{ + if (pstate.comment || pstate.quote || pstate.esc) + errx(1, "syntax error in address"); + if (pstate.wpos) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos] = '\0'; + if (is_from) + msg.from = qualify_addr(pstate.buf); + else + rcpt_add(pstate.buf); + pstate.wpos = 0; + } +} + +char * +qualify_addr(char *in) +{ + char *out; + + if (strchr(in, '@') == NULL) { + if (asprintf(&out, "%s@%s", in, host) == -1) + err(1, "qualify asprintf"); + } else + if ((out = strdup(in)) == NULL) + err(1, "qualify strdup"); + + return (out); +} + +void +rcpt_add(char *addr) +{ + void *nrcpts; + + if ((nrcpts = realloc(msg.rcpts, + sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL) + err(1, "rcpt_add realloc"); + msg.rcpts = nrcpts; + msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); +} + +void +received(void) +{ + femail_put( + "Received: (from %s@%s, uid %lu)\r\n\tby %s\r\n\t%s\r\n", + user, "localhost", (u_long)getuid(), host, time_to_text(timestamp)); } int -enqueue_init(struct message *messagep) +open_connection(void) { - int done = 0; - int n; - struct imsg imsg; + struct imsg imsg; + int fd; + int n; + + imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0); - imsg_compose(ibuf, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, messagep, sizeof(*messagep)); while (ibuf->w.queued) if (msgbuf_write(&ibuf->w) < 0) - err(1, "write error"); + err(1, "write error"); - while (!done) { + while (1) { if ((n = imsg_read(ibuf)) == -1) errx(1, "imsg_read error"); if (n == 0) @@ -257,110 +529,183 @@ enqueue_init(struct message *messagep) if ((n = imsg_get(ibuf, &imsg)) == -1) errx(1, "imsg_get error"); - if (n == 0) continue; - done = 1; - switch (imsg.hdr.type) { - case IMSG_CTL_OK: { - struct message *mp; - - mp = imsg.data; - messagep->session_id = mp->session_id; - strlcpy(messagep->message_id, mp->message_id, - sizeof(messagep->message_id)); - - return 1; - } - case IMSG_CTL_FAIL: - return 0; - default: - err(1, "unexpected reply (%d)", imsg.hdr.type); - } + fd = imsg_get_fd(ibuf, &imsg); imsg_free(&imsg); + + break; } - return 0; + return fd; } int -enqueue_messagefd(struct message *messagep) +read_reply(void) { - int done = 0; - int n; - struct imsg imsg; + char *lbuf = NULL; + size_t len, pos, spos; + long status = 0; + char buf[BUFSIZ]; + ssize_t rlen; + int done = 0; + + for (len = pos = spos = 0; !done;) { + if (pos == 0 || + (pos > 0 && memchr(buf + pos, '\n', len - pos) == NULL)) { + memmove(buf, buf + pos, len - pos); + len -= pos; + pos = 0; + if ((rlen = read(msg.fd, buf + len, + sizeof(buf) - len)) == -1) + err(1, "read"); + len += rlen; + } + spos = pos; - imsg_compose(ibuf, IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, messagep, sizeof(*messagep)); - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) < 0) - err(1, "write error"); + /* status code */ + for (; pos < len && buf[pos] >= '0' && buf[pos] <= '9'; pos++) + ; /* nothing */ - while (!done) { - if ((n = imsg_read(ibuf)) == -1) - errx(1, "imsg_read error"); - if (n == 0) - errx(1, "pipe closed"); + if (pos == len) + return (0); - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); + if (buf[pos] == ' ') + done = 1; + else if (buf[pos] != '-') + errx(1, "invalid syntax in reply from server"); - if (n == 0) - continue; + /* skip up to \n */ + for (; pos < len && buf[pos - 1] != '\n'; pos++) + ; /* nothing */ - done = 1; - switch (imsg.hdr.type) { - case IMSG_CTL_OK: - return imsg_get_fd(ibuf, &imsg); - case IMSG_CTL_FAIL: - return -1; - default: - err(1, "unexpected reply (%d)", imsg.hdr.type); + if (verbose) { + size_t clen; + + clen = pos - spos + 1; /* + 1 for trailing \0 */ + if (buf[pos - 1] == '\n') + clen--; + if (buf[pos - 2] == '\r') + clen--; + if ((lbuf = malloc(clen)) == NULL) + err(1, NULL); + strlcpy(lbuf, buf + spos, clen); + printf("<<< %s\n", lbuf); + free(lbuf); } - imsg_free(&imsg); } - return -1; + status = strtol(buf, NULL, 10); + if (status < 100 || status > 999) + errx(1, "error reading status: out of range"); + + return (status); } +void +greeting(int use_ehlo) +{ + int status; + char *cmd, *how; + + if (use_ehlo) + how = "EHLO"; + else + how = "HELO"; + + if (asprintf(&cmd, "%s %s", how, host) == -1) + err(1, "asprintf"); + send_cmd(cmd); + free(cmd); + + if ((status = read_reply()) != STATUS_HELO) { + if (use_ehlo) + greeting(0); + else + errx(1, "remote host refuses our greeting"); + } +} -int -enqueue_commit(struct message *messagep) +void +mailfrom(char *addr) { - int done = 0; - int n; - struct imsg imsg; + int status; + char *cmd; - imsg_compose(ibuf, IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, messagep, sizeof(*messagep)); - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) < 0) - err(1, "write error"); + if (asprintf(&cmd, "MAIL FROM:<%s>", addr) == -1) + err(1, "asprintf"); + send_cmd(cmd); + free(cmd); - while (!done) { - if ((n = imsg_read(ibuf)) == -1) - errx(1, "imsg_read error"); - if (n == 0) - errx(1, "pipe closed"); + if ((status = read_reply()) != STATUS_MAILFROM) + errx(1, "mail from %s refused by server", addr); +} - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); +void +rcptto(char *addr) +{ + int status; + char *cmd; - if (n == 0) - continue; + if (asprintf(&cmd, "RCPT TO:<%s>", addr) == -1) + err(1, "asprintf"); + send_cmd(cmd); + free(cmd); - done = 1; - switch (imsg.hdr.type) { - case IMSG_CTL_OK: { - return 1; - } - case IMSG_CTL_FAIL: { - return 0; - } - default: - err(1, "unexpected reply (%d)", imsg.hdr.type); - } - imsg_free(&imsg); + if ((status = read_reply()) != STATUS_RCPTTO) + errx(1, "rcpt to %s refused by server", addr); +} + +void +start_data(void) +{ + int status; + + send_cmd("DATA"); + + if ((status = read_reply()) != STATUS_DATA) + errx(1, "server sends error after DATA"); +} + +void +send_message(int noheader) +{ + /* our own headers */ + received(); + + if (!msg.saw_from) { + if (msg.fromname != NULL) + femail_put("From: %s <%s>\r\n", msg.fromname, msg.from); + else + femail_put("From: %s\r\n", msg.from); } - return 0; + if (!msg.saw_date) + femail_put("Date: %s\r\n", time_to_text(timestamp)); + + if (!msg.saw_msgid) + femail_put("Message-Id: <%llu.enqueue@%s>\r\n", + queue_generate_id(), host); + + if (noheader) + femail_write("\r\n", 2); + + femail_write(msg.data, msg.len); +} + +void +end_data(void) +{ + int status; + + femail_write(".\r\n", 3); + + if ((status = read_reply()) != STATUS_QUEUED) + errx(1, "error after sending mail, got status %d", status); + + send_cmd("QUIT"); + + if ((status = read_reply()) != STATUS_QUIT) + errx(1, "server sends error after QUIT"); } |