#include "smtp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #define SMTP_TIMEOUT_SEC 30 #define SMTP_LINE_MAX 1024 #define SMTP_REPLY_TEXT_MAX 2048 #define SMTP_READ_BUF 4096 struct smtp_conn { int fd; SSL_CTX *ctx; SSL *ssl; unsigned char in[SMTP_READ_BUF]; size_t in_len; size_t in_pos; }; struct smtp_reply { int code; char text[SMTP_REPLY_TEXT_MAX]; }; static void set_err(char *err, size_t errlen, const char *fmt, ...) { if (!err || errlen == 0) return; va_list ap; va_start(ap, fmt); vsnprintf(err, errlen, fmt, ap); va_end(ap); } static void buf_append_str(struct buf *b, const char *s) { buf_append(b, s, strlen(s)); } static void buf_appendf(struct buf *b, const char *fmt, ...) { va_list ap; va_start(ap, fmt); char tmp[512]; int n = vsnprintf(tmp, sizeof tmp, fmt, ap); va_end(ap); if (n < 0) return; if ((size_t)n < sizeof tmp) { buf_append(b, tmp, (size_t)n); return; } char *big = xmalloc((size_t)n + 1); va_start(ap, fmt); vsnprintf(big, (size_t)n + 1, fmt, ap); va_end(ap); buf_append(b, big, (size_t)n); free(big); } static void conn_close(struct smtp_conn *c) { if (c->ssl) { SSL_shutdown(c->ssl); SSL_free(c->ssl); c->ssl = NULL; } if (c->ctx) { SSL_CTX_free(c->ctx); c->ctx = NULL; } if (c->fd >= 0) { close(c->fd); c->fd = -1; } } static int conn_write(struct smtp_conn *c, const void *data, size_t n, char *err, size_t errlen) { const unsigned char *p = data; while (n > 0) { ssize_t w; if (c->ssl) { int chunk = n > (size_t)INT_MAX ? INT_MAX : (int)n; w = SSL_write(c->ssl, p, chunk); if (w <= 0) { int e = SSL_get_error(c->ssl, (int)w); if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) continue; set_err(err, errlen, "smtp: TLS write failed"); return -1; } } else { w = send(c->fd, p, n, MSG_NOSIGNAL); if (w < 0) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { set_err(err, errlen, "smtp: send timed out"); return -1; } set_err(err, errlen, "smtp: send failed: %s", strerror(errno)); return -1; } } p += (size_t)w; n -= (size_t)w; } return 0; } static int conn_line(struct smtp_conn *c, const char *line, char *err, size_t errlen) { if (conn_write(c, line, strlen(line), err, errlen) != 0) return -1; return conn_write(c, "\r\n", 2, err, errlen); } static int conn_buf_line(struct smtp_conn *c, const struct buf *b, char *err, size_t errlen) { if (conn_write(c, b->p, b->len, err, errlen) != 0) return -1; return conn_write(c, "\r\n", 2, err, errlen); } static int conn_fill(struct smtp_conn *c, char *err, size_t errlen) { for (;;) { if (c->ssl) { int n = SSL_read(c->ssl, c->in, (int)sizeof c->in); if (n > 0) { c->in_len = (size_t)n; c->in_pos = 0; return 0; } int e = SSL_get_error(c->ssl, n); if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) continue; if (e == SSL_ERROR_ZERO_RETURN) { set_err(err, errlen, "smtp: connection closed by server"); return -1; } if (e == SSL_ERROR_SYSCALL && n == 0) { set_err(err, errlen, "smtp: connection closed by server"); return -1; } if (e == SSL_ERROR_SYSCALL && (errno == EAGAIN || errno == EWOULDBLOCK)) { set_err(err, errlen, "smtp: receive timed out"); return -1; } unsigned long ec = ERR_get_error(); if (ec) set_err(err, errlen, "smtp: TLS read failed: %s", ERR_error_string(ec, NULL)); else set_err(err, errlen, "smtp: TLS read failed"); return -1; } ssize_t n = recv(c->fd, c->in, sizeof c->in, 0); if (n > 0) { c->in_len = (size_t)n; c->in_pos = 0; return 0; } if (n == 0) { set_err(err, errlen, "smtp: connection closed by server"); return -1; } if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { set_err(err, errlen, "smtp: receive timed out"); return -1; } set_err(err, errlen, "smtp: receive failed: %s", strerror(errno)); return -1; } } static int conn_read_line(struct smtp_conn *c, char *out, size_t outsz, char *err, size_t errlen) { size_t o = 0; for (;;) { if (c->in_pos >= c->in_len) { if (conn_fill(c, err, errlen) != 0) return -1; } unsigned char ch = c->in[c->in_pos++]; if (ch == '\n') { while (o > 0 && out[o - 1] == '\r') o--; out[o] = '\0'; return 0; } if (o + 1 < outsz) out[o++] = (char)ch; } } static int parse_code(const char *line, int *code) { if (strlen(line) < 3) return -1; for (int i = 0; i < 3; i++) if (!isdigit((unsigned char)line[i])) return -1; *code = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); return 0; } static int conn_read_reply(struct smtp_conn *c, struct smtp_reply *r, char *err, size_t errlen) { char line[SMTP_LINE_MAX]; size_t tlen = 0; r->code = 0; r->text[0] = '\0'; for (;;) { if (conn_read_line(c, line, sizeof line, err, errlen) != 0) return -1; int code; if (parse_code(line, &code) != 0) { set_err(err, errlen, "smtp: malformed server reply"); return -1; } r->code = code; const char *text = line + 3; while (*text == ' ' || *text == '-') text++; if (*text) { size_t n = strlen(text); if (tlen) { if (tlen + 1 < sizeof r->text) r->text[tlen++] = '\n'; else n = 0; } if (tlen + n >= sizeof r->text) n = sizeof r->text - 1 - tlen; memcpy(r->text + tlen, text, n); tlen += n; r->text[tlen] = '\0'; } if (line[3] != '-') break; } return 0; } static int conn_expect(struct smtp_conn *c, int want, const char *what, char *err, size_t errlen) { struct smtp_reply r; if (conn_read_reply(c, &r, err, errlen) != 0) return -1; if (r.code != want) { if (r.text[0]) set_err(err, errlen, "%s: server said %d %s", what, r.code, r.text); else set_err(err, errlen, "%s: server said %d", what, r.code); return -1; } return 0; } static int tcp_connect(struct smtp_conn *c, const char *host, int port, char *err, size_t errlen) { char portstr[16]; snprintf(portstr, sizeof portstr, "%d", port); struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; struct addrinfo *res = NULL; int gai = getaddrinfo(host, portstr, &hints, &res); if (gai != 0) { set_err(err, errlen, "smtp: cannot resolve %s: %s", host, gai_strerror(gai)); return -1; } int last = 0; for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { int fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, ai->ai_protocol); if (fd < 0) { last = errno; continue; } struct timeval tv; tv.tv_sec = SMTP_TIMEOUT_SEC; tv.tv_usec = 0; (void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) { c->fd = fd; freeaddrinfo(res); return 0; } last = errno; close(fd); } freeaddrinfo(res); if (last) set_err(err, errlen, "smtp: cannot connect to %s:%d: %s", host, port, strerror(last)); else set_err(err, errlen, "smtp: cannot connect to %s:%d", host, port); return -1; } static int tls_start(struct smtp_conn *c, const char *host, char *err, size_t errlen) { c->ctx = SSL_CTX_new(TLS_client_method()); if (!c->ctx) { set_err(err, errlen, "smtp: cannot create TLS context"); return -1; } SSL_CTX_set_min_proto_version(c->ctx, TLS1_2_VERSION); SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION | SSL_OP_NO_RENEGOTIATION); SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL); if (SSL_CTX_set_default_verify_paths(c->ctx) != 1) { set_err(err, errlen, "smtp: cannot load system CA certificates"); return -1; } c->ssl = SSL_new(c->ctx); if (!c->ssl) { set_err(err, errlen, "smtp: cannot create TLS connection"); return -1; } if (SSL_set_fd(c->ssl, c->fd) != 1 || SSL_set_tlsext_host_name(c->ssl, host) != 1 || SSL_set1_host(c->ssl, host) != 1) { set_err(err, errlen, "smtp: cannot set up TLS connection"); return -1; } if (SSL_connect(c->ssl) != 1) { long vr = SSL_get_verify_result(c->ssl); unsigned long ec = ERR_get_error(); if (vr != X509_V_OK) set_err(err, errlen, "smtp: TLS certificate verification failed: %s", X509_verify_cert_error_string(vr)); else if (ec) set_err(err, errlen, "smtp: TLS handshake failed: %s", ERR_error_string(ec, NULL)); else set_err(err, errlen, "smtp: TLS handshake failed"); return -1; } if (SSL_get_verify_result(c->ssl) != X509_V_OK) { set_err(err, errlen, "smtp: TLS certificate verification failed: %s", X509_verify_cert_error_string(SSL_get_verify_result(c->ssl))); return -1; } return 0; } static int mech_offered(const char *caps, const char *mech) { if (!caps) return 0; size_t mlen = strlen(mech); for (const char *p = caps; *p;) { const char *line_end = strchr(p, '\n'); size_t llen = line_end ? (size_t)(line_end - p) : strlen(p); if (llen >= 4 && strncasecmp(p, "AUTH", 4) == 0) { const char *q = p + 4; const char *end = p + llen; while (q < end) { while (q < end && (*q == ' ' || *q == '=' || *q == '\t')) q++; const char *w = q; while (q < end && *q != ' ' && *q != '=' && *q != '\t') q++; size_t wl = (size_t)(q - w); if (wl == mlen && strncasecmp(w, mech, mlen) == 0) return 1; } } if (!line_end) break; p = line_end + 1; } return 0; } static void ehlo_name(char *out, size_t n) { if (gethostname(out, n) != 0 || out[0] == '\0' || !strchr(out, '.')) snprintf(out, n, "localhost"); out[n - 1] = '\0'; } static int smtp_ehlo(struct smtp_conn *c, const char *name, struct buf *caps, char *err, size_t errlen) { struct buf cmd; buf_init(&cmd); buf_appendf(&cmd, "EHLO %s", name); int rc = conn_buf_line(c, &cmd, err, errlen); buf_free(&cmd); if (rc != 0) return -1; struct smtp_reply r; if (conn_read_reply(c, &r, err, errlen) != 0) return -1; if (r.code != 250) { if (r.text[0]) set_err(err, errlen, "EHLO: server said %d %s", r.code, r.text); else set_err(err, errlen, "EHLO: server said %d", r.code); return -1; } caps->len = 0; buf_append(caps, r.text, strlen(r.text) + 1); return 0; } static int auth_plain(struct smtp_conn *c, const char *user, const char *pass, char *err, size_t errlen) { size_t ulen = strlen(user); size_t plen = strlen(pass); unsigned char *raw = xmalloc(ulen + plen + 2); raw[0] = 0; memcpy(raw + 1, user, ulen); raw[1 + ulen] = 0; memcpy(raw + 2 + ulen, pass, plen); char *b64 = util_b64(raw, ulen + plen + 2); free(raw); struct buf cmd; buf_init(&cmd); buf_appendf(&cmd, "AUTH PLAIN %s", b64); int rc = conn_buf_line(c, &cmd, err, errlen); buf_free(&cmd); if (rc != 0) { free(b64); return -1; } struct smtp_reply r; if (conn_read_reply(c, &r, err, errlen) != 0) { free(b64); return -1; } if (r.code == 334) { rc = conn_line(c, b64, err, errlen); if (rc == 0 && conn_read_reply(c, &r, err, errlen) != 0) rc = -1; } free(b64); if (rc != 0) return -1; if (r.code != 235) { if (r.text[0]) set_err(err, errlen, "AUTH PLAIN rejected: %d %s", r.code, r.text); else set_err(err, errlen, "AUTH PLAIN rejected: %d", r.code); return -1; } return 0; } static int auth_login(struct smtp_conn *c, const char *user, const char *pass, char *err, size_t errlen) { struct smtp_reply r; if (conn_line(c, "AUTH LOGIN", err, errlen) != 0) return -1; if (conn_read_reply(c, &r, err, errlen) != 0) return -1; if (r.code != 334) { set_err(err, errlen, "AUTH LOGIN rejected: %d", r.code); return -1; } char *ub = util_b64((const unsigned char *)user, strlen(user)); int rc = conn_line(c, ub, err, errlen); free(ub); if (rc != 0) return -1; if (conn_read_reply(c, &r, err, errlen) != 0) return -1; if (r.code != 334) { set_err(err, errlen, "AUTH LOGIN rejected: %d", r.code); return -1; } char *pb = util_b64((const unsigned char *)pass, strlen(pass)); rc = conn_line(c, pb, err, errlen); free(pb); if (rc != 0) return -1; if (conn_read_reply(c, &r, err, errlen) != 0) return -1; if (r.code != 235) { if (r.text[0]) set_err(err, errlen, "AUTH LOGIN rejected: %d %s", r.code, r.text); else set_err(err, errlen, "AUTH LOGIN rejected: %d", r.code); return -1; } return 0; } static int smtp_auth(struct smtp_conn *c, const struct smtp_message *m, const char *caps, char *err, size_t errlen) { if (!m->user || !m->user[0]) return 0; const char *pass = m->password ? m->password : ""; if (mech_offered(caps, "PLAIN")) return auth_plain(c, m->user, pass, err, errlen); if (mech_offered(caps, "LOGIN")) return auth_login(c, m->user, pass, err, errlen); set_err(err, errlen, "smtp: server offers no supported AUTH mechanism"); return -1; } static int normalize_addr(const char *in, char *out, size_t outsz) { while (*in == ' ' || *in == '\t') in++; size_t n = strlen(in); while (n > 0 && (in[n - 1] == ' ' || in[n - 1] == '\t')) n--; if (n >= 2 && in[0] == '<' && in[n - 1] == '>') { in++; n -= 2; } if (n == 0 || n >= outsz) return -1; for (size_t i = 0; i < n; i++) { unsigned char ch = (unsigned char)in[i]; if (ch <= 0x20 || ch == 0x7f || ch == '<' || ch == '>') return -1; } memcpy(out, in, n); out[n] = '\0'; return 0; } static int ascii_printable(const char *s) { for (; *s; s++) { unsigned char ch = (unsigned char)*s; if (ch < 0x20 || ch >= 0x7f) return 0; } return 1; } static int needs_quoting(const char *s) { if (!*s) return 1; size_t n = strlen(s); if (*s == ' ' || *s == '\t' || s[n - 1] == ' ' || s[n - 1] == '\t') return 1; for (const char *p = s; *p; p++) if (strchr("()<>[]:;@\\,.\"", *p)) return 1; return 0; } static size_t last_line_len(const struct buf *b) { size_t i = b->len; while (i > 0 && b->p[i - 1] != '\n') i--; return b->len - i; } static void append_encoded_words(struct buf *b, const char *s) { size_t i = 0; while (s[i]) { size_t start = i; size_t chunk = 0; while (s[i] && chunk < 45) { unsigned char ch = (unsigned char)s[i]; size_t clen; if (ch < 0x80) clen = 1; else if ((ch & 0xE0) == 0xC0) clen = 2; else if ((ch & 0xF0) == 0xE0) clen = 3; else if ((ch & 0xF8) == 0xF0) clen = 4; else clen = 1; if (chunk + clen > 45) break; chunk += clen; i += clen; } if (chunk == 0) { chunk = 1; i++; } char *b64 = util_b64((const unsigned char *)(s + start), chunk); if (start > 0) { if (last_line_len(b) > 60) buf_append_str(b, "\r\n "); else buf_append_str(b, " "); } buf_append(b, "=?UTF-8?B?", 10); buf_append(b, b64, strlen(b64)); buf_append(b, "?=", 2); free(b64); } } static char *sanitize_dup(const char *s) { size_t n = strlen(s); char *out = xmalloc(n + 1); for (size_t i = 0; i < n; i++) { unsigned char ch = (unsigned char)s[i]; out[i] = (ch == '\r' || ch == '\n') ? ' ' : (char)ch; } out[n] = '\0'; return out; } static void append_phrase(struct buf *b, const char *s) { if (ascii_printable(s) && !needs_quoting(s)) { buf_append(b, s, strlen(s)); return; } if (ascii_printable(s)) { buf_append(b, "\"", 1); for (const char *p = s; *p; p++) { if (*p == '"' || *p == '\\') buf_append(b, "\\", 1); buf_append(b, p, 1); } buf_append(b, "\"", 1); return; } append_encoded_words(b, s); } static void append_b64_wrapped(struct buf *out, const unsigned char *data, size_t n) { if (n == 0) return; char *b64 = util_b64(data, n); size_t len = strlen(b64); for (size_t i = 0; i < len; i += 76) { size_t chunk = len - i < 76 ? len - i : 76; buf_append(out, b64 + i, chunk); buf_append(out, "\r\n", 2); } free(b64); } static void append_quoted_param(struct buf *b, const char *s) { for (; *s; s++) { if (*s == '"' || *s == '\\') buf_append(b, "\\", 1); buf_append(b, s, 1); } } static void copy_str(char *dst, size_t n, const char *src) { size_t len = strlen(src); if (len >= n) len = n - 1; memcpy(dst, src, len); dst[len] = '\0'; } static void message_domain(const char *from, char *out, size_t outsz) { const char *at = strrchr(from, '@'); if (at && at[1]) copy_str(out, outsz, at + 1); else copy_str(out, outsz, "localhost"); for (char *p = out; *p; p++) { unsigned char ch = (unsigned char)*p; if (ch < 0x21 || ch > 0x7e || ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '\\') *p = '_'; } } static int build_message(const struct smtp_message *m, const char *from, const char *to, struct buf *out, char *err, size_t errlen) { unsigned char rnd[12]; if (util_random(rnd, sizeof rnd) != 0) { set_err(err, errlen, "smtp: no entropy source"); return -1; } char rhex[25]; util_hex(rnd, sizeof rnd, rhex); char domain[256]; message_domain(from, domain, sizeof domain); char date[64]; time_t now = time(NULL); struct tm tm; if (gmtime_r(&now, &tm)) strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &tm); else snprintf(date, sizeof date, "Thu, 01 Jan 1970 00:00:00 +0000"); buf_append_str(out, "From: "); if (m->from_name && m->from_name[0]) { char *name = sanitize_dup(m->from_name); append_phrase(out, name); free(name); buf_append_str(out, " "); } buf_appendf(out, "<%s>\r\n", from); buf_appendf(out, "To: <%s>\r\n", to); char *subject = sanitize_dup(m->subject ? m->subject : ""); buf_append_str(out, "Subject: "); if (ascii_printable(subject)) buf_append(out, subject, strlen(subject)); else append_encoded_words(out, subject); free(subject); buf_append_str(out, "\r\n"); buf_appendf(out, "Date: %s\r\n", date); buf_appendf(out, "Message-ID: <%s.%ld@%s>\r\n", rhex, (long)getpid(), domain); buf_append_str(out, "MIME-Version: 1.0\r\n"); int has_attach = m->attach_name && m->attach_name[0] && m->attach && m->attach_len > 0; if (has_attach) { char boundary[64]; snprintf(boundary, sizeof boundary, "=_bokf_%s", rhex); char *fname = sanitize_dup(m->attach_name); const char *body = m->body ? m->body : ""; size_t body_len = strlen(body); buf_appendf(out, "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", boundary); buf_appendf(out, "\r\n--%s\r\n", boundary); buf_append_str(out, "Content-Type: text/plain; charset=utf-8\r\n"); buf_append_str(out, "Content-Transfer-Encoding: base64\r\n"); buf_append(out, "\r\n", 2); append_b64_wrapped(out, (const unsigned char *)body, body_len); buf_appendf(out, "--%s\r\n", boundary); buf_append_str(out, "Content-Type: application/pdf; name=\""); append_quoted_param(out, fname); buf_append_str(out, "\"\r\n"); buf_append_str(out, "Content-Transfer-Encoding: base64\r\n"); buf_append_str(out, "Content-Disposition: attachment; filename=\""); append_quoted_param(out, fname); buf_append_str(out, "\"\r\n\r\n"); append_b64_wrapped(out, m->attach, m->attach_len); buf_appendf(out, "--%s--\r\n", boundary); free(fname); } else { const char *body = m->body ? m->body : ""; size_t body_len = strlen(body); buf_append_str(out, "Content-Type: text/plain; charset=utf-8\r\n"); buf_append_str(out, "Content-Transfer-Encoding: base64\r\n"); buf_append(out, "\r\n", 2); append_b64_wrapped(out, (const unsigned char *)body, body_len); } return 0; } static int conn_write_dotstuffed(struct smtp_conn *c, const unsigned char *data, size_t n, char *err, size_t errlen) { size_t start = 0; while (start < n) { const unsigned char *nl = memchr(data + start, '\n', n - start); size_t end = nl ? (size_t)(nl - data) + 1 : n; if (data[start] == '.') { if (conn_write(c, ".", 1, err, errlen) != 0) return -1; } if (conn_write(c, data + start, end - start, err, errlen) != 0) return -1; start = end; } return 0; } int smtp_send(const struct smtp_message *m, char *err, size_t errlen) { if (err && errlen) err[0] = '\0'; if (!m) { set_err(err, errlen, "smtp: no message"); return -1; } if (!m->host || !m->host[0]) { set_err(err, errlen, "smtp: no host"); return -1; } if (!m->from || !m->from[0]) { set_err(err, errlen, "smtp: no from address"); return -1; } if (!m->to || !m->to[0]) { set_err(err, errlen, "smtp: no to address"); return -1; } if (m->port <= 0 || m->port > 65535) { set_err(err, errlen, "smtp: invalid port"); return -1; } const char *security = m->security ? m->security : "plain"; int use_tls = strcmp(security, "tls") == 0; int use_starttls = strcmp(security, "starttls") == 0; int use_plain = strcmp(security, "plain") == 0; if (!use_tls && !use_starttls && !use_plain) { set_err(err, errlen, "smtp: unknown security mode \"%s\"", security); return -1; } char from[512]; char to[512]; if (normalize_addr(m->from, from, sizeof from) != 0 || normalize_addr(m->to, to, sizeof to) != 0) { set_err(err, errlen, "smtp: invalid address"); return -1; } int ret = -1; struct smtp_conn c; memset(&c, 0, sizeof c); c.fd = -1; struct buf message; buf_init(&message); struct buf caps; buf_init(&caps); if (tcp_connect(&c, m->host, m->port, err, errlen) != 0) goto out; if (use_tls && tls_start(&c, m->host, err, errlen) != 0) goto out; if (conn_expect(&c, 220, "greeting", err, errlen) != 0) goto out; char hostname[256]; ehlo_name(hostname, sizeof hostname); if (smtp_ehlo(&c, hostname, &caps, err, errlen) != 0) goto out; if (use_starttls) { if (conn_line(&c, "STARTTLS", err, errlen) != 0 || conn_expect(&c, 220, "STARTTLS", err, errlen) != 0) goto out; if (tls_start(&c, m->host, err, errlen) != 0) goto out; if (smtp_ehlo(&c, hostname, &caps, err, errlen) != 0) goto out; } if (smtp_auth(&c, m, (const char *)caps.p, err, errlen) != 0) goto out; char cmd[1200]; int n = snprintf(cmd, sizeof cmd, "MAIL FROM:<%s>", from); if (n < 0 || (size_t)n >= sizeof cmd) { set_err(err, errlen, "smtp: from address too long"); goto out; } if (conn_line(&c, cmd, err, errlen) != 0 || conn_expect(&c, 250, "MAIL FROM", err, errlen) != 0) goto out; n = snprintf(cmd, sizeof cmd, "RCPT TO:<%s>", to); if (n < 0 || (size_t)n >= sizeof cmd) { set_err(err, errlen, "smtp: to address too long"); goto out; } if (conn_line(&c, cmd, err, errlen) != 0) goto out; struct smtp_reply r; if (conn_read_reply(&c, &r, err, errlen) != 0) goto out; if (r.code != 250 && r.code != 251) { if (r.text[0]) set_err(err, errlen, "RCPT TO: server said %d %s", r.code, r.text); else set_err(err, errlen, "RCPT TO: server said %d", r.code); goto out; } if (conn_line(&c, "DATA", err, errlen) != 0 || conn_expect(&c, 354, "DATA", err, errlen) != 0) goto out; if (build_message(m, from, to, &message, err, errlen) != 0) goto out; if (conn_write_dotstuffed(&c, message.p, message.len, err, errlen) != 0) goto out; if (conn_write(&c, ".\r\n", 3, err, errlen) != 0 || conn_expect(&c, 250, "message", err, errlen) != 0) goto out; if (conn_line(&c, "QUIT", NULL, 0) == 0) (void)conn_read_reply(&c, &r, NULL, 0); ret = 0; out: buf_free(&caps); buf_free(&message); conn_close(&c); return ret; }