| /* source: xioread.c */ |
| /* Copyright Gerhard Rieger and contributors (see file CHANGES) */ |
| /* Published under the GNU General Public License V.2, see file COPYING */ |
| |
| /* this is the source of the extended read function */ |
| |
| #include "xiosysincludes.h" |
| #include "xioopen.h" |
| |
| #include "xio-termios.h" |
| #include "xio-socket.h" |
| #include "xio-posixmq.h" |
| #include "xio-readline.h" |
| #include "xio-openssl.h" |
| |
| |
| /* xioread() performs read() or recvfrom() |
| If result is < 0, errno is valid */ |
| ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { |
| ssize_t bytes; |
| #if WITH_IP6 && 0 |
| int nexthead; |
| #endif |
| struct single *pipe; |
| int _errno; |
| |
| if (file->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) { |
| Error1("xioread(): invalid xiofile descriptor %p", file); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (file->tag == XIO_TAG_DUAL) { |
| pipe = file->dual.stream[0]; |
| if (pipe->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) { |
| Error1("xioread(): invalid xiofile sub descriptor %p[0]", file); |
| errno = EINVAL; |
| return -1; |
| } |
| } else { |
| pipe = &file->stream; |
| } |
| |
| if (pipe->readbytes) { |
| if (pipe->actbytes == 0) { |
| Info1("xioread(%d, ...): readbytes consumed, inserting EOF", pipe->fd); |
| return 0; /* EOF by count */ |
| } |
| |
| if (pipe->actbytes < bufsiz) { |
| bufsiz = pipe->actbytes; |
| } |
| } |
| |
| switch (pipe->dtype & XIODATA_READMASK) { |
| case XIOREAD_STREAM: |
| do { |
| bytes = Read(pipe->fd, buff, bufsiz); |
| } while (bytes < 0 && errno == EINTR); |
| if (bytes < 0) { |
| _errno = errno; |
| switch (_errno) { |
| case EPIPE: |
| case ECONNRESET: |
| /*PASSTHROUGH*/ |
| default: |
| Error4("read(%d, %p, "F_Zu"): %s", |
| pipe->fd, buff, bufsiz, strerror(_errno)); |
| } |
| errno = _errno; |
| return -1; |
| } |
| break; |
| |
| case XIOREAD_PTY: |
| { |
| int eio = 0; |
| bool m = false; /* loop message printed? */ |
| while (true) { |
| do { |
| bytes = Read(pipe->fd, buff, bufsiz); |
| } while (bytes < 0 && errno == EINTR); |
| if (bytes < 0) { |
| _errno = errno; |
| if (_errno != EIO) { |
| Error4("read(%d, %p, "F_Zu"): %s", pipe->fd, buff, bufsiz, strerror(_errno)); |
| errno = _errno; |
| return -1; |
| } |
| if (pipe->para.exec.sitout_eio.tv_sec == 0 && |
| pipe->para.exec.sitout_eio.tv_usec == 0) { |
| Notice4("read(%d, %p, "F_Zu"): %s (probably PTY closed)", |
| pipe->fd, buff, bufsiz, strerror(_errno)); |
| return 0; |
| } |
| if (!m) { |
| /* Starting first iteration: calc and report */ |
| /* Round up to 10ms */ |
| eio = 100*pipe->para.exec.sitout_eio.tv_sec + |
| (pipe->para.exec.sitout_eio.tv_usec+9999)/10000; |
| Notice3("xioread(fd=%d): EIO, sitting out %u.%02lus for recovery", pipe->fd, eio/100, (unsigned long)eio%100); |
| m = true; |
| } |
| poll(NULL, 0, 10); |
| if (--eio <= 0) { |
| /* Timeout */ |
| Error4("read(%d, %p, "F_Zu"): %s", pipe->fd, buff, bufsiz, strerror(_errno)); |
| errno = _errno; |
| return -1; |
| } |
| /* Not reached */ |
| } else |
| break; /* no error */ |
| } |
| } |
| return bytes; |
| break; |
| |
| #if WITH_POSIXMQ |
| case XIOREAD_POSIXMQ: |
| if ((bytes = xioread_posixmq(pipe, buff, bufsiz)) < 0) { |
| return -1; |
| } |
| if (pipe->dtype & XIOREAD_RECV_ONESHOT) { |
| pipe->eof = 2; |
| } |
| break; |
| #endif /* WITH_POSIXMQ */ |
| |
| #if WITH_READLINE |
| case XIOREAD_READLINE: |
| if ((bytes = xioread_readline(pipe, buff, bufsiz)) < 0) { |
| return -1; |
| } |
| break; |
| #endif /* WITH_READLINE */ |
| |
| #if WITH_OPENSSL |
| case XIOREAD_OPENSSL: |
| /* this function prints its error messages */ |
| if ((bytes = xioread_openssl(pipe, buff, bufsiz)) < 0) { |
| return -1; |
| } |
| break; |
| #endif /* WITH_OPENSSL */ |
| |
| #if _WITH_SOCKET |
| case XIOREAD_RECV: |
| if (pipe->dtype & XIOREAD_RECV_NOCHECK) { |
| /* No need to check peer address */ |
| do { |
| bytes = |
| Recv(pipe->fd, buff, bufsiz, 0); |
| } while (bytes < 0 && errno == EINTR); |
| if (bytes < 0) { |
| _errno = errno; |
| Error3("recvfrom(%d, %p, "F_Zu", 0", pipe->fd, buff, bufsiz); |
| errno = _errno; |
| return -1; |
| } |
| Notice1("received packet with "F_Zu" bytes", bytes); |
| if (bytes == 0) { |
| if (!pipe->para.socket.null_eof) { |
| errno = EAGAIN; return -1; |
| } |
| return bytes; |
| } |
| |
| } else if (pipe->dtype & XIOREAD_RECV_FROM) { |
| /* Receiving packets in addresses of RECVFROM types, the sender address |
| has already been determined in OPEN phase. */ |
| Debug1("%s(): XIOREAD_RECV and XIOREAD_RECV_FROM (peer checks already done)", |
| __func__); |
| #if WITH_RAWIP || WITH_UDP || WITH_UNIX |
| struct msghdr msgh = {0}; |
| union sockaddr_union from = {{0}}; |
| socklen_t fromlen = sizeof(from); |
| char infobuff[256]; |
| char ctrlbuff[1024]; /* ancillary messages */ |
| int rc; |
| |
| msgh.msg_name = &from; |
| msgh.msg_namelen = fromlen; |
| #if HAVE_STRUCT_MSGHDR_MSGCONTROL |
| msgh.msg_control = ctrlbuff; |
| #endif |
| #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN |
| msgh.msg_controllen = sizeof(ctrlbuff); |
| #endif |
| while ((rc = xiogetancillary(pipe->fd, &msgh, |
| MSG_PEEK |
| #ifdef MSG_TRUNC |
| |MSG_TRUNC |
| #endif |
| )) < 0 && |
| errno == EINTR) ; |
| if (rc < 0) return -1; |
| |
| /* Note: we do not call xiodopacketinfo() and xiocheckpeer() here because |
| that already happened in xioopen() / _xioopen_dgram_recvfrom() ... */ |
| |
| do { |
| bytes = |
| Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); |
| } while (bytes < 0 && errno == EINTR); |
| if (bytes < 0) { |
| char infobuff[256]; |
| _errno = errno; |
| Error6("recvfrom(%d, %p, "F_Zu", 0, %s, {"F_socklen"}): %s", |
| pipe->fd, buff, bufsiz, |
| sockaddr_info(&from.soa, fromlen, infobuff, sizeof(infobuff)), |
| fromlen, strerror(errno)); |
| errno = _errno; |
| return -1; |
| } |
| |
| #if defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) |
| /* In future versions there may be an option that controls receiving of |
| outgoing packets, but currently it is hardcoded that we try to avoid |
| them - either by once setting socket option PACKET_IGNORE_OUTGOING |
| when available, otherwise by checking flag PACKET_OUTGOING per packet. |
| */ |
| if (from.soa.sa_family == PF_PACKET) { |
| if ((from.ll.sll_pkttype & PACKET_OUTGOING) != 0) { |
| Info2("%s(fd=%d): ignoring outgoing packet", __func__, pipe->fd); |
| errno = EAGAIN; |
| return -1; |
| } |
| Debug2("%s(fd=%d): packet is not outgoing - process it", __func__, pipe->fd); |
| } |
| #endif /* defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) */ |
| |
| #if defined(PF_PACKET) && HAVE_STRUCT_TPACKET_AUXDATA |
| if (from.soa.sa_family == PF_PACKET) { |
| Debug3("xioread(FD=%d, ...): auxdata: flag=%d, vlan-id=%d", |
| pipe->fd, pipe->para.socket.ancill_flag.packet_auxdata, |
| pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci); |
| if (pipe->para.socket.retrieve_vlan && |
| pipe->para.socket.ancill_flag.packet_auxdata && |
| pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci != 0) { |
| int offs = 12; /* packet type id in Ethernet header */ |
| Debug1("xioread(%d, ...): restoring VLAN id from auxdata->tp_vlan_tci", |
| pipe->fd); |
| if (bytes+4 > bufsiz) { |
| Error("buffer too small to restore VLAN id"); |
| } |
| memmove((char *)buff+offs+4, (char *)buff+offs, bytes-offs); |
| ((unsigned short *)((char *)buff+offs))[0] = htons(ETH_P_8021Q); |
| ((unsigned short *)((char *)buff+offs))[1] = |
| htons(pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci); |
| bytes += 4; |
| } |
| } |
| #endif /* defined(PF_PACKET && HAVE_STRUCT_TPACKET_AUXDATA */ |
| |
| Notice2("received packet with "F_Zu" bytes from %s", |
| bytes, |
| sockaddr_info(&from.soa, fromlen, infobuff, sizeof(infobuff))); |
| if (bytes == 0) { |
| if (!pipe->para.socket.null_eof) { |
| errno = EAGAIN; return -1; |
| } |
| return bytes; |
| } |
| |
| if (pipe->peersa.soa.sa_family != PF_UNSPEC) { |
| /* a peer address is registered, so we need to check if it matches */ |
| #if 0 /* with UNIX sockets we find inconsistent lengths */ |
| if (fromlen != pipe->salen) { |
| Info("recvfrom(): wrong peer address length, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| #endif |
| if (pipe->dtype & XIOREAD_RECV_SKIPIP) { |
| if (pipe->peersa.soa.sa_family != from.soa.sa_family) { |
| Info("recvfrom(): wrong peer protocol, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| #if WITH_IP4 |
| switch (pipe->peersa.soa.sa_family) { |
| case PF_INET: |
| if (pipe->peersa.ip4.sin_addr.s_addr != |
| from.ip4.sin_addr.s_addr) { |
| Info("recvfrom(): wrong peer address, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| break; |
| } |
| #endif /* WITH_IP4 */ |
| } else { |
| switch (pipe->peersa.soa.sa_family) { |
| #if 0 |
| case PF_UNIX: |
| if (strncmp(pipe->peersa.un.sun_path, from.un.sun_path, |
| sizeof(from.un.sun_path))) { |
| Info("recvfrom(): wrong peer address, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| break; |
| #endif |
| #if WITH_IP6 |
| case PF_INET6: |
| /* e.g. Solaris recvfrom sets a __sin6_src_id component */ |
| if (memcmp(&from.ip6.sin6_addr, &pipe->peersa.ip6.sin6_addr, |
| sizeof(from.ip6.sin6_addr)) || |
| from.ip6.sin6_port != pipe->peersa.ip6.sin6_port) { |
| Info("recvfrom(): wrong peer address, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| break; |
| #endif /* WITH_IP6 */ |
| default: |
| if (memcmp(&from, &pipe->peersa, fromlen)) { |
| Info("recvfrom(): wrong peer address, ignoring packet"); |
| errno = EAGAIN; return -1; |
| } |
| } |
| } |
| } |
| |
| switch(from.soa.sa_family) { |
| #if HAVE_STRUCT_IP |
| int headlen; |
| #endif /* HAVE_STRUCT_IP */ |
| #if WITH_IP4 |
| case AF_INET: |
| #if HAVE_STRUCT_IP |
| if (pipe->dtype & XIOREAD_RECV_SKIPIP) { |
| /* IP4 raw sockets include the header when passing a packet to the |
| application - we don't need it here. */ |
| #if HAVE_STRUCT_IP_IP_HL |
| headlen = 4*((struct ip *)buff)->ip_hl; |
| #else /* happened on Tru64 */ |
| headlen = 4*((struct ip *)buff)->ip_vhl; |
| #endif |
| if (headlen > bytes) { |
| Warn1("xioread(%d, ...)/IP4: short packet", pipe->fd); |
| bytes = 0; |
| } else { |
| memmove(buff, ((char *)buff)+headlen, bytes-headlen); |
| bytes -= headlen; |
| } |
| } |
| #endif /* HAVE_STRUCT_IP */ |
| break; |
| #endif |
| #if WITH_IP6 |
| case AF_INET6: |
| /* does not seem to include header on Linux */ |
| /* but sometimes on AIX */ |
| break; |
| #endif |
| default: |
| /* do nothing, for now */ |
| break; |
| } |
| if (pipe->dtype & XIOREAD_RECV_ONESHOT) { |
| #if 1 |
| pipe->eof = 2; |
| #else |
| Shutdown(pipe->fd, SHUT_RD); |
| #endif |
| if (pipe->triggerfd >= 0) { |
| Info("notifying parent that socket is ready again"); |
| Close(pipe->triggerfd); |
| pipe->triggerfd = -1; |
| } |
| } |
| |
| #if 0 |
| if (fromlen != pipe->fd[0].salen) { |
| Debug("recvfrom(): wrong peer address length, ignoring packet"); |
| continue; |
| } |
| if (memcmp(&from, &pipe->fd[0].peersa.sa, fromlen)) { |
| Debug("recvfrom(): other peer address, ignoring packet"); |
| Debug16("peer: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", |
| pipe->fd[0].peersa.space[0], |
| pipe->fd[0].peersa.space[1], |
| pipe->fd[0].peersa.space[2], |
| pipe->fd[0].peersa.space[3], |
| pipe->fd[0].peersa.space[4], |
| pipe->fd[0].peersa.space[5], |
| pipe->fd[0].peersa.space[6], |
| pipe->fd[0].peersa.space[7], |
| pipe->fd[0].peersa.space[8], |
| pipe->fd[0].peersa.space[9], |
| pipe->fd[0].peersa.space[10], |
| pipe->fd[0].peersa.space[11], |
| pipe->fd[0].peersa.space[12], |
| pipe->fd[0].peersa.space[13], |
| pipe->fd[0].peersa.space[14], |
| pipe->fd[0].peersa.space[15]); |
| Debug16("from: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", |
| from.space[0], from.space[1], |
| from.space[2], from.space[3], |
| from.space[4], from.space[5], |
| from.space[6], from.space[7], |
| from.space[8], from.space[9], |
| from.space[10], from.space[11], |
| from.space[12], from.space[13], |
| from.space[14], from.space[15]); |
| continue; |
| } |
| #endif |
| #else /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */ |
| Fatal("address requires raw sockets, but they are not compiled in"); |
| return -1; |
| #endif /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */ |
| |
| } else /* ~(XIOREAD_RECV_FROM|XIOREAD_RECV_FROM) */ { |
| /* Receiving packets without planning to answer to the sender, but we |
| might need sender info for some checks, thus we use recvfrom() */ |
| struct msghdr msgh = {0}; |
| union sockaddr_union from = {{ 0 }}; |
| socklen_t fromlen = sizeof(from); |
| char infobuff[256]; |
| char ctrlbuff[1024]; /* ancillary messages */ |
| int rc; |
| |
| Debug1("%s(): XIOREAD_RECV and not XIOREAD_RECV_FROM (peer checks to be done)", |
| __func__); |
| |
| /* get source address */ |
| msgh.msg_name = &from; |
| msgh.msg_namelen = fromlen; |
| #if HAVE_STRUCT_MSGHDR_MSGCONTROL |
| msgh.msg_control = ctrlbuff; |
| #endif |
| #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN |
| msgh.msg_controllen = sizeof(ctrlbuff); |
| #endif |
| while ((rc = xiogetancillary(pipe->fd, &msgh, |
| MSG_PEEK |
| #ifdef MSG_TRUNC |
| |MSG_TRUNC |
| #endif |
| )) < 0 && |
| errno == EINTR) ; |
| if (rc < 0) return -1; |
| |
| xiodopacketinfo(pipe, &msgh, true, false); |
| if (xiocheckpeer(pipe, &from, &pipe->para.socket.la) < 0) { |
| Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); /* drop */ |
| errno = EAGAIN; return -1; |
| } |
| Info1("permitting packet from %s", |
| sockaddr_info((struct sockaddr *)&from, fromlen, |
| infobuff, sizeof(infobuff))); |
| |
| do { |
| bytes = |
| Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); |
| } while (bytes < 0 && errno == EINTR); |
| if (bytes < 0) { |
| char infobuff[256]; |
| _errno = errno; |
| Error6("recvfrom(%d, %p, "F_Zu", 0, %s, "F_socklen"): %s", |
| pipe->fd, buff, bufsiz, |
| sockaddr_info(&from.soa, fromlen, infobuff, sizeof(infobuff)), |
| fromlen, strerror(errno)); |
| errno = _errno; |
| return -1; |
| } |
| |
| #if defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) |
| /* For remarks see similar section above */ |
| if (from.soa.sa_family == PF_PACKET) { |
| if ((from.ll.sll_pkttype & PACKET_OUTGOING) != 0) { |
| Info2("%s(fd=%d): ignoring outgoing packet", __func__, pipe->fd); |
| errno = EAGAIN; |
| return -1; |
| } |
| Debug2("%s(fd=%d): packet is not outgoing - process it", __func__, pipe->fd); |
| } |
| #endif /* defined(PF_PACKET) && !defined(PACKET_IGNORE_OUTGOING) && defined(PACKET_OUTGOING) */ |
| |
| #if defined(PF_PACKET) && HAVE_STRUCT_TPACKET_AUXDATA |
| if (from.soa.sa_family == PF_PACKET) { |
| Debug3("xioread(%d, ...): auxdata: flag=%d, vlan-id=%d", |
| pipe->fd, pipe->para.socket.ancill_flag.packet_auxdata, |
| pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci); |
| if (pipe->para.socket.ancill_flag.packet_auxdata && |
| pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci && |
| pipe->para.socket.ancill_data_packet_auxdata.tp_net >= 2) { |
| Debug2("xioread(%d, ...): restoring VLAN id %d", pipe->fd, pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci); |
| int offs = pipe->para.socket.ancill_data_packet_auxdata.tp_net - 2; |
| if (bytes+4 > bufsiz) { |
| Error("buffer too small to restore VLAN id"); |
| } |
| Debug3("xioread(): memmove(%p, %p, "F_Zu")", (char *)buff+offs+4, (char *)buff+offs, bytes-offs); |
| memmove((char *)buff+offs+4, (char *)buff+offs, bytes-offs); |
| ((unsigned short *)((char *)buff+offs))[0] = htons(ETH_P_8021Q); |
| ((unsigned short *)((char *)buff+offs))[1] = |
| htons(pipe->para.socket.ancill_data_packet_auxdata.tp_vlan_tci); |
| bytes += 4; |
| } |
| } |
| #endif /* defined(PF_PACKET) &&& HAVE_STRUCT_TPACKET_AUXDATA */ |
| |
| Notice2("received packet with "F_Zu" bytes from %s", |
| bytes, |
| sockaddr_info(&from.soa, fromlen, infobuff, sizeof(infobuff))); |
| |
| if (bytes == 0) { |
| if (!pipe->para.socket.null_eof) { |
| errno = EAGAIN; return -1; |
| } |
| return bytes; |
| } |
| |
| switch(from.soa.sa_family) { |
| #if HAVE_STRUCT_IP |
| int headlen; |
| #endif /* HAVE_STRUCT_IP */ |
| #if WITH_IP4 |
| case AF_INET: |
| #if HAVE_STRUCT_IP |
| if (pipe->dtype & XIOREAD_RECV_SKIPIP) { |
| /* IP4 raw sockets include the header when passing a packet to the |
| application - we don't need it here. */ |
| #if HAVE_STRUCT_IP_IP_HL |
| headlen = 4*((struct ip *)buff)->ip_hl; |
| #else /* happened on Tru64 */ |
| headlen = 4*((struct ip *)buff)->ip_vhl; |
| #endif |
| if (headlen > bytes) { |
| Warn1("xioread(%d, ...)/IP4: short packet", pipe->fd); |
| bytes = 0; |
| } else { |
| memmove(buff, ((char *)buff)+headlen, bytes-headlen); |
| bytes -= headlen; |
| } |
| } |
| #endif /* HAVE_STRUCT_IP */ |
| break; |
| #endif |
| #if WITH_IP6 |
| case AF_INET6: /* does not seem to include header... */ |
| break; |
| #endif |
| default: |
| /* do nothing, for now */ |
| break; |
| } |
| |
| } |
| break; |
| #endif /* _WITH_SOCKET */ |
| |
| default: |
| Error("internal: undefined read operation"); |
| errno = EINVAL; return -1; |
| } |
| pipe->actbytes -= bytes; |
| return bytes; |
| } |
| |
| |
| /* this function is intended only for some special address types where the |
| select()/poll() calls cannot strictly determine if (more) read data is |
| available. currently this is for the OpenSSL based addresses. |
| */ |
| ssize_t xiopending(xiofile_t *file) { |
| struct single *pipe; |
| |
| if (file->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) { |
| Error1("xiopending(): invalid xiofile descriptor %p", file); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (file->tag == XIO_TAG_DUAL) { |
| pipe = file->dual.stream[0]; |
| if (pipe->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) { |
| Error1("xiopending(): invalid xiofile sub descriptor %p[0]", file); |
| errno = EINVAL; |
| return -1; |
| } |
| } else { |
| pipe = &file->stream; |
| } |
| |
| switch (pipe->dtype & XIODATA_READMASK) { |
| #if WITH_OPENSSL |
| case XIOREAD_OPENSSL: |
| return xiopending_openssl(pipe); |
| #endif /* WITH_OPENSSL */ |
| default: |
| return 0; |
| } |
| } |
| |