/*
** id_open.c                 Establish/initiate a connection to an IDENT server
**
** Author: Peter Eriksson <pen@lysator.liu.se>
** Fixes: Pär Emanuelsson <pell@lysator.liu.se>
** IPV6 mods: Philip Hazel <ph10@cus.cam.ac.uk>
** QNX mod:  Sven Behnsen <sven@becom.de> (inserted by PH)
*/

#ifdef NeXT3
#  include <libc.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

#ifdef HAVE_ANSIHEADERS
#  include <stdlib.h>
#  include <string.h>
#  include <unistd.h>
#  if !defined(__sgi) && !defined(VMS)
#    define bzero(p,l)     memset(p, 0, l)
#  endif
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/file.h>

#define IN_LIBIDENT_SRC
#include "ident.h"

#include <arpa/inet.h>

#ifdef _AIX
#  include <sys/select.h>
#endif


ident_t *id_open __P3(struct sockaddr *, lsock,
		      struct sockaddr *, fsock,
		      struct timeval *, timeout)
{
    ident_t *id;
    int res, tmperrno;
    int af_type = lsock->sa_family;

#if HAVE_IPV6
    struct sockaddr_in6 sin_laddr, sin_faddr;
#else
    struct sockaddr_in sin_laddr, sin_faddr;
#endif

    fd_set rs, ws, es;

#ifndef OLD_SETSOCKOPT
    int on = 1;
    struct linger linger;
#endif

    if ((id = (ident_t *) malloc(sizeof(*id))) == 0)
	return 0;

    if ((id->fd = socket(af_type, SOCK_STREAM, 0)) < 0)
    {
	free(id);
	return 0;
    }

    if (timeout)
    {
	if ((res = fcntl(id->fd, F_GETFL, 0)) < 0)
	    goto ERROR;

#ifndef VMS
#ifndef __QNX__
	if (fcntl(id->fd, F_SETFL, res | FNDELAY) < 0)
	    goto ERROR;
#endif
#endif
    }

    /* We silently ignore errors if we can't change LINGER */
#ifdef OLD_SETSOCKOPT
    /* Old style setsockopt() */
    (void) setsockopt(id->fd, SOL_SOCKET, SO_DONTLINGER);
    (void) setsockopt(id->fd, SOL_SOCKET, SO_REUSEADDR);
#else
    /* New style setsockopt() */
    linger.l_onoff = 0;
    linger.l_linger = 0;

    (void) setsockopt(id->fd, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger));
    (void) setsockopt(id->fd, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on));
#endif

    id->buf[0] = '\0';

    bzero((char *)&sin_laddr, sizeof(sin_laddr));

#if HAVE_IPV6
    sin_laddr.sin6_family = af_type;
    sin_laddr.sin6_port = 0;
    if (af_type == AF_INET6)
      sin_laddr.sin6_addr = ((struct sockaddr_in6 *)lsock)->sin6_addr;
    else   /* Fudge for IPv4 call */
      sin_laddr.sin6_flowinfo = ((struct sockaddr_in6 *)lsock)->sin6_flowinfo;

#else
    sin_laddr.sin_family = AF_INET;
    sin_laddr.sin_port = 0;
    sin_laddr.sin_addr = ((struct sockaddr_in *)lsock)->sin_addr;
#endif

    if (bind(id->fd, (struct sockaddr *) &sin_laddr, sizeof(sin_laddr)) < 0)
    {
#ifdef DEBUG
	perror("libident: bind");
#endif
	goto ERROR;
    }

    bzero((char *)&sin_faddr, sizeof(sin_faddr));

#ifdef HAVE_IPV6
    sin_faddr.sin6_family = af_type;
    sin_faddr.sin6_port = htons(IDPORT);
    if (af_type == AF_INET6)
      sin_faddr.sin6_addr = ((struct sockaddr_in6 *)fsock)->sin6_addr;
    else     /* Fudge for IPv4 call */
      sin_faddr.sin6_flowinfo = ((struct sockaddr_in6 *)fsock)->sin6_flowinfo;

#else
    sin_faddr.sin_family = AF_INET;
    sin_faddr.sin_port = htons(IDPORT);
    sin_faddr.sin_addr = ((struct sockaddr_in *)fsock)->sin_addr;
#endif

    errno = 0;
    res = connect(id->fd, (struct sockaddr *) &sin_faddr, sizeof(sin_faddr));
    if (res < 0 && errno != EINPROGRESS)
    {
#ifdef DEBUG
	perror("libident: connect");
#endif
	goto ERROR;
    }

    if (timeout)
    {
	FD_ZERO(&rs);
	FD_ZERO(&ws);
	FD_ZERO(&es);

	FD_SET(id->fd, &rs);
	FD_SET(id->fd, &ws);
	FD_SET(id->fd, &es);

/* Modern versions of HP-UX use the correct types for the arguments
of select(). Older versions do not. Luckily older versions also define
"hpux" whereas modern versions use only "__hpux" (to be more standard).
Changed this test from __hpux to hpux on 2-Feb-99 (PH). */

#if defined(hpux) || defined(__H3050) || defined(__H3050R)
	if ((res = select(FD_SETSIZE, (int *) &rs, (int *) &ws, (int *) &es, timeout)) < 0)
#else
	if ((res = select(FD_SETSIZE, &rs, &ws, &es, timeout)) < 0)
#endif
	{
#ifdef DEBUG
	    perror("libident: select");
#endif
	    goto ERROR;
	}

	if (res == 0)
	{
	    errno = ETIMEDOUT;
	    goto ERROR;
	}

	if (FD_ISSET(id->fd, &es))
	    goto ERROR;

	if (!FD_ISSET(id->fd, &rs) && !FD_ISSET(id->fd, &ws))
	    goto ERROR;
    }

    return id;

  ERROR:
    tmperrno = errno;		/* Save, so close() won't erase it */
    close(id->fd);
    free(id);
    errno = tmperrno;
    return 0;
}
