📄 txfr.c
字号:
/* */#ifdef _POSIX_SOURCE#undef _POSIX_SOURCE /* otherwise <sys/types.h> won't define fd_set */#endif#include <sys/types.h> /* BSD type fd_set FD_SET macros */#include <sys/time.h> /* BSD struct timeval */#ifndef _POSIX_SOURCE /* in case the above include files defined it */#define _POSIX_SOURCE /* yes, I know posix doesn't have select */#endif#include <unistd.h> /* alarm read write */#include <stdlib.h> /* exit malloc */#include <stdio.h>#include <errno.h> /* EINTR EAGAIN */#include <signal.h> /* SIGCHLD */#include <string.h> /* memset */#include <fcntl.h> /* O_NONBLOCK */#include <sys/time.h> /* select */#include "posignal.h"#include "log.h"#include "auth.h"#include "txfr.h"#include "deslogin.h"extern unsigned long inBytes, outBytes;#ifndef sparcvolatile #endifunsigned gotalarm = 0;/* * In POSIX, the read or write call will be interrupted by a signal. If so, * the read may return with -1 and errno=EINTR, or, if multi-byte, may return * a result of the number of bytes read. */void alarmCatcher(sig) int sig;{ gotalarm++;}#ifndef sparcvolatile #endifunsigned txfrChld = 0;void chldCatcher(sig) int sig;{ txfrChld++; if (debug) { log("%s: (txfr) SIGCHLD #%d\n", progName, txfrChld); }}int recvData(fd, buf, size, cbuf, cs) int fd; char *buf, *cbuf; unsigned size; register cipherKey cs;{ register int rcount = read(fd, cbuf, size); if (debug > 2) { log("read(%d,%d)=%d(%d)\n", fd, size, rcount, errno); } if (rcount <= 0) { if (rcount < 0) { log("%s: (recv) read %d bytes fd %d failed--%s\n", progName, size, fd, ERRMSG); } return rcount; } cipherData(buf, cbuf, rcount, cs); return rcount;}int sendData(fd, buf, size) int fd; char *buf; unsigned size;{ int wcount; wcount = write(fd, buf, size); if (debug > 2) { log("write(%d,%d)=%d(%d)\n", fd, size, wcount, errno); } /* * We could get -1 and EAGAIN if a write is attempted for less than * fpathconf(_PC_PIPE_BUF) and there isn't space yet. In this case, * if we just returned immediately, we could chew up a lot of CPU time * with select continually returning true. Gag! */ if (wcount < 0) { log("%s: (send) write %d bytes fd %d failed--%s\n", progName, size, fd, ERRMSG); exit(1); } if (wcount == 0) { log("%s: (send) write %d bytes fd %d returned 0\n", progName, size, fd); exit(1); } return wcount;}/* * Set the nonblocking flag for the given fd with error report. * returns the previous nonblock flag or -1 for falure (exits). */int setnonblock(fd, flag) int fd, flag;{ int res, oldflag; oldflag = fcntl(fd, F_GETFL); if (oldflag < 0) { log("%s: (setnonblock) fcntl(%d) F_GETFL failed--%s\n", progName, fd, ERRMSG); exit(1); } if (flag) { res = fcntl(fd, F_SETFL, oldflag | O_NONBLOCK); } else { res = fcntl(fd, F_SETFL, oldflag & ~O_NONBLOCK); } if (res < 0) { log("%s: (setnonblock) fcntl(%d) F_SETFL failed--%s\n", progName, fd, ERRMSG); exit(1); } return ((oldflag & O_NONBLOCK) == O_NONBLOCK);}/* * Select on the master pty before the slave has a successful open behaves * totally differently on every machine I tried. There appears to be no * machine independent way to do both of the following: * * 1) Determine when the slave has been opened * 2) Prevent multiple processes from opening the slave. * * If there was a way to do 2, what we should *really* do is to do a full * bore authentication protocal between the slave and tha master. Since * there is no way to achieve 2 except file permissions on the slave pty, * there isn't any point; anyone that can break the file permissions can * intercept data between the slave pty and the process using it (which * will be the user shell, so we can't help here. * * On DEC Alpha, select lies and returns true for read on the master pty when * the slave hasn't opened it yet. A subsequent read will fail errno=EIO. * It fails to return true for write at all. * It fails to return true for exception. * * On DEC Ultrix 4.3, select returns true for read on the master pty when * the slave hasn't opened it yet. A subsequent read will fail errno=EIO. * It also returns true for write. * It also returns true for exception. * * Do whatever it takes to make sure that txfr won't abort before the slave * side of the pty has been opened. Because select is so unpredicable before * the slave is open, we have to throw up our hands and poll until we don't * get an I/O error. Since we can't poll transparently we have to have * the process on the slave side send us something with mkReady. * * Returns: 0 - if timeout * 1 - success * <0 - failure (Could be from SIGCHLD) */int waitReady(fd, buf, size, timeout) int fd; char *buf; unsigned size; unsigned timeout;{ pfv oldHandler; struct timeval to; int res = 0; gotalarm = 0; oldHandler = posignal(SIGALRM, alarmCatcher); alarm(timeout / 1000); while (!res && !gotalarm) { res = read(fd, buf, size); if (debug) { log("%s: (waitReady) read=%d gotalarm=%d\n", progName, res, gotalarm); } if (res < 0) { if (errno != EIO) { break; } to.tv_sec = 0; /* some selects update to on return */ to.tv_usec = 500000; /* 500 ms polling interval */ res = select(0, 0, 0, 0, &to); /* wait a short while */ if (gotalarm) { res = 0; /* it was -1 from select being interrupted by signal */ } /* res could still be -1 if SIGCHLD */ } } alarm(0); posignal(SIGALRM, oldHandler); return res; /* res > 0 for success, < 0 failure, 0 timeout */}int mkReady(fd, buf, size, timeout) int fd; char *buf; unsigned size; unsigned timeout;{ pfv oldHandler; struct timeval to; int res = 0; gotalarm = 0; oldHandler = posignal(SIGALRM, alarmCatcher); alarm(timeout / 1000); while (!res && !gotalarm) { res = write(fd, buf, size); if (debug > 1) { log("%s: (mkReady) write=%d gotalarm=%d\n", progName, res, gotalarm); } if (res < 0) { if (errno != EIO) { break; } to.tv_sec = 0; /* some selects update to on return */ to.tv_usec = 500000; /* 500 ms polling interval */ res = select(0, 0, 0, 0, &to); /* wait a short while */ if (gotalarm) { res = 0; /* it was -1 from select being interrupted by signal */ } } } alarm(0); posignal(SIGALRM, oldHandler); return res; /* res > 0 for success, < 0 failure, 0 timeout */}/* * Transfer data bidirectionally using a buffer of given size. * Returns when timeout (ms) expires (0 = timeout) * or when eof (res = 1) * or error (res = -1); * or interrupt (res = -2) (or exception, slave closed pty) * * NOTE: there appears to be a bug in HP-UX where a read can return EAGAIN * even if select returns true for both read and write on a tty fd. * On the DEC Alpha: * If the slave side of a pty isn't open yet, the master side's select * will return true for read, and the read call will fail with * EIO. */int txfr(fd1, fdin, fdout, bufSize, timeout, key) int fd1, fdout, fdin; unsigned bufSize; unsigned timeout; keyType key;{ char *cbuf = (char *) malloc(bufSize); char *buf1 = (char *) malloc(bufSize); char *bufin = (char *) malloc(bufSize); struct timeval to, tov, *top = (struct timeval *) 0; /* Not POSIX */ unsigned max_fd = fd1; fd_set rmask, wmask, emask; int result = -1, oldblockin = 0, oldblockout = 0, oldblock1 = 0; int fdinEof = 0; cipherKey cs1 = (cipherKey) 0, csin = (cipherKey) 0; pfv oldChld; register int res, rcount1 = 0, rcountin = 0, wcount; register char *bufp1, *bufpin; if (fdout > max_fd) max_fd = fdout; if (fdin > max_fd) max_fd = fdin; if (buf1 == (char *) 0) goto txfr_done; if (bufin == (char *) 0) goto txfr_done; if (cbuf == (char *) 0) goto txfr_done;#if 0 key = (keyType) 0; /* for debug! */#endif cs1 = mkCipherKey(key, 1); /* decrypt */ csin = mkCipherKey(key, 0); /* encrypt */ if (cs1 == (cipherKey) 0) goto txfr_done; if (csin == (cipherKey) 0) goto txfr_done; bufp1 = buf1; bufpin = bufin; if (timeout != 0) { to.tv_sec = (timeout / 1000); to.tv_usec = (timeout % 1000) * 1000; top = &tov; } /* * We must use non-blocking IO to ensure that write calls won't block. * Read calls are safe anyway. */ oldblock1 = setnonblock(fd1, 1); oldblockin = setnonblock(fdin, 1); /* must be before fdout */ oldblockout = setnonblock(fdout, 1); txfrChld = 0; oldChld = posignal(SIGCHLD, chldCatcher); if (oldChld == (pfv) -1) { log("%s: (txfr) sigaction SIGCHLD failed--%s\n", progName, ERRMSG); exit(1); } FD_ZERO(&emask); do { FD_ZERO(&rmask); FD_ZERO(&wmask); FD_SET(fd1, &emask); #if 0 /* we don't want exception checking on tty or files */ FD_SET(fdout, &emask); FD_SET(fdin, &emask);#endif if (rcount1 != 0) FD_SET(fdout, &wmask); else { FD_SET(fd1, &rmask); } if (rcountin != 0) FD_SET(fd1, &wmask); else if (!fdinEof) { FD_SET(fdin, &rmask); } if (top != (struct timeval *) 0) { tov.tv_sec = to.tv_sec; tov.tv_usec = to.tv_usec; } if (debug > 2) { log("select(%u,%u,%u,%u)\n", max_fd+1, *(unsigned *)&rmask, *(unsigned *)&wmask, *(unsigned *)&emask); } if (txfrChld != 0) { /* must be right next to select */ break; } /* race condition right here */ /* * HP-UX incorrectly declares select with int * args instead of fd_set * Ignore the warnings for select for HP-UX. */ res = select(max_fd+1, &rmask, &wmask, &emask, top); if (debug > 2) { log("select returned: %u(%u,%u,%u)\n", res, *(unsigned *)&rmask, *(unsigned *)&wmask, *(unsigned *)&emask); } if (res == 0) { /* timeout */ result = 0; break; } if (res < 0) { /* error */ if (errno == EINTR) { /* signal (SIGCHLD occurred) */ result = -2; break; } else { log("%s: select failed--%s\n", progName, ERRMSG); break; } } /* * This block will never be triggered because there is no way for a * close on a slave pty to be detected on non-hpux systems. */ if (FD_ISSET(fd1, &emask) #if 0 || FD_ISSET(fdout, &emask) || FD_ISSET(fdin, &emask)#endif ) { /* close */ result = -2; break; } if (FD_ISSET(fd1, &rmask)) { rcount1 = recvData(fd1, buf1, bufSize, cbuf, cs1); if (rcount1 <= 0) { if (rcount1 == 0) { result = 1; break; } else { result = -1; break; } } bufp1 = buf1; inBytes += rcount1; } if (FD_ISSET(fdin, &rmask)) { rcountin = recvData(fdin, bufin, bufSize, cbuf, csin); if (rcountin <= 0) { if (rcountin == 0) { fdinEof = 1; } else { result = -1; break; } } bufpin = bufin; outBytes += rcountin; } if (FD_ISSET(fd1, &wmask)) { wcount = sendData(fd1, bufpin, rcountin); rcountin -= wcount; bufpin += wcount; } if (FD_ISSET(fdout, &wmask)) { wcount = sendData(fdout, bufp1, rcount1); rcount1 -= wcount; bufp1 += wcount; } } while (txfrChld == 0); posignal(SIGCHLD, oldChld); setnonblock(fd1, oldblock1); setnonblock(fdin, oldblockin); /* * We must blindly assume that we don't want non-blocking I/O when whe * leave. The reason is that two fd's may actually be connected to the * same device. If this is the case, then it's possible to switch the * nonblocking mode incorrectly. (e.g. setting NON_BLOCK in fdin also * changes it on fdout) */ setnonblock(fdout, oldblockin); /* oldblockin is not a bug */txfr_done: destroyCipherKey(&csin); destroyCipherKey(&cs1); free(bufin); free(buf1); free(cbuf); return result;}/* * Input a string of upto size chars with timout terminated by CR LF or NULL * Input: * size - the maximum number of characters to read * * Output: * *buf - The characters read not including terminator * A '\0' character is always added to the end of buf * * Returns: the number of characters read (not including terminator) * This is true even if we were interrupted by an alarm. */int getString(fd, buf, size, timeout) int fd; char *buf; unsigned size; unsigned timeout;{ register unsigned count = size; register char *chp = buf; char ch; register unsigned seconds = (unsigned) ((timeout + (1000L-1)) / 1000L); register int rcount; pfv oldHandler; gotalarm = 0; oldHandler = posignal(SIGALRM, alarmCatcher); alarm(seconds); while (count != 0) { rcount = read(fd, &ch, 1); if (rcount <= 0 || gotalarm != 0) break; if (ch == '\r' || ch == '\n' || ch == '\0') break; *chp = ch; count -= rcount; chp += rcount; } alarm(0); posignal(SIGALRM, oldHandler); *chp = '\0'; return size - count;}/* * Read a block of binary data of the given size. * Return the number of bytes read (0 if timeout, or read error). */int getBlock(fd, buf, size, timeout) int fd; char *buf; unsigned size; unsigned timeout;{ register unsigned seconds = (unsigned) ((timeout + (1000-1)) / 1000); register int rcount; pfv oldHandler; gotalarm = 0; oldHandler = posignal(SIGALRM, alarmCatcher); alarm(seconds); rcount = read(fd, buf, size); alarm(0); posignal(SIGALRM, oldHandler); /* * No error checking is reported here. If we got a a signal, we could get * either -1/errno=EINTR, or a short read, depending upon implementation. */ if (rcount < 0) { rcount = 0; } return rcount;}/* * Encipher all the data from sfd to dfd. Send the entire shmeel in timeout * seconds, abort if longer. This routine is used for sending a nologin * message contained in the file /etc/nologin. */int cipherCopy(dfd, sfd, timeout, key) int dfd, sfd; unsigned timeout; keyType key;{ int rcount, wcount = 0; char buf[128], cbuf[128]; register char *bufp = buf; pfv oldHandler; cipherKey cs = mkCipherKey(key, 0); /* encrypt */ if (cs == (cipherKey) 0) return -1; gotalarm = 0; oldHandler = posignal(SIGALRM, alarmCatcher); alarm(timeout); do { while ((rcount = read(sfd, bufp, 1)) > 0) { if (*bufp == '\n') { *bufp++ = '\r'; *bufp = '\n'; } bufp++; if (bufp >= buf + 127 || gotalarm != 0) break; } if (gotalarm != 0) break; rcount = bufp - buf; bufp = buf; cipherData(cbuf, buf, rcount, cs); wcount = write(dfd, cbuf, rcount); if (wcount <= 0) break; } while (gotalarm == 0); alarm(0); posignal(SIGALRM, oldHandler); destroyCipherKey(&cs); if (rcount < 0 || wcount < 0) return -1; return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -