/* 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; } }