#include <conf.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#ifdef	HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <netinet/in.h>
#include <netdb.h>
#ifdef	HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef	HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef	HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef	HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <x_types.h>
#include <sysutils.h>
#include <netutils.h>
#include <utils.h>
#ifdef	HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#define	CLEANUP	{ goto cleanup; }
#define	GETOUT	{ goto getout; }


#define	EMPTY_USER_DATA	((void *) 1)

typedef struct _tcpmux_status {
  int		listensock;
  Int32		maxconn;
  Int32		num_conns;
  void		**conn_user_data;
  int		*conns;
  FILE		**conn_fps;
  fd_set	allfds;
  int		maxfd;
  Flag		*closed_conns;
  Flag		have_closed_conns;
  void		(*failed_conn_func)(int, void *, void *);
  void		*(*conn_init_func)(int, Int32, void *, struct sockaddr_in *,
				void *, TcpMuxCallbDoneActions *);
  void		*data;
  Uns32		flags;
  void		*conn_user_data_to_be_freed;
} TcpMuxStatus;

static Flag
accept_connection(TcpMuxStatus * status, Flag in_subr)
{
  int		fl, fd;
  Flag		end = NO;
  Int32		sock_optlen[2];
#define	addr_len sock_optlen
  struct sockaddr_in	peeraddr;
  TcpMuxCallbDoneActions	init_done_actions;

  fl = fcntl(status->listensock, F_GETFL);
  fcntl(status->listensock, F_SETFL, fl | O_NONBLOCK | O_NDELAY);

  *((int *) &(addr_len[0])) = sizeof(peeraddr);
  fd = accept(status->listensock,
			(struct sockaddr * ) (& peeraddr), (int *) &(addr_len[0]));
  if(fd < 0 || (status->maxconn > 0 && status->num_conns + 1 > status->maxconn)){
    if(status->failed_conn_func)
	status->failed_conn_func(fd >= 0 ? fd : status->listensock, status->data,
			status);

    if(fd >= 0)
	close(fd);
  }
  else{
    fcntl(status->listensock, F_SETFL, fl & (~ O_NONBLOCK) & (~ O_NDELAY));

    fl = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, fl & (~ O_NONBLOCK) & (~ O_NDELAY));

    fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | 1);

    if(in_subr && ! status->conn_user_data_to_be_freed){
		/* the memory space must be valid until exit from input_proc_func, */
		/* cause it might be in use by that function, so we store it in a */
		/* status field and free it later */
	status->conn_user_data_to_be_freed = status->conn_user_data;

	status->conn_user_data = NEWP(void *, status->num_conns + 2);

	memcpy(status->conn_user_data, status->conn_user_data_to_be_freed,
			sizeof(void *) * (status->num_conns + 1));
    }
    else{
	status->conn_user_data = ZRENEWP(status->conn_user_data, void *, status->num_conns + 2);
    }

    status->conns = ZRENEWP(status->conns, int, status->num_conns + 1);
    status->closed_conns = ZRENEWP(status->closed_conns, Flag, status->num_conns + 1);
    status->conn_fps = ZRENEWP(status->conn_fps, FILE *, status->num_conns + 1);

    status->closed_conns[status->num_conns] = NO;
    status->conn_user_data[status->num_conns + 1] = NULL;

    if(status->conn_init_func){
	SETZERO(init_done_actions);
	init_done_actions.conns_to_close = status->closed_conns;

	status->conn_user_data[status->num_conns] = (*status->conn_init_func)(fd,
			status->num_conns, &status->conn_user_data,
			&peeraddr, status->data, &init_done_actions);
	if(!status->conn_user_data[status->num_conns]){
	  close(fd);
	  fd = -1;
	  if(status->num_conns < 1 && (status->flags & TCPMUX_STOP_ON_LAST_CLOSE))
	    end = YES;
	}

	if(init_done_actions.have_conns_to_close)
	  status->have_closed_conns = YES;
    }
    else
      status->conn_user_data[status->num_conns] = EMPTY_USER_DATA;

    if(fd >= 0){
	status->conns[status->num_conns] = fd;
	status->conn_fps[status->num_conns] = fdopen(fd, "r+");
	FD_SET(fd, &status->allfds);
	if(fd > status->maxfd)
	  status->maxfd = fd;

	status->num_conns++;
    }
  }

  return(end);
}

Int32
tcp_mux_long_io(
  void *	tmstatptr,
  int		fd,
  UChar *	data,
  Int32		num,
  Int32		do_write,
  Int32		(*rw_func)(int, UChar *, Int32))
{
  fd_set	rfds, wfds;
  int		sock;
  Int32		i, n;
  TcpMuxStatus	*tcpmux_status;
  Flag		lastconn;

  if(!tmstatptr)
    return((*rw_func)(fd, data, num));

  tcpmux_status = (TcpMuxStatus *) tmstatptr;
  sock = tcpmux_status->listensock;

  n = MAX(fd, sock) + 1;

  forever{
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_SET(sock, &rfds);
    FD_SET(fd, (do_write ? (&wfds) : (&rfds)));
    i = select(n, &rfds, &wfds, NULL, NULL);
    if(i <= 0)
	return(i);

    if(FD_ISSET(fd, (do_write ? (&wfds) : (&rfds))))
	return((*rw_func)(fd, data, num));

    if(FD_ISSET(sock, &rfds)){
	lastconn = accept_connection(tmstatptr, YES);
	if(lastconn)
	  fprintf(stderr, "Internal error in netutils.c, 1.\n");
    }
  }
}

Int32
tcp_mux_service(
  int		port,
  void		*(*conn_init_func)(int, Int32, void *, struct sockaddr_in *, void *, TcpMuxCallbDoneActions *),
  Int32		(*input_proc_func)(int, void *, Int32, void *, TcpMuxCallbDoneActions *, void *),
  Int32		(*conn_deinit_func)(int, void *, Int32, void *, void *),
  Int32		maxconn,
  void		(*failed_conn_func)(int, void *, void *),
  Uns32		flags,
  void		*data)
{
  Int32		i, j;
  Int32		r = 0;
  Int32		actconn = 0;
  struct sockaddr_in	addr;
  int		sock_optval, sockfd_socktype;
  Int32		sock_optlen[2];
  Flag		end = NO;
  fd_set	tmpfds;
  TcpMuxStatus	status;
  TcpMuxCallbDoneActions	input_done_actions;
  struct sigaction	pipe_sigact, org_sigact;

  SETZERO(status);
  status.maxconn = maxconn;
  status.failed_conn_func = failed_conn_func;
  status.conn_init_func = conn_init_func;
  status.data = data;
  status.flags = flags;

  SETZERO(pipe_sigact);
  pipe_sigact.sa_handler = SIG_IGN;
#ifdef	SA_RESTART
  pipe_sigact.sa_flags = SA_RESTART;
#endif
  sigaction(SIGPIPE, &pipe_sigact, &org_sigact);

  if(flags & TCPMUX_INETD_STARTED){
    status.listensock = 0;
  }
  else{
    status.listensock = socket(AF_INET, SOCK_STREAM, 0);

    *((int *) &(sock_optlen[0])) = sizeof(int);
    i = getsockopt(status.listensock, SOL_SOCKET, SO_TYPE,
			(char *) &sockfd_socktype, (int *) &(sock_optlen[0]));
    if(i){
	sockfd_socktype = -1;
    }

    sock_optval = 1;
    if(sockfd_socktype == SOCK_STREAM){
	if( (i = setsockopt(status.listensock, SOL_SOCKET, SO_REUSEADDR,
				(char *) &sock_optval, sizeof(int))) < 0){
	  r = -1;
	  CLEANUP;
	}
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(0L);

    i = bind(status.listensock, (struct sockaddr * ) (& addr), sizeof(addr));

    i = listen(status.listensock, 64);
  }

  fcntl(status.listensock, F_SETFD, fcntl(status.listensock, F_GETFD) | 1);

  FD_ZERO(&status.allfds);
  FD_SET(status.listensock, &status.allfds);

  status.maxfd = status.listensock;

  do{
    status.have_closed_conns = NO;

    COPYVAL(tmpfds, status.allfds);
    i = select(status.maxfd + 1, &tmpfds, NULL, NULL, 0);

    memset(status.closed_conns, 0, status.num_conns * sizeof(status.closed_conns[0]));

    if(FD_ISSET(status.listensock, &tmpfds)){
	if(accept_connection(&status, NO))
	  end = YES;
    }
    else{
	for(i = 0; i < status.num_conns; i++){
	  if(actconn >= status.num_conns)
	    actconn = 0;

	  if(FD_ISSET(status.conns[actconn], &tmpfds)){
	    if(input_proc_func){
		SETZERO(input_done_actions);
		input_done_actions.conns_to_close = status.closed_conns;

		j = (*input_proc_func)(status.conns[actconn],
				status.conn_user_data + actconn, actconn, data,
				&input_done_actions, &status);

		if(j)
		  status.closed_conns[actconn] = status.have_closed_conns = YES;

		ZFREE(status.conn_user_data_to_be_freed);

		if(input_done_actions.have_conns_to_close)
		  status.have_closed_conns = YES;
	    }
	  }

	  actconn++;
	}
    }

    if(status.have_closed_conns){
      for(i = 0; i < status.num_conns; i++){
	if(status.closed_conns[i]){
	  if(conn_deinit_func){
	    j = (*conn_deinit_func)(status.conns[i], status.conn_user_data + i, i,
					data, &status);
	  }

	  FD_CLR(status.conns[i], &status.allfds);

	  if(status.conn_fps[i])
	    fclose(status.conn_fps[i]);
	  else
	    close(status.conns[i]);

	  if(status.conns[i] == status.maxfd)
	    status.maxfd--;

	  memmove(status.conns + i, status.conns + i + 1,
			(status.num_conns - i - 1) * sizeof(status.conns[0]));
	  memmove(status.conn_fps + i, status.conn_fps + i + 1,
			(status.num_conns - i - 1) * sizeof(status.conn_fps[0]));
	  memmove(status.closed_conns + i, status.closed_conns + i + 1,
			(status.num_conns - i - 1) * sizeof(status.closed_conns[0]));
	  memmove(status.conn_user_data + i, status.conn_user_data + i + 1,
			(status.num_conns - i) * sizeof(status.conn_user_data[0]));

	  i--;
	  status.num_conns--;

	  if(status.num_conns < 1 && (flags & TCPMUX_STOP_ON_LAST_CLOSE)){
	    end = YES;
	    break;
	  }
	}
      }
    }
  } while(! end);

 cleanup:

  close(status.listensock);

  ZFREE(status.conns);
  ZFREE(status.conn_fps);
  ZFREE(status.closed_conns);
  ZFREE(status.conn_user_data);

  sigaction(SIGPIPE, &org_sigact, NULL);

  return(r);
}

UChar *
get_my_off_hn()
{
  struct utsname	unamb;
  struct hostent	*hp;

  if(uname(&unamb) < 0)
    return(NULL);

  hp = gethostbyname(unamb.nodename);
  if(!hp)
    return(NULL);

  return(strdup(hp->h_name));
}

Int8
same_host(UChar * hn1, UChar * hn2)
{
  struct hostent	*hep1, *hep2;
  Int32		i, j, n1, n2, l1, l2;
  Int8		r = 0, ce;
  char		*addresses1 = NULL, *addresses2 = NULL;

  if(! hn1 || ! hn2){
    errno = EINVAL;
    return(-1);
  }

  ce = (strcmp(hn1, hn2) ? 0 : 2);

  if(ce)
    return(ce);

  hep1 = gethostbyname(hn1);
  if(!hep1)
    return(ce);

  l1 = hep1->h_length;
  for(n1 = 0; hep1->h_addr_list[n1]; n1++);
  addresses1 = NEWP(UChar, n1 * l1);
  if(!addresses1){
    r = - errno;
    GETOUT;
  }

  for(i = 0; i < n1; i++)
    memcpy(addresses1 + i * l1, hep1->h_addr_list[i], l1);

  hep2 = gethostbyname(hn2);
  if(!hep2){
    r = ce;
    CLEANUP;
  }

  l2 = hep2->h_length;
  for(n2 = 0; hep2->h_addr_list[n2]; n2++);
  addresses2 = NEWP(UChar, n2 * l2);
  if(!addresses2){
    r = - errno;
    GETOUT;
  }

  for(i = 0; i < n2; i++)
    memcpy(addresses2 + i * l2, hep2->h_addr_list[i], l2);

  if(l1 != l2){
    r = ce;
    CLEANUP;
  }

  for(i = 0; i < n1; i++){
    for(j = 0; j < n2; j++){
	if(! memcmp(addresses1 + i * l1, addresses2 + j * l2, l1)){
	  r = 1;
	  i = n1;
	  break;
	}
    }
  }

 cleanup:
  ZFREE(addresses1);
  ZFREE(addresses2);

  return(r);

 getout:
  CLEANUP;
}

int
get_tcp_portnum(UChar * servicename)
{
  struct servent	*se;
  int			n, p, len, l;
  UChar			*sn_cp;

  if(!servicename){
    errno = EINVAL;
    return(-1);
  }

  sn_cp = strdup(servicename);
  if(!sn_cp)
    return(-1);

  massage_string(sn_cp);
  len = strlen(sn_cp);

  l = -1;
  n = sscanf(sn_cp, "%d%n", &p, &l);
  free(sn_cp);

  if(n > 0 && len == l)
    return(p);

  se = getservbyname(servicename, "tcp");
  if(!se)
    return(-1);

  return(ntohs(se->s_port));
}

int
open_tcpip_conn(UChar * hostname, UChar * servname, Int32 fallback_port)
{
  struct sockaddr_in	addr;
  struct hostent	*hp;
  struct linger	linger;
  int		sockfd, fl;
  Int32		i, portnum;

  if(!servname && fallback_port < 0){
    errno = EINVAL;
    return(-1);
  }

  hp = gethostbyname(hostname);
  if(!hp)
    return(-2);

  if(!servname){
    portnum = fallback_port;
  }
  else{
    portnum = get_tcp_portnum(servname);
    if(portnum < 0)
	return(-3);
  }

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if(sockfd < 0)
    return(-4);

  addr.sin_family = AF_INET;
  addr.sin_port = htons(0);
  addr.sin_addr.s_addr = htonl(0L);

  i = bind(sockfd, (struct sockaddr * ) (& addr), sizeof(addr));
  if(i){
    close(sockfd);
    return(-5);
  }

  memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
  addr.sin_port = htons(portnum);

  if(connect(sockfd, (struct sockaddr * ) (& addr), sizeof(addr)) < 0){
    close(sockfd);
    return(-6);
  }

  linger.l_onoff = 1;
  linger.l_linger = 60;
  i = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &linger,
						sizeof (linger));

  fl = fcntl(sockfd, F_GETFL);
  fcntl(sockfd, F_SETFL, fl & (~ O_NONBLOCK) & (~ O_NDELAY));

  return(sockfd);
}

Int32
set_ip_throughput(int sockfd)
{
    int			sock_optval;
    Int32		r, i;

    r = 0;

#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
    sock_optval = IPTOS_THROUGHPUT;
    if( (i = setsockopt(sockfd, IPPROTO_IP, IP_TOS,
				(char *) &sock_optval, sizeof(int))) < 0)
	r -= 1;
#endif

    return(r);
}

Int32
set_tcp_nodelay(int sockfd, Flag turn_on)
{
    int			sock_optval, sockfd_socktype;
    Int32		sock_optlen[2], r, i;

    r = 0;

	/* I use 32 Bit int for sock_optlen. See server.c for a *comment* */
    *((int *) &(sock_optlen[0])) = sizeof(int);
    i = getsockopt(sockfd, SOL_SOCKET, SO_TYPE,
			(char *) &sockfd_socktype, (int *) &(sock_optlen[0]));
    if(i){
	r -= 1;
	sockfd_socktype = -1;
    }

#ifdef	TCP_NODELAY
    sock_optval = (turn_on ? 1 : 0);
    if(sockfd_socktype == SOCK_STREAM){
      if( (i = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
				(char *) &sock_optval, sizeof(int))) < 0)
	r -= 2;
    }
#endif

    return(r);
}

Int32
set_socket_keepalive(int sockfd)
{
    int			sock_optval;
    Int32		r, i;

    r = 0;

#ifdef	SO_KEEPALIVE
    sock_optval = 1;
    if( (i = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
				(char *) &sock_optval, sizeof(int))) < 0)
	r -= 1;
#endif

    return(r);
}

UChar *
get_hostnamestr(struct sockaddr_in * peeraddr)
{
  struct hostent	*hp;
  UChar		*hostname;

  if(!peeraddr)
    return(get_my_off_hn());

  hp = gethostbyaddr((char *) & peeraddr->sin_addr,
					sizeof(struct in_addr), AF_INET);
  if(hp)
    hostname = strdup(hp->h_name);
  else

#ifdef	HAVE_INET_NTOA

    hostname = strdup(inet_ntoa(peeraddr->sin_addr));

#else
	{
	  char		naddr[30];	/* manually: assuming IP-V4-address */
	  unsigned long	laddr;

	  laddr = ntohl(peeraddr->sin_addr.s_addr);

	  sprintf(naddr, "%u.%u.%u.%u", laddr >> 24, (laddr >> 16) & 0xff,
				(laddr >> 8) & 0xff, laddr & 0xff);
	  hostname = strdup(naddr);
	}	  
#endif

  return(hostname);
}

UChar *
get_connected_peername(int sock)
{
  int		i;
  struct sockaddr_in	peeraddr;
  Int32		len[2];

  len[0] = len[1] = 0;
  *((int *) &(len[0])) = sizeof(peeraddr);
  i = getpeername(sock, (struct sockaddr *) &peeraddr, (int *) &(len[0]));
  if(i){
    return(NULL);
  }

  return(get_hostnamestr(&peeraddr));
}

/* *comment* on getsockopt argument 5:
 * The IBM had the *great* idea to change the type of argument 5 of
 * getsockopt (and BTW argument 3 of getpeername) to size_t *. On the
 * Digital Unix size_t is an 8 Byte unsigned integer. Therefore the
 * safety belt 
 *  int sock_optlen[2].
 * I tried to find a workaround using autoconfig. Parsing the Headers
 * using cpp and perl or awk does not lead to satisfying results. Using
 * the TRY_COMPILE macro of autoconfig fails sometimes. E.G. the DEC
 * C-compiler does not complain on wrong re-declarations (neither via
 * error-message nor by the exit status). Try yourself, if you don't
 * believe. Furthermore there are 4 ways of declaration i found on
 * several systems. 4th argument as char * or void *, 5th arg as
 * explained of type int * or size_t *. Testing all these is annoying.
 * Maybe some other vendors have other ideas, how getsockopt should
 * be declared (sigh). Therefore i use two 32-Bit integers. Even if
 * a system call thinks, it must put there a 64-Bit int, the program
 * does not fail.
 */
