/* $OpenBSD: imsg.c,v 1.2 2008/12/04 17:24:13 cloder Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smtpd.h" static u_int32_t imsgid = 1; void imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *)) { if (!ibuf->pid) { msgbuf_init(&ibuf->w); bzero(&ibuf->r, sizeof(ibuf->r)); ibuf->pid = getpid(); ibuf->handler = handler; TAILQ_INIT(&ibuf->fds); } ibuf->fd = fd; ibuf->w.fd = fd; } ssize_t imsg_read(struct imsgbuf *ibuf) { struct msghdr msg; struct cmsghdr *cmsg; union { struct cmsghdr hdr; char buf[CMSG_SPACE(sizeof(int) * 16)]; } cmsgbuf; struct iovec iov; ssize_t n; int fd; struct imsg_fd *ifd; bzero(&msg, sizeof(msg)); iov.iov_base = ibuf->r.buf + ibuf->r.wpos; iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { if (errno != EINTR && errno != EAGAIN && errno != EMSGSIZE) { log_warn("imsg_read: pipe read error"); return (-1); } return (-2); } ibuf->r.wpos += n; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { fd = (*(int *)CMSG_DATA(cmsg)); if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) fatal("imsg_read calloc"); ibuf->id = imsgid++; ifd->fd = fd; ifd->id = ibuf->id; TAILQ_INSERT_TAIL(&ibuf->fds, ifd, entry); } else log_warn("imsg_read: got unexpected ctl data level %d " "type %d", cmsg->cmsg_level, cmsg->cmsg_type); } return (n); } ssize_t imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) { size_t av, left, datalen; av = ibuf->r.wpos; if (IMSG_HEADER_SIZE > av) return (0); memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); if (imsg->hdr.len < IMSG_HEADER_SIZE || imsg->hdr.len > MAX_IMSGSIZE) { log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u", imsg->hdr.len, imsg->hdr.type); return (-1); } if (imsg->hdr.len > av) return (0); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; if ((imsg->data = malloc(datalen)) == NULL) { log_warn("imsg_get"); return (-1); } memcpy(imsg->data, ibuf->r.rptr, datalen); imsg->id = 0; if (ibuf->id != 0) { imsg->id = ibuf->id; ibuf->id = 0; } if (imsg->hdr.len < av) { left = av - imsg->hdr.len; memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); ibuf->r.wpos = left; } else ibuf->r.wpos = 0; return (datalen + IMSG_HEADER_SIZE); } int imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, pid_t pid, int fd, void *data, u_int16_t datalen) { struct buf *wbuf; int n; if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) return (-1); if (imsg_add(wbuf, data, datalen) == -1) return (-1); wbuf->fd = fd; if ((n = imsg_close(ibuf, wbuf)) < 0) return (-1); return (n); } int imsg_composev(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, pid_t pid, int fd, const struct iovec *iov, int iovcnt) { struct buf *wbuf; int n; int i, datalen = 0; for (i = 0; i < iovcnt; i++) datalen += iov[i].iov_len; if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) return (-1); for (i = 0; i < iovcnt; i++) if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) return (-1); wbuf->fd = fd; if ((n = imsg_close(ibuf, wbuf)) < 0) return (-1); return (n); } /* ARGSUSED */ struct buf * imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid, pid_t pid, u_int16_t datalen) { struct buf *wbuf; struct imsg_hdr hdr; datalen += IMSG_HEADER_SIZE; if (datalen > MAX_IMSGSIZE) { log_warnx("imsg_create: len %zu > MAX_IMSGSIZE; " "type %u peerid %u", datalen + IMSG_HEADER_SIZE, type, peerid); return (NULL); } hdr.type = type; hdr.peerid = peerid; if ((hdr.pid = pid) == 0) hdr.pid = ibuf->pid; if ((wbuf = buf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { log_warn("imsg_create: buf_open"); return (NULL); } if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) return (NULL); return (wbuf); } int imsg_add(struct buf *msg, void *data, u_int16_t datalen) { if (datalen) if (buf_add(msg, data, datalen) == -1) { log_warnx("imsg_add: buf_add error"); buf_free(msg); return (-1); } return (datalen); } int imsg_append(struct imsgbuf *ibuf, struct buf *msg) { int n; struct imsg_hdr *hdr; hdr = (struct imsg_hdr *)msg->buf; hdr->len = (u_int16_t)msg->wpos; if ((n = buf_close(&ibuf->w, msg)) < 0) { log_warnx("imsg_close: buf_close error"); buf_free(msg); return (-1); } return (n); } int imsg_close(struct imsgbuf *ibuf, struct buf *msg) { int n; struct imsg_hdr *hdr; hdr = (struct imsg_hdr *)msg->buf; hdr->len = (u_int16_t)msg->wpos; if ((n = buf_close(&ibuf->w, msg)) < 0) { log_warnx("imsg_close: buf_close error"); buf_free(msg); return (-1); } imsg_event_add(ibuf); return (n); } void imsg_free(struct imsg *imsg) { free(imsg->data); } int imsg_get_fd(struct imsgbuf *ibuf, struct imsg *imsg) { int fd; struct imsg_fd *ifd; if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) return (-1); TAILQ_FOREACH(ifd, &ibuf->fds, entry) { if (ifd->id == imsg->id) break; } if (ifd == NULL) return (-1); fd = ifd->fd; TAILQ_REMOVE(&ibuf->fds, ifd, entry); free(ifd); return (fd); } int imsg_flush(struct imsgbuf *ibuf) { while (ibuf->w.queued) if (msgbuf_write(&ibuf->w) < 0) return (-1); return (0); } void imsg_clear(struct imsgbuf *ibuf) { while (ibuf->w.queued) msgbuf_clear(&ibuf->w); }