static void filter_io_in(struct io *io, int evt) { struct filter_session *s = io->arg; char *line; size_t len; log_trace(TRACE_FILTERS, "filter-api:%s filter_io_in(%p, %s)", filter_name, s, io_strevent(evt)); switch (evt) { case IO_DATAIN: nextline: line = iobuf_getline(&s->pipe.ibuf, &len); if ((line == NULL && iobuf_len(&s->pipe.ibuf) >= LINE_MAX) || (line && len >= LINE_MAX)) { s->pipe.error = 1; break; } /* No complete line received */ if (line == NULL) { iobuf_normalize(&s->pipe.ibuf); /* flow control */ if (iobuf_queued(&s->pipe.obuf) >= FILTER_HIWAT) io_pause(&s->pipe.iev, IO_PAUSE_IN); return; } s->pipe.idatalen += len + 1; /* XXX warning: do not clear io from this call! */ filter_dispatch_dataline(s->id, line); goto nextline; case IO_DISCONNECTED: if (iobuf_len(&s->pipe.ibuf)) { log_warn("warn: filter-api:%s %016"PRIx64" incomplete input", filter_name, s->id); } log_trace(TRACE_FILTERS, "filter-api:%s %016"PRIx64" input done (%zu bytes)", filter_name, s->id, s->pipe.idatalen); break; default: log_warn("warn: filter-api:%s %016"PRIx64": unexpected io event %d on data pipe", filter_name, s->id, evt); s->pipe.error = 1; } if (s->pipe.error) { io_clear(&s->pipe.oev); iobuf_clear(&s->pipe.obuf); } io_clear(&s->pipe.iev); iobuf_clear(&s->pipe.ibuf); filter_trigger_eom(s); }
char * iobuf_getline(struct iobuf *iobuf, size_t *rlen) { char *buf; size_t len, i; buf = iobuf_data(iobuf); len = iobuf_len(iobuf); for (i = 0; i + 1 <= len; i++) if (buf[i] == '\n') { /* Note: the returned address points into the iobuf * buffer. We NUL-end it for convenience, and discard * the data from the iobuf, so that the caller doesn't * have to do it. The data remains "valid" as long * as the iobuf does not overwrite it, that is until * the next call to iobuf_normalize() or iobuf_extend(). */ iobuf_drop(iobuf, i + 1); len = (i && buf[i - 1] == '\r') ? i - 1 : i; buf[len] = '\0'; if (rlen) *rlen = len; return (buf); } return (NULL); }
void iobuf_drop(struct iobuf *io, size_t n) { if (n >= iobuf_len(io)) { io->rpos = io->wpos = 0; return; } io->rpos += n; }
static int clamav_message(struct clamav *cl) { char *l = NULL; if (clamav_read(cl, &l) != 0) return -1; if (clamav_result(cl, l) == -1) return -1; if (cl->r == -1) { log_warnx("warn: message: result failed"); return -1; } if (iobuf_len(&cl->iobuf)) { log_warnx("warn: message: incomplete"); return -1; } return 0; }
static int clamav_read(struct clamav *cl, char **l) { int r; while ((*l = iobuf_getline(&cl->iobuf, NULL)) == NULL) { if (iobuf_len(&cl->iobuf) >= LINE_MAX) { log_warnx("warn: read: iobuf_getline"); return -1; } iobuf_normalize(&cl->iobuf); if ((r = iobuf_read(&cl->iobuf, cl->fd)) < 0) { if (r != IOBUF_CLOSED) log_warn("warn: read: iobuf_read r=%d", r); return r; } } return 0; }
static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg, *p; size_t len; const char *error; int cont; X509 *x; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: log_info("smtp-out: Connected on session %016"PRIx64, s->id); if (s->use_smtps) { io_set_write(io); mta_start_tls(s); } else { mta_enter_state(s, MTA_BANNER); io_set_read(io); } break; case IO_TLSREADY: log_info("smtp-out: Started TLS on session %016"PRIx64": %s", s->id, ssl_to_text(s->io.ssl)); s->flags |= MTA_TLS; if (mta_verify_certificate(s)) { io_pause(&s->io, IO_PAUSE_IN); break; } case IO_TLSVERIFIED: x = SSL_get_peer_certificate(s->io.ssl); if (x) { log_info("smtp-out: Server certificate verification %s " "on session %016"PRIx64, (s->flags & MTA_VERIFIED) ? "succeeded" : "failed", s->id); X509_free(x); } if (s->use_smtps) { mta_enter_state(s, MTA_BANNER); io_set_read(io); } else mta_enter_state(s, MTA_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= SMTPD_MAXLINESIZE) { mta_error(s, "Input too long"); mta_free(s); return; } iobuf_normalize(&s->iobuf); break; } log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); if ((error = parse_smtp_response(line, len, &msg, &cont))) { mta_error(s, "Bad response: %s", error); mta_free(s); return; } /* read extensions */ if (s->state == MTA_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH ", 5) == 0) { s->ext |= MTA_EXT_AUTH; if ((p = strstr(msg, " PLAIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_PLAIN; if ((p = strstr(msg, " LOGIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_LOGIN; } else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; else if (strcmp(msg, "DSN") == 0) s->ext |= MTA_EXT_DSN; } if (cont) goto nextline; if (s->state == MTA_QUIT) { log_info("smtp-out: Closing session %016"PRIx64 ": %zu message%s sent.", s->id, s->msgcount, (s->msgcount > 1) ? "s" : ""); mta_free(s); return; } io_set_write(io); mta_response(s, line); if (s->flags & MTA_FREE) { mta_free(s); return; } iobuf_normalize(&s->iobuf); if (iobuf_len(&s->iobuf)) { log_debug("debug: mta: remaining data in input buffer"); mta_error(s, "Remote host sent too much data"); if (s->flags & MTA_WAIT) s->flags |= MTA_FREE; else mta_free(s); } break; case IO_LOWAT: if (s->state == MTA_BODY) { mta_enter_state(s, MTA_BODY); if (s->flags & MTA_FREE) { mta_free(s); return; } } if (iobuf_queued(&s->iobuf) == 0) io_set_read(io); break; case IO_TIMEOUT: log_debug("debug: mta: %p: connection timeout", s); mta_error(s, "Connection timeout"); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_ERROR: log_debug("debug: mta: %p: IO error: %s", s, io->error); mta_error(s, "IO Error: %s", io->error); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_DISCONNECTED: log_debug("debug: mta: %p: disconnected in state %s", s, mta_strstate(s->state)); mta_error(s, "Connection closed unexpectedly"); if (!s->ready) mta_connect(s); else mta_free(s); break; default: fatalx("mta_io() bad event"); } }
size_t io_pending(struct io *io) { return iobuf_len(io->iobuf); }
void session_io(struct io *io, int evt) { struct session *s = io->arg; void *ssl; char *line; size_t len; log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch(evt) { case IO_TLSREADY: s->s_flags |= F_SECURE; if (s->s_l->flags & F_SMTPS) stat_increment("smtp.smtps", 1); if (s->s_l->flags & F_STARTTLS) stat_increment("smtp.tls", 1); if (s->s_state == S_INIT) { io_set_write(&s->s_io); session_respond(s, SMTPD_BANNER, env->sc_hostname); } session_enter_state(s, S_GREETED); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->s_iobuf, &len); if ((line == NULL && iobuf_len(&s->s_iobuf) >= SMTP_LINE_MAX) || (line && len >= SMTP_LINE_MAX)) { session_respond(s, "500 5.0.0 Line too long"); session_enter_state(s, S_QUIT); io_set_write(io); return; } if (line == NULL) { iobuf_normalize(&s->s_iobuf); return; } if (s->s_state == S_DATACONTENT && strcmp(line, ".")) { /* more data to come */ session_line(s, line, len); goto nextline; } /* pipelining not supported */ if (iobuf_len(&s->s_iobuf)) { session_respond(s, "500 5.0.0 Pipelining not supported"); session_enter_state(s, S_QUIT); io_set_write(io); return; } session_line(s, line, len); iobuf_normalize(&s->s_iobuf); io_set_write(io); break; case IO_LOWAT: if (s->s_state == S_QUIT) { session_destroy(s, "done"); break; } io_set_read(io); /* wait for the client to start tls */ if (s->s_state == S_TLS) { ssl = ssl_smtp_init(s->s_l->ssl_ctx); io_start_tls(io, ssl); } break; case IO_TIMEOUT: session_destroy(s, "timeout"); break; case IO_DISCONNECTED: session_destroy(s, "disconnected"); break; case IO_ERROR: session_destroy(s, "error"); break; default: fatal("session_io()"); } }
static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg, *p; size_t len; const char *error; int cont; X509 *x; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: log_info("smtp-out: Connected on session %016"PRIx64, s->id); if (s->use_smtps) { io_set_write(io); mta_start_tls(s); } else { mta_enter_state(s, MTA_BANNER); io_set_read(io); } break; case IO_TLSREADY: log_info("smtp-out: Started TLS on session %016"PRIx64": %s", s->id, ssl_to_text(s->io.ssl)); s->flags |= MTA_TLS; if (mta_verify_certificate(s)) { io_pause(&s->io, IO_PAUSE_IN); break; } case IO_TLSVERIFIED: x = SSL_get_peer_certificate(s->io.ssl); if (x) { log_info("smtp-out: Server certificate verification %s " "on session %016"PRIx64, (s->flags & MTA_VERIFIED) ? "succeeded" : "failed", s->id); X509_free(x); } if (s->use_smtps) { mta_enter_state(s, MTA_BANNER); io_set_read(io); } else mta_enter_state(s, MTA_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= LINE_MAX) { mta_error(s, "Input too long"); mta_free(s); return; } iobuf_normalize(&s->iobuf); break; } log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); if ((error = parse_smtp_response(line, len, &msg, &cont))) { mta_error(s, "Bad response: %s", error); mta_free(s); return; } /* read extensions */ if (s->state == MTA_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH ", 5) == 0) { s->ext |= MTA_EXT_AUTH; if ((p = strstr(msg, " PLAIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_PLAIN; if ((p = strstr(msg, " LOGIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_LOGIN; } else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; else if (strcmp(msg, "DSN") == 0) s->ext |= MTA_EXT_DSN; } /* continuation reply, we parse out the repeating statuses and ESC */ if (cont) { if (s->replybuf[0] == '\0') (void)strlcat(s->replybuf, line, sizeof s->replybuf); else { line = line + 4; if (isdigit((int)*line) && *(line + 1) == '.' && isdigit((int)*line+2) && *(line + 3) == '.' && isdigit((int)*line+4) && isspace((int)*(line + 5))) (void)strlcat(s->replybuf, line+5, sizeof s->replybuf); else (void)strlcat(s->replybuf, line, sizeof s->replybuf); } goto nextline; } /* last line of a reply, check if we're on a continuation to parse out status and ESC. * if we overflow reply buffer or are not on continuation, log entire last line. */ if (s->replybuf[0] != '\0') { p = line + 4; if (isdigit((int)*p) && *(p + 1) == '.' && isdigit((int)*p+2) && *(p + 3) == '.' && isdigit((int)*p+4) && isspace((int)*(p + 5))) p += 5; if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf) (void)strlcpy(s->replybuf, line, sizeof s->replybuf); } else (void)strlcpy(s->replybuf, line, sizeof s->replybuf); if (s->state == MTA_QUIT) { log_info("smtp-out: Closing session %016"PRIx64 ": %zu message%s sent.", s->id, s->msgcount, (s->msgcount > 1) ? "s" : ""); mta_free(s); return; } io_set_write(io); mta_response(s, s->replybuf); if (s->flags & MTA_FREE) { mta_free(s); return; } if (s->flags & MTA_RECONN) { s->flags &= ~MTA_RECONN; mta_connect(s); return; } iobuf_normalize(&s->iobuf); if (iobuf_len(&s->iobuf)) { log_debug("debug: mta: remaining data in input buffer"); mta_error(s, "Remote host sent too much data"); if (s->flags & MTA_WAIT) s->flags |= MTA_FREE; else mta_free(s); } break; case IO_LOWAT: if (s->state == MTA_BODY) { mta_enter_state(s, MTA_BODY); if (s->flags & MTA_FREE) { mta_free(s); return; } } if (iobuf_queued(&s->iobuf) == 0) io_set_read(io); break; case IO_TIMEOUT: log_debug("debug: mta: %p: connection timeout", s); mta_error(s, "Connection timeout"); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_ERROR: log_debug("debug: mta: %p: IO error: %s", s, io->error); if (!s->ready) { mta_error(s, "IO Error: %s", io->error); mta_connect(s); break; } else if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL))) { /* error in non-strict SSL negotiation, downgrade to plain */ if (s->flags & MTA_TLS) { log_info("smtp-out: Error on session %016"PRIx64 ": opportunistic TLS failed, " "downgrading to plain", s->id); s->flags &= ~MTA_TLS; s->flags |= MTA_DOWNGRADE_PLAIN; mta_connect(s); break; } } mta_error(s, "IO Error: %s", io->error); mta_free(s); break; case IO_TLSERROR: log_debug("debug: mta: %p: TLS IO error: %s", s, io->error); if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL))) { /* error in non-strict SSL negotiation, downgrade to plain */ log_info("smtp-out: TLS Error on session %016"PRIx64 ": TLS failed, " "downgrading to plain", s->id); s->flags &= ~MTA_TLS; s->flags |= MTA_DOWNGRADE_PLAIN; mta_connect(s); break; } mta_error(s, "IO Error: %s", io->error); mta_free(s); break; case IO_DISCONNECTED: log_debug("debug: mta: %p: disconnected in state %s", s, mta_strstate(s->state)); mta_error(s, "Connection closed unexpectedly"); if (!s->ready) mta_connect(s); else mta_free(s); break; default: fatalx("mta_io() bad event"); } }
size_t io_datalen(struct io *io) { return iobuf_len(&io->iobuf); }
static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg; size_t len; struct mta_host *host; const char *error; int cont; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: s->is_reading = 0; io_set_timeout(io, 300000); io_set_write(io); host = TAILQ_FIRST(&s->hosts); dns_query_ptr(&host->sa, s->id); break; case IO_TLSREADY: s->flags |= MTA_TLS; if (s->state == MTA_CONNECT) /* smtps */ mta_enter_state(s, MTA_SMTP_BANNER); else mta_enter_state(s, MTA_SMTP_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= SMTP_LINE_MAX) { mta_status(s, 1, "150 Input too long"); mta_enter_state(s, MTA_DONE); return; } iobuf_normalize(&s->iobuf); break; } log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); if ((error = parse_smtp_response(line, len, &msg, &cont))) { mta_status(s, 1, "150 Bad response: %s", error); mta_enter_state(s, MTA_DONE); return; } /* read extensions */ if (s->state == MTA_SMTP_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH", 4) == 0) s->ext |= MTA_EXT_AUTH; else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; } if (cont) goto nextline; if (s->state == MTA_SMTP_QUIT) { mta_enter_state(s, MTA_DONE); return; } mta_response(s, line); iobuf_normalize(&s->iobuf); break; case IO_LOWAT: if (s->state == MTA_SMTP_BODY) mta_enter_state(s, MTA_SMTP_BODY); if (iobuf_queued(&s->iobuf) == 0) { s->is_reading = 1; io_set_read(io); } break; case IO_TIMEOUT: log_debug("mta: %p: connection timeout", s); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 connection timeout"); mta_enter_state(s, MTA_DONE); break; case IO_ERROR: log_debug("mta: %p: IO error: %s", s, strerror(errno)); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 IO error"); mta_enter_state(s, MTA_DONE); break; case IO_DISCONNECTED: log_debug("mta: %p: disconnected in state %s", s, mta_strstate(s->state)); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 connection closed unexpectedly"); mta_enter_state(s, MTA_DONE); break; default: fatalx("mta_io() bad event"); } }