/* cpbidir.c */ /* Copyright 1999-2000 by Eberhard Mattes Donated to the public domain. No warranty. 1999-03-17 Initial version 2000-05-31 Add FLAGS 2000-07-07 */ #include #include #include #include #include #include #include #include #include #include "firewall.h" #include "libemfw.h" #include "emio.h" /* Bidirectionally copy between two file descriptors without risking deadlock. */ #include #include #include "libemfw.h" #include "emio.h" #define CHANNEL_BUFSIZ 4096 #define CB_SHUTDOWN (CB_SHUTDOWN1 | CB_SHUTDOWN2) #define CB_EOF (CB_EOF1 | CB_EOF2) #define CB1 (CB_SHUTDOWN1 | CB_EOF1) #define CB2 (CB_SHUTDOWN2 | CB_EOF2) struct channel { int fdr, fdw; int count, eof; int emi_active; unsigned flags; EMI_FILE *emi; char buf[CHANNEL_BUFSIZ]; }; static int channel_init (struct channel *c, EMI_FILE *emi, int fdr, int fdw, unsigned flags) { int fl; c->fdr = fdr; c->fdw = fdw; c->count = 0; c->eof = 0; c->emi = emi; c->emi_active = (emi != NULL && emi->ptr != emi->end); c->flags = flags; fl = fcntl (fdw, F_GETFL, 0); if (fl != -1) fl = fcntl (fdw, F_SETFL, fl | O_NDELAY); if (fl == -1) { int e = errno; syslog (LLEV, "copy_bidir: fcntl: %s", strerror(errno)); errno = e; return -1; } return 0; } static void channel_fdset (const struct channel *c, fd_set *rfds, fd_set *wfds) { if (!c->eof && !c->emi_active && c->count < CHANNEL_BUFSIZ) FD_SET (c->fdr, rfds); if (c->count > 0 || c->emi_active) FD_SET (c->fdw, wfds); } static int channel_maybe_shutdown (struct channel *c) { if (c->eof && c->count == 0 && !c->emi_active && (c->flags & CB_SHUTDOWN)) { /* No further output */ if (shutdown (c->fdw, 1) != 0 && errno != ENOTSOCK) { int e = errno; syslog (LLEV, "copy_bidir: shutdown: %s", strerror(errno)); errno = e; return -1; } } return 0; } static int channel_read (struct channel *c, fd_set *rfds) { int n; if (c->emi_active) { EMI_FILE *emi = c->emi; int buffered = (int) (emi->end - emi->ptr); int n = CHANNEL_BUFSIZ - c->count; DEBUG_ASSERT (buffered > 0); /* emi_active! */ if (n > buffered) n = buffered; if (n > 0) { memcpy (c->buf + c->count, emi->ptr, n); emi->ptr += n; emi->amount += n; c->count += n; if (emi->ptr == emi->end) c->emi_active = 0; } } else if (FD_ISSET (c->fdr, rfds)) { n = read (c->fdr, c->buf + c->count, CHANNEL_BUFSIZ - c->count); if (n == -1) { int e = errno; syslog (LLEV, "copy_bidir: read: %s", strerror(errno)); errno = e; return -1; } if (n == 0) { c->eof = 1; if (channel_maybe_shutdown (c) != 0) return -1; } c->count += n; if (c->emi != NULL) c->emi->amount += n; } return 0; } static int channel_write (struct channel *c, fd_set *wfds) { int n; if (FD_ISSET (c->fdw, wfds)) { n = write (c->fdw, c->buf, c->count); if (n == -1) { int e = errno; syslog (LLEV, "copy_bidir: write: %s", strerror(errno)); errno = e; return -1; } if (n < c->count) memmove (c->buf, c->buf + n, c->count - n); c->count -= n; if (channel_maybe_shutdown (c) != 0) return -1; } return 0; } /* Any more data to be processed? (This function ignores CB_EOF.) */ static int channel_more (const struct channel *c) { return !c->eof || c->count > 0 || c->emi_active; } /* Channel terminated? (This function uses CB_EOF.) */ static int channel_eof (const struct channel *c) { return (c->flags & CB_EOF) && !channel_more (c); } static int get_fd (int *fd, EMI_FILE *emi) { if (emi != NULL) { if (emi->flags & EMI_FLAG_ERROR) { syslog (LLEV, "copy_bidir: get_fd: EMI_FLAG_ERROR"); errno = EINVAL; return -1; } if (*fd != -1 && *fd != emi->fd) { syslog (LLEV, "copy_bidir: get_fd: file descriptor mismatch"); errno = EINVAL; return -1; } *fd = emi->fd; } return 0; } int copy_bidir_emi (EMI_FILE *emi1, int fd1, EMI_FILE *emi2, int fd2, int timeout, unsigned flags) { struct channel c1, c2; fd_set rfds, wfds; struct timeval tv; int n, maxfd; if (get_fd (&fd1, emi1) != 0) return -1; if (get_fd (&fd2, emi2) != 0) return -1; maxfd = fd1 > fd2 ? fd1 : fd2; if (maxfd >= FD_SETSIZE) { syslog (LLEV, "copy_bidir: FD_SETSIZE exceeded"); errno = EINVAL; return -1; } if (channel_init (&c1, emi1, fd1, fd2, flags & CB1) != 0) return -1; if (channel_init (&c2, emi2, fd2, fd1, flags & CB2) != 0) return -1; while (channel_more (&c1) || channel_more (&c2)) { if (channel_eof (&c1) || channel_eof (&c2)) break; /* CB_EOF */ FD_ZERO (&rfds); FD_ZERO (&wfds); channel_fdset (&c1, &rfds, &wfds); channel_fdset (&c2, &rfds, &wfds); tv.tv_sec = timeout; tv.tv_usec = 0; n = select (maxfd + 1, &rfds, &wfds, (fd_set *)0, &tv); if (n == 0) return 1; /* Timeout */ if (n < 0) { int e = errno; syslog (LLEV, "copy_bidir: select: %s", strerror(errno)); errno = e; return -1; } if (channel_read (&c1, &rfds) != 0) return -1; if (channel_read (&c2, &rfds) != 0) return -1; if (channel_write (&c1, &wfds) != 0) return -1; if (channel_write (&c2, &wfds) != 0) return -1; } return 0; } int copy_bidir (int fd1, int fd2, int timeout, unsigned flags) { return copy_bidir_emi (NULL, fd1, NULL, fd2, timeout, flags); }