/*
 * Copyright (c) 1993 W. Richard Stevens.  All rights reserved.
 * Permission to use or modify this software and its documentation only for
 * educational purposes and without fee is hereby granted, provided that
 * the above copyright notice appear in all copies.  The author makes no
 * representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

#include	"sock.h"

/* Copy everything from stdin to "sockfd",
 * and everything from "sockfd" to stdout. */

void
loop_udp(int sockfd)
{
	int						maxfdp1, nread, ntowrite, stdineof,
							clilen, servlen, flags;
	fd_set					rset;
	struct sockaddr_in		cliaddr;		/* for UDP server */
	struct sockaddr_in		servaddr;		/* for UDP client */

#ifdef	HAVE_MSGHDR_MSG_CONTROL
	struct iovec			iov[1];
	struct msghdr			msg;

#ifdef	IP_RECVDSTADDR		/* 4.3BSD Reno and later */
	static struct cmsghdr  *cmptr = NULL;	/* malloc'ed */
	struct in_addr			dstinaddr;		/* for UDP server */
#define	CONTROLLEN	(sizeof(struct cmsghdr) + sizeof(struct in_addr))
#endif	/* IP_RECVDSTADDR */

#endif	/* HAVE_MSGHDR_MSG_CONTROL */

	if (pauseinit)
		sleep_us(pauseinit*1000);	/* intended for server */

	flags = 0;
	stdineof = 0;
	FD_ZERO(&rset);
	maxfdp1 = sockfd + 1;	/* check descriptors [0..sockfd] */

		/* If UDP client issues connect(), recv() and write() are used.
		   Server is harder since cannot issue connect().  We use recvfrom()
		   or recvmsg(), depending on OS. */

	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(STDIN_FILENO, &rset);
		FD_SET(sockfd, &rset);

		if (select(maxfdp1, &rset, NULL, NULL, NULL) < 0)
			err_sys("select error");

		if (FD_ISSET(STDIN_FILENO, &rset)) {	/* data to read on stdin */
			if ( (nread = read(STDIN_FILENO, rbuf, readlen)) < 0)
				err_sys("read error from stdin");
			else if (nread == 0) {	/* EOF on stdin */
				if (halfclose) {
					if (shutdown(sockfd, SHUT_WR) < 0)
						err_sys("shutdown() error");

					FD_CLR(STDIN_FILENO, &rset);
					stdineof = 1;	/* don't read stdin anymore */
					continue;		/* back to select() */
				}
				break;		/* default: stdin EOF -> done */
			}

			if (crlf) {
				ntowrite = crlf_add(wbuf, writelen, rbuf, nread);
				if (connectudp) {
					if (write(sockfd, wbuf, ntowrite) != ntowrite)
						err_sys("write error");
				} else {
					if (sendto(sockfd, wbuf, ntowrite, 0,
						  (struct sockaddr *) &servaddr, sizeof(servaddr))
																   != ntowrite)
						err_sys("sendto error");
				}
			} else {
				if (connectudp) {
					if (write(sockfd, rbuf, nread) != nread)
						err_sys("write error");
				} else {
					if (sendto(sockfd, rbuf, nread, 0,
						  (struct sockaddr *) &servaddr, sizeof(servaddr))
																      != nread)
						err_sys("sendto error");
				}
			}
		}

		if (FD_ISSET(sockfd, &rset)) {	/* data to read from socket */
			if (server) {
				clilen = sizeof(cliaddr);
#ifndef	HAVE_MSGHDR_MSG_CONTROL	/* vanilla BSD sockets */
				nread = recvfrom(sockfd, rbuf, readlen, 0,
									(struct sockaddr *) &cliaddr, &clilen);

#else	/* 4.3BSD Reno and later; use recvmsg() to get at MSG_TRUNC flag */
		/* Also lets us get at control information (destination address) */


				iov[0].iov_base = rbuf;
				iov[0].iov_len  = readlen;
				msg.msg_iov          = iov;
				msg.msg_iovlen       = 1;
				msg.msg_name         = (caddr_t) &cliaddr;
				msg.msg_namelen      = clilen;

#ifdef	IP_RECVDSTADDR
				if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
					err_sys("malloc error for control buffer");

				msg.msg_control      = (caddr_t) cmptr;	/* for dest address */
				msg.msg_controllen   = CONTROLLEN;
#else
				msg.msg_control      = (caddr_t) 0;	/* no ancillary data */
				msg.msg_controllen   = 0;
#endif	/* IP_RECVDSTADDR */
				msg.msg_flags        = 0;			/* flags returned here */

				nread = recvmsg(sockfd, &msg, 0);
#endif	/* HAVE_MSGHDR_MSG_CONTROL */
				if (nread < 0)
					err_sys("datagram receive error");

				if (verbose) {
					printf("from %s", INET_NTOA(cliaddr.sin_addr));
#ifdef	HAVE_MSGHDR_MSG_CONTROL
#ifdef	IP_RECVDSTADDR
					if (recvdstaddr) {
						if (cmptr->cmsg_len != CONTROLLEN)
							err_quit("control length (%d) != %d",
									 cmptr->cmsg_len, CONTROLLEN);
						if (cmptr->cmsg_level != IPPROTO_IP)
							err_quit("control level != IPPROTO_IP");
						if (cmptr->cmsg_type != IP_RECVDSTADDR)
							err_quit("control type != IP_RECVDSTADDR");
						memcpy(&dstinaddr, CMSG_DATA(cmptr),
							  sizeof(struct in_addr));
						bzero(cmptr, CONTROLLEN);

						printf(", to %s", INET_NTOA(dstinaddr));
					}
#endif	/* IP_RECVDSTADDR */
#endif	/* HAVE_MSGHDR_MSG_CONTROL */
					printf(": ");
					fflush(stdout);
				}

#ifdef	MSG_TRUNC
				if (msg.msg_flags & MSG_TRUNC)
					printf("(datagram truncated)\n");
#endif

			} else if (connectudp) {
					/* msgpeek = 0 or MSG_PEEK */
				flags = msgpeek;
			oncemore:
				if ( (nread = recv(sockfd, rbuf, readlen, flags)) < 0)
					err_sys("recv error");
				else if (nread == 0) {
					if (verbose)
						fprintf(stderr, "connection closed by peer\n");
					break;		/* EOF, terminate */
				}

			} else {
				/* Must use recvfrom() for unconnected UDP client */
				servlen = sizeof(servaddr);
				nread = recvfrom(sockfd, rbuf, readlen, 0,
								 (struct sockaddr *) &servaddr, &servlen);
				if (nread < 0)
					err_sys("datagram recvfrom() error");

				if (verbose) {
					printf("from %s", INET_NTOA(servaddr.sin_addr));
					printf(": ");
					fflush(stdout);
				}
			}

			if (crlf) {
				ntowrite = crlf_strip(wbuf, writelen, rbuf, nread);
				if (writen(STDOUT_FILENO, wbuf, ntowrite) != ntowrite)
					err_sys("writen error to stdout");
			} else {
				if (writen(STDOUT_FILENO, rbuf, nread) != nread)
					err_sys("writen error to stdout");
			}

			if (flags != 0) {
				flags = 0;		/* no infinite loop */
				goto oncemore;	/* read the message again */
			}
		}
	}

	if (pauseclose) {
		if (verbose)
				fprintf(stderr, "pausing before close\n");
		sleep_us(pauseclose*1000);
	}

	if (close(sockfd) < 0)
		err_sys("close error");		/* since SO_LINGER may be set */
}
