/* socket.c - socket functions */

/*			   NOTICE
 *
 * Copyright (c) 1990,1992,1993 Britt Yenne.  All rights reserved.
 * 
 * This software is provided AS-IS.  The author gives no warranty,
 * real or assumed, and takes no responsibility whatsoever for any 
 * use or misuse of this software, or any damage created by its use
 * or misuse.
 * 
 * This software may be freely copied and distributed provided that
 * no part of this NOTICE is deleted or edited in any manner.
 * 
 */

/* Mail comments or questions to ytalk@austin.eds.com */

#include "header.h"
#include "menu.h"
#include "socket.h"

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#ifdef _AIX
# include <sys/select.h>
#endif

struct _talkd talkd[MAXDAEMON+1];
int daemons = 0;

static int otalk, ntalk;		/* daemon numbers */
static CTL_MSG omsg;			/* old talk message */
static CTL_RESPONSE orsp;		/* old talk response */
static CTL_MSG42 nmsg;			/* new talk message */
static CTL_RESPONSE42 nrsp;		/* new talk response */

static int autofd = -1;			/* auto invite socket fd */
static struct sockaddr_in autosock;	/* auto invite socket */
static ylong autoid[MAXDAEMON+1];	/* auto invite seq numbers */
static ylong announce_id = 0;		/* announce sequence id */
static readdr *readdr_list = NULL;	/* list of re-addresses */

#define IN_ADDR(s)	((s).sin_addr.s_addr)
#define IN_PORT(s)	((s).sin_port)
#define SOCK_EQUAL(s,c)	(IN_PORT(s) == IN_PORT(c) && IN_ADDR(s) == IN_ADDR(c))

/* ---- local functions ---- */

/* Create a datagram socket.
 */
static int
init_dgram(sock)
  struct sockaddr_in *sock;
{
    int fd;
    size_t socklen;

    sock->sin_family = AF_INET;
    IN_ADDR(*sock) = INADDR_ANY;
    IN_PORT(*sock) = 0;
    if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
	show_error("init_dgram: socket() failed");
	bail(YTE_ERROR);
    }
    if(bind(fd, (struct sockaddr *)sock, sizeof(struct sockaddr_in)) != 0)
    {
	close(fd);
	show_error("init_dgram: bind() failed");
	bail(YTE_ERROR);
    }
    socklen = sizeof(struct sockaddr_in);
    if(getsockname(fd, (struct sockaddr *)sock, &socklen) < 0)
    {
	close(fd);
	show_error("init_dgram: getsockname() failed");
	bail(YTE_ERROR);
    }
    IN_ADDR(*sock) = me->host_addr;
    return fd;
}

/* Initialize a new daemon structure.
 */
static int
init_daemon(name, port, mptr, mlen, rptr, rlen)
  char *name;
  short port;
  yaddr mptr, rptr;
  int mlen, rlen;
{
    struct servent *serv;
    int d;

    if(daemons >= MAXDAEMON)
    {
	show_error("init_daemon: too many daemons");
	bail(YTE_ERROR);
    }
    d = ++daemons;	/* daemon number zero is not defined */

    if((serv = getservbyname(name, "udp")) != NULL)
	talkd[d].port = serv->s_port;
    else
	talkd[d].port = port;
    
    talkd[d].fd = init_dgram(&(talkd[d].sock));
    talkd[d].mptr = mptr;
    talkd[d].mlen = mlen;
    talkd[d].rptr = rptr;
    talkd[d].rlen = rlen;
    return d;
}

static void
read_autoport(fd)
  int fd;
{
    size_t socklen;
    static v2_pack pack;
    static char estr[V2_NAMELEN + V2_HOSTLEN + 20];
    static struct sockaddr_in temp;

    /* accept the connection */

    socklen = sizeof(struct sockaddr_in);
    if((fd = accept(autofd, (struct sockaddr *) &temp, &socklen)) == -1)
    {
	show_error("read_autoport: accept() failed");
	return;
    }

    /* The autoport socket just uses the old Ytalk version 2.?
     * packet.
     */
    errno = 0;
    if(full_read(fd, &pack, V2_PACKLEN) < 0 || pack.code != V2_AUTO)
    {
	show_error("read_autoport: unknown auto-invite connection");
	close(fd);
	return;
    }
    close(fd);
    if(!(def_flags & FL_INVITE))
    {
	sprintf(estr, "Talk to %s@%s?", pack.name, pack.host);
	if(yes_no(estr) == 'n')
	    return;
    }
    sprintf(estr, "%s@%s", pack.name, pack.host);
    invite(estr, 1);	/* we should be expected */
}

/* Create and initialize the auto-invitation socket.
 */
static void
init_autoport()
{
    size_t socklen;

    autosock.sin_family = AF_INET;
    IN_ADDR(autosock) = INADDR_ANY;
    IN_PORT(autosock) = 0;
    if((autofd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
	show_error("init_autoport: socket() failed");
	return;
    }
    if(bind(autofd, (struct sockaddr *)&autosock,
	sizeof(struct sockaddr_in)) < 0)
    {
	close(autofd);
	autofd = -1;
	show_error("init_autoport: bind() failed");
	return;
    }
    socklen = sizeof(struct sockaddr_in);
    if(getsockname(autofd, (struct sockaddr *)&autosock, &socklen) < 0)
    {
	close(autofd);
	autofd = -1;
	show_error("init_autoport: getsockname() failed");
	return;
    }
    IN_ADDR(autosock) = me->host_addr;
    if(listen(autofd, 5) < 0)
    {
	close(autofd);
	autofd = -1;
	show_error("init_autoport: listen() failed");
	return;
    }
    (void)memset(autoid, 0, (MAXDAEMON + 1) * sizeof(ylong));
    add_fd(autofd, read_autoport);
}

/* Fill the socket address field with the appropriate return address for
 * the host I'm sending to.
 */
static void
place_my_address(sock, addr)
  BSD42_SOCK *sock;
  register ylong addr;
{
    register readdr *r;

    for(r = readdr_list; r != NULL; r = r->next)

	if( ((r->from_addr & r->from_mask) == (me->host_addr & r->from_mask))
		&& ((addr & r->mask) == r->addr) )
	{
	    addr = (r->id_addr & r->id_mask) |
		   (me->host_addr & (~(r->id_mask)));
	    IN_ADDR(*sock) = addr;
	    break;
	}
    if(r == NULL)
	IN_ADDR(*sock) = me->host_addr;
    sock->sin_family = htons(AF_INET);
}

/* sendit() sends the completed message to the talk daemon at the given
 * hostname, then reads a response packet.
 */
static int
sendit(addr, d)
  ylong addr;	/* host internet address */
  int d;	/* daemon number */
{
    int n;
    struct sockaddr_in daemon;
    struct timeval tv;
    char *rtype, *mtype;
    fd_set sel;

    /* set up the appropriate message structure */

    if(d == ntalk)
    {
	nmsg.vers = TALK_VERSION;
	place_my_address(&(nmsg.ctl_addr), addr);
	mtype = &(nmsg.type);
	rtype = &(nrsp.type);
    }
    else if(d == otalk)
    {
	omsg.type = nmsg.type;
	omsg.addr = nmsg.addr;
	omsg.id_num = nmsg.id_num;
	omsg.pid = nmsg.pid;
	strncpy(omsg.l_name, nmsg.l_name, NAME_SIZE);
	strncpy(omsg.r_name, nmsg.r_name, NAME_SIZE);
	strncpy(omsg.r_tty, nmsg.r_tty, TTY_SIZE);
	place_my_address(&(omsg.ctl_addr), addr);
	mtype = &(omsg.type);
	rtype = &(orsp.type);
    }
    else
    {
	sprintf(errstr, "Unkown daemon type: %d", d);
	show_error(errstr);
	return -1;
    }

    /* set up a sockaddr_in for the daemon we're sending to */

    daemon.sin_family = AF_INET;
    IN_ADDR(daemon) = addr;
    IN_PORT(daemon) = talkd[d].port;

    /* flush any lingering input */

    FD_ZERO(&sel);
    for(;;)
    {
	tv.tv_sec = 0L;
	tv.tv_usec = 0L;
	FD_SET(talkd[d].fd, &sel);
	if((n = select(talkd[d].fd + 1, &sel, 0, 0, &tv)) < 0)
	{
	    show_error("sendit: flush select() failed");
	    return -1;
	}
	if(n <= 0)
	    break;
	if(recv(talkd[d].fd, talkd[d].rptr, talkd[d].rlen, 0) < 0)
	{
	    show_error("sendit: flush recv() failed");
	    return -1;
	}
    }

    /* Now we need to send the actual packet.  Due to unreliability of
     * DGRAM sockets, we must resend the packet until we get a response
     * from the server.  Geez... two different daemons, both on unreliable
     * sockets, and maybe even different daemons on different machines.
     * Is *nothing* reliable anymore???
     */
    do
    {
    	do
	{
	    n = sendto(talkd[d].fd, (char *)talkd[d].mptr, talkd[d].mlen,
		0, (struct sockaddr *) &daemon, sizeof(daemon));
	    if(n != talkd[d].mlen)
	    {
		show_error("sendit: sendto() failed");
		return -1;
	    }

	    tv.tv_sec = 5L;
	    tv.tv_usec = 0L;
	    FD_SET(talkd[d].fd, &sel);
	    if((n = select(talkd[d].fd + 1, &sel, 0, 0, &tv)) < 0)
	    {
		show_error("sendit: first select() failed");
		return -1;
	    }
 	} while (n <= 0);	/* ie: until we receive a reply */

    	do
	{
	    n = recv(talkd[d].fd, talkd[d].rptr, talkd[d].rlen, 0);
	    if(n < 0)
	    {
		show_error("sendit: recv() failed");
		return -1;
	    }

	    if(*rtype != *mtype)
		tv.tv_sec = 5L;
	    else
		tv.tv_sec = 0L;
	    tv.tv_usec = 0L;
	    FD_SET(talkd[d].fd, &sel);
	    if((n = select(talkd[d].fd + 1, &sel, 0, 0, &tv)) < 0)
	    {
		show_error("sendit: second select() failed");
		return -1;
	    }
    	} while(n > 0 && *rtype != *mtype);
    } while(*rtype != *mtype);

    /* WHEW */

    /* Just because a person is a SYSADMIN doesn't necessarily mean he/she
     * knows everything about installing software.  In fact, many have been
     * known to install the talk daemon without setting the option required
     * to pad out the structures so that "long"s are on four-byte boundaries
     * on machines where "long"s can be on two-byte boundaries.  This "bug"
     * cost me about four hours of debugging to discover, so I'm not happy
     * right now.  Anyway, here's a quick hack to fix this problem.
     */
    if(d == otalk && nrsp.type == LOOK_UP && nrsp.answer == 0)
    {
	u_short t;
	(void)memcpy((char *)&t, ((char *)&orsp.addr.sin_family)-2, sizeof(t));
	if(ntohs(t) == AF_INET && ntohs(orsp.addr.sin_family) != AF_INET)
	{
	    char *c;
	    c = ((char *)&orsp) + sizeof(orsp) - 1;
	    for(; c >= (char *)&orsp.id_num; c--)
		*c = *(c-2);
	}
    }

    /* Fill in the new talk response structure if we just read an
     * old one.
     */
    if(d == otalk)
    {
	nrsp.type = orsp.type;
	nrsp.answer = orsp.answer;
	nrsp.id_num = orsp.id_num;
	nrsp.addr = orsp.addr;
    }

    return 0;
}

/* find_daemon() locates the talk daemon(s) on a machine and determines
 * what version(s) of the daemon are running.
 */
static int
find_daemon(addr)
  ylong addr;
{
    register hostinfo *h;
    register int n, i, d;
    CTL_MSG m1;
    CTL_MSG42 m2;
    struct sockaddr_in daemon;
    struct timeval tv;
    int out, max;
    fd_set sel;
    static hostinfo *host_head = NULL;

    /* If we've already used this host, look it up instead of blitting to
     * the daemons again...
     */
    for(h = host_head; h; h = h->next)
	if(h->host_addr == addr)
	    return h->dtype;

    daemon.sin_family = AF_INET;
    IN_ADDR(daemon) = addr;

    m1 = omsg;
    m2 = nmsg;
    /* m1.ctl_addr = talkd[otalk].sock; */
    memcpy(&m1.ctl_addr, &talkd[otalk].sock, sizeof(m1.ctl_addr));
    place_my_address(&(m1.ctl_addr), addr);
    /* m2.ctl_addr = talkd[ntalk].sock; */
    memcpy(&m2.ctl_addr, &talkd[ntalk].sock, sizeof(m2.ctl_addr));
    place_my_address(&(m2.ctl_addr), addr);
    m1.type = m2.type = LOOK_UP;
    m1.id_num = m2.id_num = htonl(0);
    m1.r_tty[0] = m2.r_tty[0] = '\0';
    strcpy(m1.r_name, "ytalk");
    strcpy(m2.r_name, "ytalk");
    m1.addr.sin_family = m2.addr.sin_family = htons(AF_INET);
    m1.ctl_addr.sin_family = m2.ctl_addr.sin_family = htons(AF_INET);

    out = 0;
    for(i = 0; i < 5; i++)
    {
	IN_PORT(daemon) = talkd[ntalk].port;
	n = sendto(talkd[ntalk].fd, (char *)&m2, sizeof(m2),
	    0, (struct sockaddr *) &daemon, sizeof(daemon));
	if(n != sizeof(m2))
	    show_error("Warning: cannot write to new talk daemon");

	IN_PORT(daemon) = talkd[otalk].port;
	n = sendto(talkd[otalk].fd, (char *)&m1, sizeof(m1),
	    0, (struct sockaddr *) &daemon, sizeof(daemon));
	if(n != sizeof(m1))
	    show_error("Warning: cannot write to old talk daemon");

	tv.tv_sec = 4L;
	tv.tv_usec = 0L;

	FD_ZERO(&sel);
	FD_SET(talkd[ntalk].fd, &sel);
	FD_SET(talkd[otalk].fd, &sel);
	max = talkd[ntalk].fd;
	if (max < talkd[otalk].fd)
	  max = talkd[otalk].fd;

	if((n = select(1+max, &sel, NULL, NULL, &tv)) < 0)
	{
	    show_error("find_daemon: first select() failed");
	    continue;
	}
	if(n == 0)
	    continue;

	do
	{
	    for(d = 1; d <= daemons; d++)
		if(FD_ISSET(talkd[d].fd, &sel))
		{
		    int r;

		    r = recv(talkd[d].fd, errstr, talkd[d].rlen, 0);
		    if (r < 0) {
		    	if (errno == EINTR || errno == ECONNREFUSED)
			    continue;
			else
			    show_error("find_daemon: recv() failed");
		    }
		    out |= (1 << d);
		}

	    tv.tv_sec = 0L;
	    tv.tv_usec = 500000L;	/* give the other daemon a chance */
	    FD_ZERO(&sel);
	    FD_SET(talkd[ntalk].fd, &sel);
	    FD_SET(talkd[otalk].fd, &sel);
	    if((n = select(1+max, &sel, NULL, NULL, &tv)) < 0)
		show_error("find_daemon: second select() failed");
	} while(n > 0);

	h = (hostinfo *)get_mem(sizeof(hostinfo));
	h->next = host_head;
	host_head = h;
	h->host_addr = addr;
	h->dtype = out;
	if (out)
		return out;
    }
    sprintf(errstr, "No talk daemon on %s", host_name(addr));
    show_error(errstr);
    return 0;
}

static ylong
make_net_mask(addr)
  ylong addr;
{
    if(addr & (ylong)0xff)
	return (ylong)0xffffffff;
    if(addr & (ylong)0xffff)
	return (ylong)0xffffff00;
    if(addr & (ylong)0xffffff)
	return (ylong)0xffff0000;
    if(addr)
	return (ylong)0xff000000;
    return (ylong)0;
}

/* ---- global functions ---- */

/* Initialize sockets and message parameters.
 */
void
init_socket()
{
    /* init daemons in order of preference */

    ntalk = init_daemon("ntalk", 518, &nmsg, sizeof(nmsg),
	&nrsp, sizeof(nrsp));
    otalk = init_daemon("talk", 517, &omsg, sizeof(omsg),
	&orsp, sizeof(orsp));

    strncpy(nmsg.l_name, me->user_name, NAME_SIZE);

    /* omsg.ctl_addr = talkd[otalk].sock; */
    /* nmsg.ctl_addr = talkd[ntalk].sock; */
    memcpy(&omsg.ctl_addr, &talkd[otalk].sock, sizeof(omsg.ctl_addr));
    memcpy(&nmsg.ctl_addr, &talkd[ntalk].sock, sizeof(nmsg.ctl_addr));
    /* just in case */
    omsg.ctl_addr.sin_family = nmsg.ctl_addr.sin_family = htons(AF_INET);
    nmsg.vers = TALK_VERSION;

    (void)find_daemon(me->host_addr);
    if (!(def_flags & FL_NOAUTO))
	init_autoport();
}

/* Close every open descriptor.  This should only be used for a quick
 * exit... it does not gracefully shut systems down.
 */
void
close_all()
{
    register yuser *u;
    register int d;

    for(u = user_list; u; u = u->unext)
    {
	if(u->fd > 0)
	    close(u->fd);
	if(u->output_fd > 0)
	    close(u->output_fd);
    }
    if(autofd > 0)
	close(autofd);
    for(d = 1; d <= daemons; d++)
	close(talkd[d].fd);
}

/* The following routines send a request across the DGRAM socket to the
 * talk daemons.
 */

/* First, a quick and easy interface for the user sockets.
 */
int
send_dgram(user, type)
  yuser *user;
  u_char type;
{
    ylong addr;
    int d;

    /* set up the message type and where to send it */

    switch(type)
    {
	case LEAVE_INVITE:	/* leave an invite on my machine */
	    addr = me->host_addr;
	    nmsg.type = LEAVE_INVITE;
	    nmsg.id_num = htonl(user->l_id);
	    break;
	case DELETE_INVITE:	/* delete my invite on my machine */
	    addr = me->host_addr;
	    nmsg.type = DELETE;
	    nmsg.id_num = htonl(user->l_id);
	    break;
	case ANNOUNCE:		/* ring a user */
	    addr = user->host_addr;
	    nmsg.type = ANNOUNCE;
	    announce_id += 5;	/* no guesswork here */
	    nmsg.id_num = htonl(announce_id);
	    break;
	case LOOK_UP:		/* look up remote invitation */
	    addr = user->host_addr;
	    nmsg.type = LOOK_UP;
	    nmsg.id_num = htonl(user->r_id);
	    break;
	case DELETE:		/* delete erroneous remote invitation */
	    addr = user->host_addr;
	    nmsg.type = DELETE;
	    nmsg.id_num = htonl(user->r_id);
	    break;
	case AUTO_LOOK_UP:	/* look up remote auto-invitation */
	    addr = user->host_addr;
	    nmsg.type = LOOK_UP;
	    nmsg.id_num = htonl(user->r_id);
	    break;
	case AUTO_DELETE:	/* delete erroneous remote auto-invitation */
	    addr = user->host_addr;
	    nmsg.type = DELETE;
	    nmsg.id_num = htonl(user->r_id);
	    break;
	default:
	    errno = 0;
	    show_error("send_dgram: unknown type");
	    return -1;
    }

    /* find a common daemon, if possible */

    if(user->daemon != 0)
	d = user->daemon;
    else
    {
	int dtype, d1, d2;

	/* Find the daemon(s) their host supports.  If our two machines
	 * support a daemon in common, use that one.  Else, normal UNIX
	 * "talk" is already screwed to the wall, but YTalk will at least
	 * work.
	 */
	d1 = find_daemon(user->host_addr);
	d2 = find_daemon(me->host_addr);
	dtype = d1 & d2;

	if(d1 == 0 || d2 == 0)
	    return -1;
	if(dtype == 0)
	{
	    dtype = find_daemon(addr);
	    for(d = 1; d <= daemons; d++)
		if(dtype & (1<<d))
		    break;
	    if(d > daemons)
		return -1;
	}
	else
	{
	    for(d = 1; d <= daemons; d++)
		if(dtype & (1<<d))
		{
		    user->daemon = d;
		    break;
		}
	    if(d > daemons)
		return -1;
	}
    }

    /* Each user has his own unique daemon id.  Why?  Tsch.  Why.
     * Well, the talk daemons consider two users equivalent if their
     * usernames and machine names match.  Hence, the daemons will not
     * allow ytalk to talk with two different users with the same name
     * on some machine.  By assigning unique process id's, we trick
     * the daemons into thinking we're several different users trying
     * to talk to the same person.  Sick?  Don't blame me.
     */
    nmsg.pid = htonl(user->d_id);
    if(type == AUTO_LOOK_UP || type == AUTO_DELETE)
    {
	strcpy(nmsg.l_name, "+AUTO");	/* put on my mask... */
	strncpy(nmsg.r_name, user->user_name, NAME_SIZE);
	nmsg.r_tty[0] = '\0';
    }
    else
    {
	strncpy(nmsg.r_name, user->user_name, NAME_SIZE);
	strncpy(nmsg.r_tty, user->tty_name, TTY_SIZE);
    }
    /* nmsg.addr = user->sock; */
    memcpy(&nmsg.addr, &user->sock, sizeof(nmsg.addr));
    nmsg.addr.sin_family = htons(AF_INET);
    if(sendit(addr, d) != 0)
    {
	if(type == AUTO_LOOK_UP || type == AUTO_DELETE)
	    strncpy(nmsg.l_name, me->user_name, NAME_SIZE);
	return -2;
    }

    switch(type)
    {
	case LEAVE_INVITE:
	    user->l_id = ntohl(nrsp.id_num);
	    break;
	case LOOK_UP:
	    user->r_id = ntohl(nrsp.id_num);
	    break;
	case AUTO_LOOK_UP:
	    strncpy(nmsg.l_name, me->user_name, NAME_SIZE);
	    user->r_id = ntohl(nrsp.id_num);
	    break;
	case AUTO_DELETE:
	    strncpy(nmsg.l_name, me->user_name, NAME_SIZE);
	    break;
    }
    return nrsp.answer;
}

/* Next, an interface for the auto-invite socket.  The auto-invite socket
 * always sends to the caller's host, and always does just an invite.
 */
int
send_auto(type)
  u_char type;
{
    int dtype, d, rc;

    if(autofd < 0)
	return 0;
    nmsg.type = type;
    strcpy(nmsg.r_name, "+AUTO");
    nmsg.r_tty[0] = '\0';
    /* nmsg.addr = autosock; */
    memcpy(&nmsg.addr, &autosock, sizeof(nmsg.addr));
    nmsg.addr.sin_family = htons(AF_INET);

    rc = 0;
    dtype = find_daemon(me->host_addr);
    for(d = daemons; d >= 1; d--)
	if(dtype & (1<<d))
	{
	    nmsg.id_num = htonl(autoid[d]);
	    nmsg.pid = htonl(1);
	    if(sendit(me->host_addr, d) < 0)
		rc = -1;
	    else
		autoid[d] = ntohl(nrsp.id_num);
	}

    if(rc)
	return rc;
    if(type == LEAVE_INVITE)
	return 0;
    return nrsp.answer;
}

/* Shut down the auto-invitation system.
 */
void
kill_auto()
{
    if(autofd < 0)
	return;
    (void)send_auto(DELETE);
    remove_fd(autofd);
    close(autofd);
    autofd = -1;
}

/* Create a TCP socket for communication with other talk users.
 */
int
newsock(user)
  yuser *user;
{
    int fd;
    size_t socklen;

    user->sock.sin_family = AF_INET;
    IN_ADDR(user->sock) = INADDR_ANY;
    IN_PORT(user->sock) = 0;
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
	show_error("newsock: socket() failed");
	return -1;
    }
    if(bind(fd, (struct sockaddr *)&user->sock, sizeof(struct sockaddr_in)) < 0)
    {
	close(fd);
	show_error("newsock: bind() failed");
	return -1;
    }
    socklen = sizeof(struct sockaddr_in);
    if(getsockname(fd, (struct sockaddr *)&user->sock, &socklen) < 0)
    {
	close(fd);
	show_error("newsock: getsockname() failed");
	return -1;
    }
    place_my_address(&(user->sock), user->host_addr);
    if(listen(fd, 5) < 0)
    {
	close(fd);
	show_error("newsock: listen() failed");
	return -1;
    }
    user->fd = fd;
    fd_to_user[user->fd] = user;
    user->orig_sock = user->sock;
    return 0;
}

/* Connect to another user's communication socket.
 */
int
connect_to(user)
  yuser *user;
{
    register yuser *u;
    int fd;
    size_t socklen;
    struct sockaddr_in sock, orig_sock;

    orig_sock = *(struct sockaddr_in *)&nrsp.addr;
    orig_sock.sin_family = AF_INET;

    /* it could be one of mine... */
    for(u = user_list; u; u = u->unext)
	if(SOCK_EQUAL(orig_sock, u->orig_sock))
	    return -3;
    if(SOCK_EQUAL(orig_sock, autosock))
	return -3;

    sock = orig_sock;
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
	show_error("connect_to: socket() failed");
	return -1;
    }
    if(connect(fd, (struct sockaddr *)&sock, sizeof(struct sockaddr_in)) < 0)
    {
	close(fd);
	if(errno == ECONNREFUSED)
	{
	    errno = 0;
	    return -2;
	}
	show_error("connect_to: connect() failed");
	return -1;
    }
    socklen = sizeof(struct sockaddr_in);
    if(getsockname(fd, (struct sockaddr *)&sock, &socklen) < 0)
    {
	close(fd);
	show_error("connect_to: getsockname() failed");
	return -1;
    }
    if(user)
    {
	user->sock = sock;
	user->orig_sock = orig_sock;
	user->fd = fd;
	fd_to_user[user->fd] = user;
    }
    return fd;
}

/* Find a host's address.
 */
ylong
get_host_addr(hostname)
  char *hostname;
{
    struct hostent *host;
    ylong addr;
    ylong inet_addr();

    errno = 0;
    if((host = (struct hostent *) gethostbyname(hostname)) != NULL)
    {
	if(host->h_length != sizeof(addr))
	{
	    sprintf(errstr, "Bad IN addr: %s", hostname);
	    show_error(errstr);
	    return (ylong)-1;
	}
	(void)memcpy(&addr, host->h_addr, sizeof(addr));
    }
    else if((addr = (ylong)inet_addr(hostname)) == (ylong)-1)
	return (ylong)-1;
    return addr;
}

/* Find a host name by host address.
   [19NOV96 Roger]: try to find the fqdn (1st alias with a dot)
 */
char *
host_name(addr)
  ylong addr;
{
    struct hostent *host;
    char *inet_ntoa();
    char **s;

    if((host = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)) == NULL)
    {
	struct in_addr tmp;
	tmp.s_addr = addr;
	return inet_ntoa(tmp);
    }
    if (strchr(host->h_name, '.'))
      return (char *)host->h_name;
    s = host->h_aliases;
    if (s && *s)
      for (; *s; s++)
        if (strchr(*s, '.'))
          return *s;
    return (char *)host->h_name;
}

/* Re-address a given host ("from_id") to the given address or host
 * id ("to_id") when communicating with some other host id ("on_id").
 * This is useful especially over routers where "foo.com" is known
 * as the differently-addressed "bar.com" to host "xyzzy.com".
 */
void
readdress_host(from_id, to_id, on_id)
  char *from_id, *to_id, *on_id;
{
    register readdr *new;
    ylong from_addr, to_addr, on_addr;
    ylong from_mask, to_mask, on_mask;

    if((from_addr = get_host_addr(from_id)) == (ylong)-1)
    {
	sprintf(errstr, "Unknown host: '%s'", from_id);
	show_error(errstr);
	return;
    }
    if((to_addr = get_host_addr(to_id)) == (ylong)-1)
    {
	sprintf(errstr, "Unknown host: '%s'", to_id);
	show_error(errstr);
	return;
    }
    if((on_addr = get_host_addr(on_id)) == (ylong)-1)
    {
	sprintf(errstr, "Unknown host: '%s'", on_id);
	show_error(errstr);
	return;
    }
    from_mask = make_net_mask(from_addr);
    to_mask = make_net_mask(to_addr);
    on_mask = make_net_mask(on_addr);
    
#if 0
    if((from_addr & from_mask) != (me->host_addr & from_mask))
	return;
#endif
    if(from_addr == to_addr)
	return;

    new = (readdr *)get_mem(sizeof(readdr));
    new->addr = on_addr;
    new->mask = on_mask;
    new->from_addr = from_addr;
    new->from_mask = from_mask;
    new->id_addr = to_addr;
    new->id_mask = to_mask;
    new->next = readdr_list;
    readdr_list = new;
}

