diff options
-rw-r--r-- | usr.sbin/smtpd/control.c | 189 | ||||
-rw-r--r-- | usr.sbin/smtpd/enqueue.c | 835 | ||||
-rw-r--r-- | usr.sbin/smtpd/mfa.c | 31 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue.c | 49 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c | 59 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl.c | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 15 |
8 files changed, 725 insertions, 463 deletions
diff --git a/usr.sbin/smtpd/control.c b/usr.sbin/smtpd/control.c index ce1ce84ab93..10bda60485e 100644 --- a/usr.sbin/smtpd/control.c +++ b/usr.sbin/smtpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.21 2009/03/29 14:18:20 jacekm Exp $ */ +/* $OpenBSD: control.c,v 1.22 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -315,57 +315,10 @@ control_dispatch_ext(int fd, short event, void *arg) break; switch (imsg.hdr.type) { - case IMSG_MFA_RCPT: { - struct message_recipient *mr; - - if (c->state != CS_INIT && c->state != CS_RCPT) - goto badstate; - - mr = imsg.data; - imsg_compose(env->sc_ibufs[PROC_MFA], IMSG_MFA_RCPT, 0, 0, -1, - mr, sizeof(*mr)); - event_del(&c->ibuf.ev); + case IMSG_SMTP_ENQUEUE: + imsg_compose(env->sc_ibufs[PROC_SMTP], + IMSG_SMTP_ENQUEUE, 0, 0, -1, &fd, sizeof(fd)); break; - } - case IMSG_QUEUE_CREATE_MESSAGE: { - struct message *messagep; - - if (c->state != CS_NONE && c->state != CS_DONE) - goto badstate; - - messagep = imsg.data; - messagep->session_id = fd; - imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, - messagep, sizeof(*messagep)); - event_del(&c->ibuf.ev); - break; - } - case IMSG_QUEUE_MESSAGE_FILE: { - struct message *messagep; - - if (c->state != CS_RCPT) - goto badstate; - - messagep = imsg.data; - messagep->session_id = fd; - imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, - messagep, sizeof(*messagep)); - event_del(&c->ibuf.ev); - break; - } - case IMSG_QUEUE_COMMIT_MESSAGE: { - struct message *messagep; - - if (c->state != CS_FD) - goto badstate; - - messagep = imsg.data; - messagep->session_id = fd; - imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, - messagep, sizeof(*messagep)); - event_del(&c->ibuf.ev); - break; - } case IMSG_STATS: { struct stats s; @@ -504,7 +457,6 @@ control_dispatch_ext(int fd, short event, void *arg) imsg_free(&imsg); continue; -badstate: badcred: imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); @@ -610,16 +562,6 @@ control_dispatch_lka(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_QUEUE_TEMPFAIL: { - struct submit_status *ss; - - log_debug("GOT LFA REPLY"); - ss = imsg.data; - if (ss->code != 250) - log_debug("LKA FAILED WITH TEMPORARY ERROR"); - - break; - } default: log_warnx("control_dispatch_lka: got imsg %d", imsg.hdr.type); @@ -666,26 +608,6 @@ control_dispatch_mfa(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_MFA_RCPT: { - struct submit_status *ss; - struct ctl_conn *c; - - ss = imsg.data; - if ((c = control_connbyfd(ss->id)) == NULL) { - log_warn("control_dispatch_queue: fd %lld: not found", ss->id); - return; - } - - event_add(&c->ibuf.ev, NULL); - if (ss->code == 250) { - c->state = CS_RCPT; - break; - } - - imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - - break; - } default: log_warnx("control_dispatch_mfa: got imsg %d", imsg.hdr.type); @@ -732,94 +654,6 @@ control_dispatch_queue(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_QUEUE_CREATE_MESSAGE: { - struct submit_status *ss; - struct ctl_conn *c; - - ss = imsg.data; - if ((c = control_connbyfd(ss->id)) == NULL) { - log_warn("control_dispatch_queue: fd %lld: not found", ss->id); - return; - } - event_add(&c->ibuf.ev, NULL); - - if (ss->code != 250) { - imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, - NULL, 0); - } - else { - c->state = CS_INIT; - ss->msg.session_id = ss->id; - strlcpy(ss->msg.message_id, ss->u.msgid, - sizeof(ss->msg.message_id)); - imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, -1, - &ss->msg, sizeof(struct message)); - } - - break; - } - case IMSG_QUEUE_COMMIT_ENVELOPES: { - struct submit_status *ss; - struct ctl_conn *c; - - ss = imsg.data; - if ((c = control_connbyfd(ss->id)) == NULL) { - log_warn("control_dispatch_queue: fd %lld: not found", ss->id); - return; - } - event_add(&c->ibuf.ev, NULL); - c->state = CS_RCPT; - imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, -1, - NULL, 0); - - break; - } - case IMSG_QUEUE_MESSAGE_FILE: { - struct submit_status *ss; - struct ctl_conn *c; - int fd; - - ss = imsg.data; - if ((c = control_connbyfd(ss->id)) == NULL) { - log_warn("control_dispatch_queue: fd %lld: not found", - ss->id); - return; - } - event_add(&c->ibuf.ev, NULL); - - fd = imsg_get_fd(ibuf, &imsg); - if (ss->code == 250) { - c->state = CS_FD; - imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, fd, - &ss->msg, sizeof(struct message)); - } - else - imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, - &ss->msg, sizeof(struct message)); - break; - } - case IMSG_QUEUE_COMMIT_MESSAGE: { - struct submit_status *ss; - struct ctl_conn *c; - - ss = imsg.data; - if ((c = control_connbyfd(ss->id)) == NULL) { - log_warn("control_dispatch_queue: fd %lld: not found", - ss->id); - return; - } - event_add(&c->ibuf.ev, NULL); - - if (ss->code == 250) { - c->state = CS_DONE; - imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, -1, - &ss->msg, sizeof(struct message)); - } - else - imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1, - &ss->msg, sizeof(struct message)); - break; - } case IMSG_STATS: { struct stats *s; struct ctl_conn *c; @@ -973,6 +807,21 @@ control_dispatch_smtp(int sig, short event, void *p) break; } + case IMSG_SMTP_ENQUEUE: { + struct ctl_conn *c; + int client_fd; + + client_fd = *(int *)imsg.data; + + if ((c = control_connbyfd(client_fd)) == NULL) { + log_warn("control_dispatch_smtp: fd %d not found", client_fd); + return; + } + + imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, + imsg_get_fd(ibuf, &imsg), NULL, 0); + break; + } default: log_warnx("control_dispatch_smtp: got imsg %d", imsg.hdr.type); 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"); } diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c index d9b6cded642..9836e6f4f8a 100644 --- a/usr.sbin/smtpd/mfa.c +++ b/usr.sbin/smtpd/mfa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mfa.c,v 1.18 2009/03/29 14:18:20 jacekm Exp $ */ +/* $OpenBSD: mfa.c,v 1.19 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -47,8 +47,8 @@ void mfa_setup_events(struct smtpd *); void mfa_disable_events(struct smtpd *); void mfa_timeout(int, short, void *); -void mfa_test_mail(struct smtpd *, struct message *, int); -void mfa_test_rcpt(struct smtpd *, struct message_recipient *, int); +void mfa_test_mail(struct smtpd *, struct message *); +void mfa_test_rcpt(struct smtpd *, struct message_recipient *); int mfa_ruletest_rcpt(struct smtpd *, struct path *, struct sockaddr_storage *); int mfa_check_source(struct map *, struct sockaddr_storage *); int mfa_match_mask(struct sockaddr_storage *, struct netaddr *); @@ -151,10 +151,10 @@ mfa_dispatch_smtp(int sig, short event, void *p) switch (imsg.hdr.type) { case IMSG_MFA_MAIL: - mfa_test_mail(env, imsg.data, PROC_SMTP); + mfa_test_mail(env, imsg.data); break; case IMSG_MFA_RCPT: - mfa_test_rcpt(env, imsg.data, PROC_SMTP); + mfa_test_rcpt(env, imsg.data); break; default: log_warnx("mfa_dispatch_smtp: got imsg %d", @@ -214,12 +214,8 @@ mfa_dispatch_lka(int sig, short event, void *p) struct submit_status *ss; ss = imsg.data; - if (ss->msg.flags & F_MESSAGE_ENQUEUED) - imsg_compose(env->sc_ibufs[PROC_CONTROL], IMSG_MFA_RCPT, - 0, 0, -1, ss, sizeof(*ss)); - else - imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_RCPT, - 0, 0, -1, ss, sizeof(*ss)); + imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_RCPT, + 0, 0, -1, ss, sizeof(*ss)); break; } default: @@ -268,9 +264,6 @@ mfa_dispatch_control(int sig, short event, void *p) break; switch (imsg.hdr.type) { - case IMSG_MFA_RCPT: - mfa_test_rcpt(env, imsg.data, PROC_CONTROL); - break; default: log_warnx("mfa_dispatch_control: got imsg %d", imsg.hdr.type); @@ -398,7 +391,7 @@ msg_cmp(struct message *m1, struct message *m2) } void -mfa_test_mail(struct smtpd *env, struct message *m, int sender) +mfa_test_mail(struct smtpd *env, struct message *m) { struct submit_status ss; @@ -422,7 +415,7 @@ mfa_test_mail(struct smtpd *env, struct message *m, int sender) goto accept; refuse: - imsg_compose(env->sc_ibufs[sender], IMSG_MFA_MAIL, 0, 0, -1, &ss, + imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_MAIL, 0, 0, -1, &ss, sizeof(ss)); return; @@ -433,7 +426,7 @@ accept: } void -mfa_test_rcpt(struct smtpd *env, struct message_recipient *mr, int sender) +mfa_test_rcpt(struct smtpd *env, struct message_recipient *mr) { struct submit_status ss; @@ -451,14 +444,14 @@ mfa_test_rcpt(struct smtpd *env, struct message_recipient *mr, int sender) ! valid_domainpart(ss.u.path.domain)) goto refuse; - if (sender == PROC_SMTP && (ss.flags & F_MESSAGE_AUTHENTICATED)) + if (ss.flags & F_MESSAGE_AUTHENTICATED) goto accept; if (mfa_ruletest_rcpt(env, &ss.u.path, &ss.ss)) goto accept; refuse: - imsg_compose(env->sc_ibufs[sender], IMSG_MFA_RCPT, 0, 0, -1, &ss, + imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_RCPT, 0, 0, -1, &ss, sizeof(ss)); return; diff --git a/usr.sbin/smtpd/queue.c b/usr.sbin/smtpd/queue.c index 278ee2207ab..08f691e3a66 100644 --- a/usr.sbin/smtpd/queue.c +++ b/usr.sbin/smtpd/queue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: queue.c,v 1.58 2009/03/29 14:18:20 jacekm Exp $ */ +/* $OpenBSD: queue.c,v 1.59 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -219,6 +219,7 @@ queue_dispatch_smtp(int sig, short event, void *p) case IMSG_QUEUE_CREATE_MESSAGE: { struct message *messagep; struct submit_status ss; + int (*f)(char *); log_debug("queue_dispatch_smtp: creating message file"); messagep = imsg.data; @@ -226,7 +227,12 @@ queue_dispatch_smtp(int sig, short event, void *p) ss.code = 250; bzero(ss.u.msgid, MAX_ID_SIZE); - if (! queue_create_incoming_layout(ss.u.msgid)) + if (messagep->flags & F_MESSAGE_ENQUEUED) + f = enqueue_create_layout; + else + f = queue_create_incoming_layout; + + if (! f(ss.u.msgid)) ss.code = 421; imsg_compose(ibuf, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, @@ -235,20 +241,37 @@ queue_dispatch_smtp(int sig, short event, void *p) } case IMSG_QUEUE_REMOVE_MESSAGE: { struct message *messagep; + void (*f)(char *); messagep = imsg.data; - queue_delete_incoming_message(messagep->message_id); + if (messagep->flags & F_MESSAGE_ENQUEUED) + f = enqueue_delete_message; + else + f = queue_delete_incoming_message; + + f(messagep->message_id); + break; } case IMSG_QUEUE_COMMIT_MESSAGE: { struct message *messagep; struct submit_status ss; + size_t *counter; + int (*f)(struct message *); messagep = imsg.data; ss.id = messagep->session_id; - if (queue_commit_incoming_message(messagep)) - s_queue.inserts_remote++; + if (messagep->flags & F_MESSAGE_ENQUEUED) { + f = enqueue_commit_message; + counter = &s_queue.inserts_local; + } else { + f = queue_commit_incoming_message; + counter = &s_queue.inserts_remote; + } + + if (f(messagep)) + (*counter)++; else ss.code = 421; @@ -261,11 +284,17 @@ queue_dispatch_smtp(int sig, short event, void *p) struct message *messagep; struct submit_status ss; int fd; + int (*f)(struct message *); messagep = imsg.data; ss.id = messagep->session_id; - fd = queue_open_incoming_message_file(messagep); + if (messagep->flags & F_MESSAGE_ENQUEUED) + f = enqueue_open_messagefile; + else + f = queue_open_incoming_message_file; + + fd = f(messagep); if (fd == -1) ss.code = 421; @@ -473,18 +502,12 @@ queue_dispatch_lka(int sig, short event, void *p) case IMSG_QUEUE_COMMIT_ENVELOPES: { struct message *messagep; struct submit_status ss; - enum smtp_proc_type peer; messagep = imsg.data; ss.id = messagep->session_id; ss.code = 250; - if (messagep->flags & F_MESSAGE_ENQUEUED) - peer = PROC_CONTROL; - else - peer = PROC_SMTP; - - imsg_compose(env->sc_ibufs[peer], IMSG_QUEUE_COMMIT_ENVELOPES, + imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &ss, sizeof(ss)); break; diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 5d59f809169..90fee941759 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.33 2009/04/09 19:49:34 jacekm Exp $ */ +/* $OpenBSD: smtp.c,v 1.34 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -25,6 +25,7 @@ #include <ctype.h> #include <event.h> +#include <netdb.h> #include <pwd.h> #include <signal.h> #include <stdio.h> @@ -529,6 +530,62 @@ smtp_dispatch_control(int sig, short event, void *p) break; switch (imsg.hdr.type) { + case IMSG_SMTP_ENQUEUE: { + static struct listener l; + struct addrinfo hints, *res; + struct session *s; + int fd[2]; + + bzero(&l, sizeof(l)); + l.env = env; + + if (s_smtp.sessions_active >= env->sc_maxconn) { + log_warnx("denying local connection, too many" + " sessions active"); + imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, + imsg.data, sizeof(int)); + break; + } + + if (socketpair( + AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) + fatal("socketpair"); + + if ((s = calloc(1, sizeof(*s))) == NULL) + fatal(NULL); + + s->s_id = queue_generate_id(); + s->s_fd = fd[0]; + s->s_tm = time(NULL); + s->s_env = env; + s->s_l = &l; + s->s_msg.flags |= F_MESSAGE_ENQUEUED; + + bzero(&hints, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + + if (getaddrinfo("::1", NULL, &hints, &res) != 0) + fatal("getaddrinfo"); + + memcpy(&s->s_ss, res->ai_addr, res->ai_addrlen); + + s_smtp.sessions++; + s_smtp.sessions_active++; + + strlcpy(s->s_hostname, "localhost", + sizeof(s->s_hostname)); + strlcpy(s->s_msg.session_hostname, s->s_hostname, + sizeof(s->s_msg.session_hostname)); + + SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s); + + session_init(s->s_l, s); + + imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, fd[1], + imsg.data, sizeof(int)); + break; + } case IMSG_SMTP_PAUSE: smtp_pause(env); break; diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index 5a3931f1d4c..09e8ceef5f2 100644 --- a/usr.sbin/smtpd/smtp_session.c +++ b/usr.sbin/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.65 2009/04/09 20:19:03 todd Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.66 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -901,7 +901,8 @@ session_destroy(struct session *s) close(s->s_fd); s_smtp.sessions_active--; - if (s_smtp.sessions_active < s->s_env->sc_maxconn) + if (s_smtp.sessions_active < s->s_env->sc_maxconn && + !(s->s_msg.flags & F_MESSAGE_ENQUEUED)) event_add(&s->s_l->ev, NULL); if (s->s_bev != NULL) { diff --git a/usr.sbin/smtpd/smtpctl.c b/usr.sbin/smtpd/smtpctl.c index 9f20c59e6df..22f7dea195c 100644 --- a/usr.sbin/smtpd/smtpctl.c +++ b/usr.sbin/smtpd/smtpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpctl.c,v 1.20 2009/04/15 20:34:59 jacekm Exp $ */ +/* $OpenBSD: smtpctl.c,v 1.21 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -76,7 +76,8 @@ usage(void) extern char *__progname; if (sendmail) - fprintf(stderr, "usage: %s [-i] rcpt [...]\n", __progname); + fprintf(stderr, "usage: %s [-tv] [-f from] [-F name] to ..\n", + __progname); else fprintf(stderr, "usage: %s command [argument ...]\n", __progname); exit(1); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index bb8207271eb..99c6bca3455 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.98 2009/04/15 20:34:59 jacekm Exp $ */ +/* $OpenBSD: smtpd.h,v 1.99 2009/04/16 15:35:06 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> @@ -216,7 +216,9 @@ enum imsg_type { IMSG_MTA_RESUME, IMSG_SMTP_RESUME, - IMSG_STATS + IMSG_STATS, + + IMSG_SMTP_ENQUEUE }; #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) @@ -227,20 +229,11 @@ enum blockmodes { BM_NONBLOCK }; -enum ctl_state { - CS_NONE = 0, - CS_INIT, - CS_RCPT, - CS_FD, - CS_DONE -}; - struct ctl_conn { TAILQ_ENTRY(ctl_conn) entry; u_int8_t flags; #define CTL_CONN_NOTIFY 0x01 struct imsgbuf ibuf; - enum ctl_state state; }; TAILQ_HEAD(ctl_connlist, ctl_conn); |