/******************************************************************************
    (c) 2001-2005 Christine Caulfield                 christine.caulfield@googlemail.com
    (c) 2003 Dmitri Popov

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
******************************************************************************/
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#ifdef HAVE_NET_ETHERNET_H
#include <net/ethernet.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <syslog.h>
#include <ctype.h>
#include <regex.h>
#include <stdlib.h>
#include <utmp.h>
#include <grp.h>
#include <signal.h>
#include <assert.h>
#include <netinet/in.h>

#include <list>
#include <queue>
#include <map>
#include <string>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <iomanip>

#include "lat.h"
#include "utils.h"
#include "session.h"
#include "localport.h"
#include "connection.h"
#include "circuit.h"
#include "latcpcircuit.h"
#include "llogincircuit.h"
#include "server.h"
#include "services.h"
#include "lat_messages.h"
#include "dn_endian.h"

#if defined (__APPLE__) && !defined(_SOCKLEN_T)
typedef int socklen_t;
#endif

// Remove any dangling symlinks in the /dev/lat directory
void LATServer::tidy_dev_directory()
{
    DIR *dir;
    struct dirent *de;
    char current_dir[PATH_MAX];

    getcwd(current_dir, sizeof(current_dir));
    chdir(LAT_DIRECTORY);

    dir = opendir(LAT_DIRECTORY);
    if (!dir) return; // Doesn't exist - this is OK

    while ( (de = readdir(dir)) )
    {
	if (de->d_name[0] != '.')
	    unlink(de->d_name);
    }
    closedir(dir);
    chdir(current_dir);
}

unsigned char *LATServer::get_local_node(void)
{
    unsigned int i;

    if (local_name[0] == '\0')
    {
	struct utsname uts;
	char *trailer;

	uname(&uts);
	if (strchr(uts.nodename, '.'))
	    *strchr(uts.nodename, '.') = '\0';

        // This gets rid of the name-Computer bit on Darwin
	trailer = strstr(uts.nodename, "-Computer");
	if (trailer)
	    *trailer = '\0';

	// LAT node names must be <= 16 characters long
	if (strlen(uts.nodename) > 16)
	    uts.nodename[16] = '\0';

	strcpy((char *)local_name, uts.nodename);

	// Make it all upper case
	for (i=0; i<strlen((char *)local_name); i++)
	{
	    if (islower(local_name[i])) local_name[i] = toupper(local_name[i]);
	}
    }
    return local_name;
}

void LATServer::alarm_signal(int sig)
{
     if (Instance()->alarm_mode == 0)
     {
	 Instance()->send_service_announcement(sig);
     }
     Instance()->send_solicit_messages(sig);

}


// Send ENQUIRY for a node - mainly needed for DS90L servers that
// don't advertise.
void LATServer::send_enq(const char *node)
{
    unsigned char packet[1600];
    static unsigned char id = 1;
    LAT_Enquiry *enqmsg = (LAT_Enquiry *)packet;
    int ptr = sizeof(LAT_Enquiry);

    enqmsg->cmd = LAT_CCMD_ENQUIRE;
    enqmsg->dummy = 0;
    enqmsg->hiver = LAT_VERSION;
    enqmsg->lover = LAT_VERSION;
    enqmsg->latver = LAT_VERSION;
    enqmsg->latver_eco = LAT_VERSION_ECO;
    enqmsg->mtu = dn_htons(1500);
    enqmsg->id  = id++;
    enqmsg->retrans_timer = 75; // * 10ms - give it more time to respond

    add_string(packet, &ptr, (const unsigned char *)node);
    packet[ptr++] = 1; /* Length of group data */
    packet[ptr++] = 1; /* Group mask */

    add_string(packet, &ptr, local_name);
    packet[ptr++] = 0x00;
    packet[ptr++] = 0x00;
    packet[ptr++] = 0x00;
    packet[ptr++] = 0x00;

    /* This is the LAT multicast address */
    static unsigned char addr[6] = { 0x09, 0x00, 0x2b, 0x00, 0x00, 0x0f };

debuglog(("send_enq : node: %s, local_name: %s\n", node, local_name));

    for (int i=0; i<num_interfaces;i++)
    {
	if (iface->send_packet(interface_num[i], addr, packet, ptr) < 0)
	{
debuglog(("Error send packet\n"));
	    interface_error(interface_num[i], errno);
	}
	else
	{
	    interface_errs[interface_num[i]] = 0; // Clear errors
	}
    }

    LATServices::Instance()->touch_dummy_node_respond_counter(node);
}

void LATServer::add_slave_node(const char *node_name)
{
    sig_blk_t _block(SIGALRM);
    for (std::list<std::string>::iterator iter = slave_nodes.begin();
         iter != slave_nodes.end();
         iter++)
    {
        if (*iter == node_name) {
           // do not duplicate nodes
           return;
        }
    }
    slave_nodes.push_front(node_name);
}

/* Called on the multicast timer - advertise our service on the LAN */
void LATServer::send_service_announcement(int sig)
{
    unsigned char packet[1600];
    int ptr;
    struct utsname uinfo;
    char  *myname;

    // Only send it if we have some services
    if (servicelist.size())
    {
	LAT_ServiceAnnounce *announce = (LAT_ServiceAnnounce *)packet;
	ptr = sizeof(LAT_ServiceAnnounce);

	announce->cmd             = LAT_CCMD_SERVICE;
	announce->circuit_timer   = circuit_timer;
	announce->hiver           = LAT_VERSION;
	announce->lover           = LAT_VERSION;
	announce->latver          = LAT_VERSION;
	announce->latver_eco      = LAT_VERSION_ECO;
	announce->incarnation     = --multicast_incarnation;
	announce->flags           = 0x1f;
	announce->mtu             = dn_htons(1500);
	announce->multicast_timer = multicast_timer;
	if (do_shutdown)
	{
	    announce->node_status     = 3;    // Not accepting connections
	}
	else
	{
	    announce->node_status     = 2;    // Accepting connections
	}

	// Send group codes
	if (groups_set)
	{
	    announce->group_length = 32;
	    memcpy(&packet[ptr], groups, 32);
	    ptr += 32;
	    announce->flags |= 1;
	}
	else
	{
	    announce->group_length    = 1;
	    packet[ptr++] = 01;
	}

	/* Get host info */
	uname(&uinfo);

	// Node name
	myname = (char*)get_local_node();
	packet[ptr++] = strlen(myname);
	strcpy((char*)packet+ptr, myname);
	ptr += strlen(myname);

	// Greeting
	packet[ptr++] = strlen((char*)greeting);
	strcpy((char*)packet+ptr, (char*)greeting);
	ptr += strlen((char*)greeting);

	// Number of services
	packet[ptr++] = servicelist.size();
	std::list<serviceinfo>::iterator i(servicelist.begin());
	for (; i != servicelist.end(); i++)
	{
	    // Service rating
	    unsigned char real_rating;
	    if (i->get_static())
	    {
		real_rating = i->get_rating();
	    }
	    else
	    {
		/* Calculate dynamic rating */
		real_rating =  (unsigned char)(i->get_rating() / (get_loadavg()+1.0));
		debuglog(("Dynamic service rating is %d\n", real_rating));
	    }
	    packet[ptr++] = real_rating;

	    // Service name
	    const std::string name = i->get_name();
	    packet[ptr++]     = name.length();
	    strcpy((char *)packet+ptr, i->get_name().c_str());
	    ptr += name.length();

	    // Service Identification
	    std::string id = i->get_id();
	    if (id.length() == 0)
	    {
		// Default service identification string
		char stringbuf[1024];
		sprintf(stringbuf, "%s %s", uinfo.sysname, uinfo.release);
		id = std::string(stringbuf);
	    }

	    packet[ptr++] = id.length();
	    strcpy((char *)packet+ptr, id.c_str());
	    ptr += id.length();

	    // Make sure the service table knows about all our services
	    unsigned char dummy_macaddr[6];
	    memset(dummy_macaddr, 0, sizeof(dummy_macaddr));
	    if (!do_shutdown)
		LATServices::Instance()->add_service(std::string((char*)get_local_node()),
						     name,
						     id, real_rating, 0, dummy_macaddr);
	}

	// Not sure what node service classes are
	// probably somthing to do with port services and stuff.
	packet[ptr++] = 0x01; // Node service classes length
	packet[ptr++] = 0x01; // Node service classes
	packet[ptr++] = 0x00;
	packet[ptr++] = 0x00;

	unsigned char addr[6];
	/* This is the LAT multicast address */
	addr[0]  = 0x09;
	addr[1]  = 0x00;
	addr[2]  = 0x2b;
	addr[3]  = 0x00;
	addr[4]  = 0x00;
	addr[5]  = 0x0f;

	for (int i=0; i<num_interfaces;i++)
	{
	    if (iface->send_packet(interface_num[i], addr, packet, ptr) < 0)
	    {
		debuglog(("sending service announcement, send error: %d\n", errno));
		interface_error(interface_num[i], errno);
	    }
	    else
		interface_errs[interface_num[i]] = 0; // Clear errors
	}
    }
    /* Send it every minute */
    signal(SIGALRM, &alarm_signal);
    alarm(multicast_timer);
}

// Send solicit messages to slave nodes
void LATServer::send_solicit_messages(int sig)
{
    static unsigned int counter = 0;
    static unsigned int last_list_size = 0;

    if (alarm_mode == 0)
    {
	counter = 0;
	alarm_mode = 1;
	debuglog(("set alarm_mode to 1\n"));

	if (!known_slave_nodes.empty())
	{
	    std::string known_node = known_slave_nodes.front();
	    known_slave_nodes.pop_front();
	    slave_nodes.push_back(known_node);
	    debuglog(("known(%d) => slave(%d) : %s\n", known_slave_nodes.size(),
		       slave_nodes.size(), known_node.c_str()));
	    if (slave_nodes.size() == 1)
	    {
		alarm_mode = 0;
		debuglog(("set alarm_mode to 0 - one slave\n"));
		send_enq(slave_nodes.front().c_str());
		// alarm() is already charged by send_service_announcement()
		return;
	    }
	}
    }
    else
    {
	if (slave_nodes.size() < last_list_size)
	{
	    counter -= last_list_size - slave_nodes.size();
	}
    }

    if (slave_nodes.size() > counter && !slave_nodes.empty())
    {
	std::string node_name = slave_nodes.front();
	slave_nodes.pop_front();
	slave_nodes.push_back(node_name);

	send_enq(node_name.c_str());

	alarm(1);
	counter++;
	last_list_size = slave_nodes.size();
    }
    else
    {
	alarm_mode = 0;

	//well, it's not quite correct, I know...
	alarm(counter >= multicast_timer ? 1 : multicast_timer - counter);
	debuglog(("set alarm_mode to 0, timer %d\n", counter >= multicast_timer ? 1 : multicast_timer - counter));
    }
}

// Log an error against an interface. If we get three of these
// then we remove it.
// If that means we have no interfaces, then closedown.
void LATServer::interface_error(int ifnum, int err)
{
    syslog(LOG_ERR, "Error on interface %s: %s\n", iface->ifname(ifnum).c_str(), strerror(err));

    // Too many errors, remove it
    if (++interface_errs[ifnum] > 3)
    {
	int i,j;

	syslog(LOG_ERR, "Interface will be removed from LATD\n");

	// Look for it
	for (i=0; i<num_interfaces; i++)
	{
	    if (interface_num[i] == ifnum) goto remove_if;
	}

	// Ugh, didn't find it. that can't be right!
	if (i>=num_interfaces)
	{
	    syslog(LOG_ERR, "Don't seem to have a reference to this interface....\n");
	    return;
	}

    remove_if:
	// Shuffle the list down
	for (j=i; j<num_interfaces-1; j++)
	{
	    interface_num[j] = interface_num[j+1];
	}

	if (--num_interfaces == 0)
	{
	    syslog(LOG_ERR, "No valid interfaces left. LATD closing down\n");

	    // No point in going down cleanly as that just sends network messages
	    // and we have nowhere to send then through.
	    exit(9);
	}
    }
}

/* Main loop */
void LATServer::run()
{
    int status;

    // Bind interfaces
    for (int i=0; i<num_interfaces;i++)
    {
	iface->set_lat_multicast(interface_num[i]);
    }

    // Add it/them to the sockets list
    if (iface->one_fd_per_interface())
    {
	for (int i=0; i<num_interfaces;i++)
	{
	    fdlist.push_back(fdinfo(iface->get_fd(interface_num[i]), 0, LAT_SOCKET));
	}
    }
    else
    {
	fdlist.push_back(fdinfo(iface->get_fd(0), 0, LAT_SOCKET));
    }

    // Open LATCP socket
    unlink(LATCP_SOCKNAME);
    latcp_socket = socket(PF_UNIX, SOCK_STREAM, 0);
    if (latcp_socket < 0)
    {
	syslog(LOG_ERR, "Can't create latcp socket: %m");
	exit(1);
    }

    struct sockaddr_un sockaddr;
    strcpy(sockaddr.sun_path, LATCP_SOCKNAME);
    sockaddr.sun_family = AF_UNIX;
    if (bind(latcp_socket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)))
    {
	syslog(LOG_ERR, "can't bind latcp socket: %m");
        exit(1);
    }
    if (listen(latcp_socket, 1) != 0)
    {
	syslog(LOG_ERR, "listen latcp: %m");
    }
    // Make sure only root can talk to us via the latcp socket
    chmod(LATCP_SOCKNAME, 0600);
    fdlist.push_back(fdinfo(latcp_socket, 0, LATCP_RENDEZVOUS));

    // Open llogin socket
    unlink(LLOGIN_SOCKNAME);
    llogin_socket = socket(PF_UNIX, SOCK_STREAM, 0);
    if (llogin_socket < 0)
    {
	syslog(LOG_ERR, "Can't create llogin socket: %m");
	exit(1);
    }

    strcpy(sockaddr.sun_path, LLOGIN_SOCKNAME);
    sockaddr.sun_family = AF_UNIX;
    if (bind(llogin_socket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)))
    {
	syslog(LOG_ERR, "can't bind llogin socket: %m");
        exit(1);
    }
    if (listen(llogin_socket, 1) != 0)
    {
	syslog(LOG_ERR, "listen llogin: %m");
    }
    // Make sure everyone can use it
    chmod(LLOGIN_SOCKNAME, 0666);
    fdlist.push_back(fdinfo(llogin_socket, 0, LLOGIN_RENDEZVOUS));

    // Don't start sending service announcements
    // until we get an UNLOCK message from latcp.

    // We rely on POSIX's select() behaviour in that we use the
    // time left in the timeval parameter to make sure the circuit timer
    // goes off at a reasonably predictable interval.

#ifndef __linux__
    struct timeval next_circuit_time;
    next_circuit_time.tv_sec = 0;
    next_circuit_time.tv_usec = 0;
#endif

    struct timeval tv = {0, circuit_timer*10000};
    time_t last_node_expiry = time(NULL);
    do_shutdown = false;
    do
    {
	fd_set fds;
	FD_ZERO(&fds);

	std::list<fdinfo>::iterator i(fdlist.begin());
	for (; i != fdlist.end(); i++)
	{
	    if (i->active())
	        FD_SET(i->get_fd(), &fds);
	}

// Only Linux seems to have this select behaviour...
// for BSDs we need to work our for ourselves the delay
// until the next circuit timer tick.
#ifndef __linux__
	struct timeval now;

	gettimeofday(&now, NULL);
	if (next_circuit_time.tv_sec == 0)
	{
	    next_circuit_time = now;
	    next_circuit_time.tv_usec += circuit_timer*10000;
	    next_circuit_time.tv_sec += (next_circuit_time.tv_usec / 1000000);
	    next_circuit_time.tv_usec = (next_circuit_time.tv_usec % 1000000);
	}

	tv = next_circuit_time;
	if (tv.tv_sec < now.tv_sec
	    || (tv.tv_sec == now.tv_sec
		&& tv.tv_usec <= now.tv_usec))
	{
	    tv.tv_sec = tv.tv_usec = 0;
	}
	else
	{
	    tv.tv_sec -= now.tv_sec;
	    if ((tv.tv_usec -= now.tv_usec) < 0)
	    {
		tv.tv_sec--;
		tv.tv_usec += 1000000;
	    }
	}
#endif

	status = select(FD_SETSIZE, &fds, NULL, NULL, &tv);
	if (status < 0)
	{
	    if (errno != EINTR)
	    {
		syslog(LOG_WARNING, "Error in select: %m");
		debuglog(("Error in select: %s\n", strerror(errno)));
		do_shutdown = true;
	    }
	}
	else
	{
	    if (status == 0) // Circuit timer
	    {
	      for (int i=1; i<MAX_CONNECTIONS; i++)
		if (connections[i])
		  connections[i]->circuit_timer();
	      tv.tv_usec = circuit_timer*10000;

	      // Is it time we expired some more nodes ?
	      if (time(NULL) - last_node_expiry > 15)
		  LATServices::Instance()->expire_nodes();
#ifndef __linux__
	      next_circuit_time.tv_sec = 0;
#endif
	    }
	    else
	    {
		// Unix will never scale while this is necessary
		std::list<fdinfo>::iterator fdl(fdlist.begin());
		for (; fdl != fdlist.end(); fdl++)
		{
		    if (fdl->active() &&
			FD_ISSET(fdl->get_fd(), &fds))
		    {
			process_data(*fdl);
		    }
		}
	    }
	}

	// Tidy deleted sessions
	if (!dead_session_list.empty())
	{
	    std::list<deleted_session>::iterator dsl(dead_session_list.begin());
	    for (; dsl != dead_session_list.end(); dsl++)
	    {
		delete_entry(*dsl);
	    }
	    dead_session_list.clear();
	}

	// Tidy deleted connections
	if (!dead_connection_list.empty())
	{
	    std::list<int>::iterator dcl(dead_connection_list.begin());
	    for (; dcl != dead_connection_list.end(); dcl++)
	    {
		if (connections[*dcl])
	        {
		    delete connections[*dcl];
		    connections[*dcl] = NULL;
		}
	    }
	    dead_connection_list.clear();
	}

    } while (!do_shutdown);

    send_service_announcement(-1); // Say we are unavailable

    close(latcp_socket);
    unlink(LATCP_SOCKNAME);
    unlink(LLOGIN_SOCKNAME);

    tidy_dev_directory();
}

/* LAT socket has something for us */
void LATServer::read_lat(int sock)
{
    unsigned char buf[1600];
    unsigned char macaddr[6];
    int    len;
    int    i;
    int    ifn;
    bool   more = true;
    LAT_Header *header = (LAT_Header *)buf;

    while (more)
    {
	len = iface->recv_packet(sock, ifn, macaddr, buf, sizeof(buf), more);
	if (len == 0)
	    continue; // Probably a rogue packet

	if (len < 0)
	{
	    if (errno != EINTR && errno != EAGAIN)
	    {
		syslog(LOG_ERR, "recvmsg: %m");
		return;
	    }
	}

	// Not listening yet, but we must read the message otherwise we
	// we will spin until latcp unlocks us.
	if (locked)
	       continue;

	// Parse & dispatch it.
	switch(header->cmd)
	{
	case LAT_CCMD_SREPLY:
	case LAT_CCMD_SDATA:
	case LAT_CCMD_SESSION:
        {
	    debuglog(("session cmd for connid %d\n", header->remote_connid));
	    LATConnection *conn = NULL;
            if(header->remote_connid < MAX_CONNECTIONS)
            {
                conn = connections[header->remote_connid];
            }

	    if (conn)
	    {
	        conn->process_session_cmd(buf, len, macaddr);
	    }
	    else
	    {
		// Message format error
		send_connect_error(2, header, ifn, macaddr);
	    }
	}
	break;

	case LAT_CCMD_CONNECT:
        {
	    // Make a new connection

	    //  Check that the connection is really for one of our services
	    unsigned char name[256];
	    int ptr = sizeof(LAT_Start);
	    get_string(buf, &ptr, name);

	    debuglog(("got connect for node %s\n", name));

	    if (strcmp((char *)name, (char *)get_local_node()))
	    {
		// How the &?* did that happen?
		send_connect_error(2, header, ifn, macaddr);
		continue;
	    }

     	    // Make a new connection.
	    if ( ((i=make_new_connection(buf, len, ifn, header, macaddr) )) > 0)
	    {
		debuglog(("Made new connection: %d\n", i));
		connections[i]->send_connect_ack();
	    }
	}
	break;

	case LAT_CCMD_CONACK:
        {
	    LATConnection *conn = NULL;
	    debuglog(("Got connect ACK for %d\n", header->remote_connid));
	    if (header->remote_connid < MAX_CONNECTIONS)
	    {
		conn = connections[header->remote_connid];
	    }

	    if (conn)
	    {
		conn->got_connect_ack(buf);
	    }
	    else
	    {
		// Insufficient resources
		send_connect_error(7, header, ifn, macaddr);
	    }
	}
	break;

	case LAT_CCMD_CONREF:
	case LAT_CCMD_DISCON:
        {
	    debuglog(("Disconnecting connection %d: status %x(%s)\n",
		      header->remote_connid,
		      buf[sizeof(LAT_Header)],
		      lat_messages::connection_disconnect_msg(buf[sizeof(LAT_Header)]) ));
	    if (header->remote_connid < MAX_CONNECTIONS)
	    {
		LATConnection *conn = connections[header->remote_connid];
		if (conn)
		{
		    // We don't delete clients, we just quiesce them.
		    if (conn->isClient())
		    {
			conn->disconnect_client();
			if (conn->num_clients() == 0)
			{
			    delete conn;
			    connections[header->remote_connid] = NULL;
			}
		    }
		    else
		    {
			delete conn;
			connections[header->remote_connid] = NULL;
		    }
		}
	    }
	    else
	    {
		// Message format error
		send_connect_error(2, header, ifn, macaddr);
	    }
	}
	break;

	case LAT_CCMD_SERVICE:
	    // Keep a list of known services
	    add_services(buf, len, ifn, macaddr);
	    break;

	case LAT_CCMD_ENQUIRE:
	    reply_to_enq(buf, len, ifn, macaddr);
	    break;

	case LAT_CCMD_ENQREPLY:
	    got_enqreply(buf, len, ifn, macaddr);
	    break;

	case LAT_CCMD_STATUS:
	    forward_status_messages(buf, len);
	    break;

	    // Request for a reverse-LAT connection.
	case LAT_CCMD_COMMAND:
	    process_command_msg(buf, len, ifn, macaddr);
	    break;
	}
    }
}

// Add an FD to the list of FDs to listen to
void LATServer::add_fd(int fd, fd_type type)
{
    std::list<fdinfo>::iterator fdi;

    debuglog(("Add_fd: %d\n", fd));

    fdi = find(fdlist.begin(), fdlist.end(), fd);
    if (fdi != fdlist.end()) return; // Already exists


    fdlist.push_back(fdinfo(fdinfo(fd, NULL, type)));
}

// Remove FD from the FD list
void LATServer::remove_fd(int fd)
{
    std::list<fdinfo>::iterator fdi;
    debuglog(("remove_fd: %d\n", fd));

    fdi = find(fdlist.begin(), fdlist.end(), fd);
    if (fdi == fdlist.end()) return; // Does not exist

    fdlist.remove(*fdi);
}

// Change the DISABLED state of a PTY fd
void LATServer::set_fd_state(int fd, bool disabled)
{
    std::list<fdinfo>::iterator fdi;
    debuglog(("set_fd_state: %d, %d\n", fd, disabled));

    fdi = find(fdlist.begin(), fdlist.end(), fd);
    if (fdi == fdlist.end()) return; // Does not exist

    fdi->set_disabled(disabled);
}



/* Send a LAT message to a specified MAC address */
int LATServer::send_message(unsigned char *buf, int len, int interface, unsigned char *macaddr)
{
  if (len < 46) len = 46; // Minimum packet length
  if (len%2) len++;       // Must be an even number

#ifdef PACKET_LOSS
  static int c=0;
  if ((++c % PACKET_LOSS) == 0) {
      debuglog(("NOT SENDING THIS MESSAGE!\n"));
      return 0;
  }
#endif

  if (interface == 0) // Send to all
  {
      for (int i=0; i<num_interfaces;i++)
      {
	  if (iface->send_packet(interface_num[i], macaddr, buf, len) < 0)
	  {
	      interface_error(interface_num[i], errno);
	  }
	  else
	      interface_errs[interface_num[i]] = 0; // Clear errors

      }
  }
  else
  {
      if (iface->send_packet(interface, macaddr, buf, len) < 0)
      {
	  interface_error(interface, errno);
	  return -1;
      }
      interface_errs[interface] = 0; // Clear errors
  }

  return 0;

}

/* Get the system load average */
double LATServer::get_loadavg(void)
{
    double avg[3];

    if (getloadavg(avg, 3) > 0)
    {
	return avg[0];
    }
    else
    {
	return 0;
    }
}

// Forward status messages to their recipient connection objects.
void LATServer::forward_status_messages(unsigned char *inbuf, int len)
{
    int ptr = sizeof(LAT_Status);
    unsigned char node[256];

    get_string(inbuf, &ptr, node);

    // Forward all the StatusEntry messages
    while (ptr <= len)
    {
	if (ptr%2) ptr++; // Word aligned

	LAT_StatusEntry *entry = (LAT_StatusEntry *)&inbuf[ptr];
	if (entry->length == 0) break;

	ptr += sizeof(LAT_StatusEntry);
	get_string(inbuf, &ptr, node); // Service name
	ptr += inbuf[ptr]+1; // Past port name
	ptr += inbuf[ptr]+1; // Past service description

	if (entry->request_id < MAX_CONNECTIONS &&
	    connections[entry->request_id])
	    connections[entry->request_id]->got_status(node, entry);

    }
}

/* Reply to an ENQUIRE message with our MAC address & service name */
void LATServer::reply_to_enq(unsigned char *inbuf, int len, int interface,
			     unsigned char *remote_mac)
{
    int inptr, outptr, i;
    unsigned char outbuf[1600];
    unsigned char req_service[1600];
    LAT_Header *outhead = (LAT_Header *)outbuf;
    LAT_Header *inhead = (LAT_Header *)inbuf;

    inptr = sizeof(LAT_Header)+4;

    /* This is the service being enquired about */
    get_string(inbuf, &inptr, req_service);

    debuglog(("got ENQ for %s\n", req_service));

    // Ignore empty requests:: TODO: is this right?? - maybe we issue
    // a response with all our services in it.
    if (strlen((char*)req_service) == 0) return;

    unsigned char reply_macaddr[6];
    unsigned char *reply_node;

    // See if it is for us if we're not doing responder work.
    if (!responder)
    {
	std::list<serviceinfo>::iterator sii;
	sii = find(servicelist.begin(), servicelist.end(), (char *)req_service);
	if (sii == servicelist.end()) return; // Not ours
    }

    std::string node;
    if (LATServices::Instance()->get_highest(std::string((char *)req_service),
					     node, reply_macaddr, &interface))
    {
	reply_node = (unsigned char *)node.c_str();
    }
    else
    {
	return; // nevver 'eard of 'im.
    }

    debuglog(("Sending ENQ reply for %s\n", req_service));

    inptr += 2;

    outptr = sizeof(LAT_Header);
    outbuf[outptr++] = 0x93;
    outbuf[outptr++] = 0x07;
    outbuf[outptr++] = 0x00;
    outbuf[outptr++] = 0x00;

    /* The service's MAC address */
    outbuf[outptr++] = 0x06;
    outbuf[outptr++] = 0x00;
    for (i=0; i<5; i++)
	outbuf[outptr++] = reply_macaddr[i];

    /* Don't know what this is */
    outbuf[outptr++] = 0x14;
    outbuf[outptr++] = 0x00;

    add_string(outbuf, &outptr, reply_node);
    outbuf[outptr++] = 0x01;
    outbuf[outptr++] = 0x01;
    add_string(outbuf, &outptr, req_service);

    outhead->cmd             = LAT_CCMD_ENQREPLY;
    outhead->num_slots       = 0;
    outhead->remote_connid   = inhead->remote_connid;
    outhead->local_connid    = inhead->local_connid;
    outhead->sequence_number = inhead->sequence_number;
    outhead->ack_number      = inhead->ack_number;

    send_message(outbuf, outptr, interface, remote_mac);
}

void LATServer::shutdown()
{
    do_shutdown = true;
}

void LATServer::init(bool _static_rating, int _rating,
		     char *_service, char *_greeting, char **_interfaces,
		     int _verbosity, int _timer)
{
    // Server is locked until latcp has finished
    locked = true;

    /* Initialise the platform-specific interface code */
    iface = LATinterfaces::Create();
    if (iface->Start(LATinterfaces::ProtoLAT) == -1)
    {
	syslog(LOG_ERR, "Can't create LAT protocol socket: %m\n");
	exit(1);

    }

#ifdef ENABLE_DEFAULT_SERVICE
    // Add the default session
    servicelist.push_back(serviceinfo(_service,
				      _rating,
				      _static_rating));
#endif

    strcpy((char *)greeting, _greeting);
    verbosity = _verbosity;

    // Convert all the interface names to numbers and check they are usable
    if (_interfaces[0])
    {
	int i=0;
	while (_interfaces[i])
	{
	    interface_num[num_interfaces] = iface->find_interface(_interfaces[i]);
	    if (interface_num[num_interfaces] == -1)
	    {
		syslog(LOG_ERR, "Can't use interface %s: ignored\n", _interfaces[i]);
	    }
	    else
	    {
		interface_errs[num_interfaces] = 0;  // Clear errors
		num_interfaces++;
	    }
	    i++;
	}
	if (num_interfaces == 0)
	{
	    syslog(LOG_ERR, "No usable interfaces, latd exiting.\n");
	    exit(9);
	}
    }
    else
    {
	iface->get_all_interfaces(interface_num, num_interfaces);
    }

    // Remove any old /dev/lat symlinks
    tidy_dev_directory();

    // Save these two for any newly added services
    rating = _rating;
    static_rating = _static_rating;

    next_connection = 1;
    multicast_incarnation = 0;
    circuit_timer = _timer/10;
    local_name[0] = '\0'; // Use default node name

    memset(connections, 0, sizeof(connections));

    // Enable user group 0
    memset(user_groups, 0, 32);
    user_groups[0] = 1;

    // Look in /etc/group for a group called "lat"
    struct group *gr = getgrnam("lat");
    if (gr)
	lat_group = gr->gr_gid;
    else
	lat_group = 0;

}

// Create a new connection object for this remote node.
int LATServer::make_new_connection(unsigned char *buf, int len,
				   int interface,
				   LAT_Header *header,
				   unsigned char *macaddr)
{
    int i;
    i = get_next_connection_number();

    if (i >= 0)
    {
	next_connection = i+1;
	connections[i] = new LATConnection(i, buf, len, interface,
					   header->sequence_number,
					   header->ack_number,
					   macaddr);
    }
    else
    {
// Number of virtual circuits exceeded
	send_connect_error(9, header, interface, macaddr);
	return -1;
    }
    return i;
}

void LATServer::delete_session(int conn, unsigned char id, int fd)
{
    // Add this to the list and delete them later
    deleted_session s(LOCAL_PTY, conn, id, fd);
    dead_session_list.push_back(s);
}

void LATServer::delete_connection(int conn)
{
    // Add this to the list and delete them later
    debuglog(("connection %d pending deletion\n", conn));

    dead_connection_list.push_back(conn);
}

// Got a reply from a DS90L - add it to the services list in a dummy service
void LATServer::got_enqreply(unsigned char *buf, int len, int interface, unsigned char *macaddr)
{
    int off = 14;
    unsigned char node_addr[6];
    char node_description[256];
    char node_name[256];

    memset(node_addr, 0x00, sizeof(node_addr));
    if (0 == memcmp(node_addr, buf + off, sizeof(node_addr)))
    {
	return;
    }

    memset(node_addr, 0xFF, sizeof(node_addr));
    if (0 == memcmp(node_addr, buf + off, sizeof(node_addr)))
    {
	return;
    }

    // Node MAC address
    memcpy(node_addr, buf + off, sizeof(node_addr));

    // Skip destination node name
    off = 22;
    off += buf[off] + 1;

    // Skip node groups
    off += buf[off] + 1;

    get_string(buf, &off, (unsigned char*)node_name);
    get_string(buf, &off, (unsigned char*)node_description);

#if defined(debuglog)
    if (memcmp(node_addr, macaddr, sizeof(node_addr))) {
	debuglog(("got_enqreply : macaddr is different : %02hhX-%02hhX-%02hhX-%02hhX-%02hhX-%02hhX\n",
		  macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]));
    }
#endif

// Add it to the dummy service.
    debuglog(("got_enqreply : node: '%s' : %02hhX-%02hhX-%02hhX-%02hhX-%02hhX-%02hhX\n",
	      node_name,
	      node_addr[0], node_addr[1], node_addr[2], node_addr[3], node_addr[4], node_addr[5]));
    LATServices::Instance()->add_service(std::string(node_name),
					 std::string(""), // Dummy service name
					 std::string(node_description),
					 0,
					 interface, node_addr);

    {
        sig_blk_t _block(SIGALRM);
	std::list<std::string>::iterator sl_iter;
        sl_iter = find(slave_nodes.begin(), slave_nodes.end(), node_name);
        if (sl_iter != slave_nodes.end()) {
            debuglog(("got_enqreply : to remove from slave(%d), node: '%s'\n", 
		      slave_nodes.size(), sl_iter->c_str()));
	    slave_nodes.erase(sl_iter);
	}

        sl_iter = find(known_slave_nodes.begin(), known_slave_nodes.end(), node_name);
        if (sl_iter == known_slave_nodes.end()) {
	    known_slave_nodes.push_back(node_name);
            debuglog(("got_enqreply : added to known(%d), node: '%s'\n", 
		      known_slave_nodes.size(),
		       node_name));
	}
    }
}


// Got a COMMAND message from a remote node. it probably wants
// to set up a reverse-LAT session to us.
void LATServer::process_command_msg(unsigned char *buf, int len, int interface, unsigned char *macaddr)
{
    LAT_Command *msg = (LAT_Command *)buf;
    int ptr = sizeof(LAT_Command);
    unsigned char remnode[256];
    unsigned char remport[256];
    unsigned char service[256];
    unsigned char portname[256];

// TODO: Check the params in the LAT_Command message...
    debuglog(("REVLAT cmd: %d, opcode:%d request_id: %d\n", msg->cmd, msg->opcode, msg->request_id));

    ptr += buf[ptr]+1; // Skip past groups for now.
    ptr += buf[ptr]+1; // Skip past groups for now.

    // Get the remote node name.
    get_string(buf, &ptr, remnode);
    get_string(buf, &ptr, remport);
    ptr += buf[ptr]+1; // Skip past greeting...
    get_string(buf, &ptr, service);
    get_string(buf, &ptr, portname);

    debuglog(("Got request for reverse-LAT connection to %s/%s from %s/%s\n",
	      service, portname, remnode, remport));

    // Make a new connection
    // We can reuse connections here...
    int connid = find_connection_by_node((char *)remnode);
    if (connid == -1)
    {
	connid = get_next_connection_number();
	connections[connid] = new LATConnection(connid,
						(char *)service,
						(char *)remport,
						(char *)portname,
						(char *)remnode,
						false,
						true);
    }

    // Make a reverse-session and connect it.
    LAT_SessionStartCmd startcmd;
    memset(&startcmd, 0, sizeof(startcmd));
    startcmd.dataslotsize = 255;
    if (connections[connid]->create_reverse_session((const char *)service,
						    (const char *)&startcmd,
						    dn_ntohs(msg->request_id),
						    interface, macaddr) == -1)
    {
	// If we created this connection just for the new session
	// then get rid of it.
	if (connections[connid]->num_clients() == 0)
	    delete_connection(connid);
    }
}

// Add services received from a service announcement multicast
void LATServer::add_services(unsigned char *buf, int len, int interface, unsigned char *macaddr)
{
    LAT_ServiceAnnounce *announce = (LAT_ServiceAnnounce *)buf;
    int ptr = sizeof(LAT_ServiceAnnounce);
    unsigned char service_groups[32];
    unsigned char nodename[256];
    unsigned char greeting[255];
    unsigned char service[255];
    unsigned char ident[255];

    // Set the groups to all zeros initially
    memset(service_groups, 0, sizeof(service_groups));

    // Make sure someone isn't pulling our leg.
    if (announce->group_length > 32)
	announce->group_length = 32;

    // Get group numbers
    memcpy(service_groups, buf+ptr, announce->group_length);

    // Compare with our user groups mask (which is always either completely
    // empty or the full 32 bytes)
    int i;
    bool gotone = false;
    for (i=0; i<announce->group_length; i++)
    {
	if (user_groups[i] & service_groups[i])
	{
	    gotone = true;
	    break;
	}
    }

    if (!gotone)
    {
	debuglog(("remote node not in our user groups list\n"));
	return;
    }

    ptr += announce->group_length;
    get_string(buf, &ptr, nodename);
    get_string(buf, &ptr, greeting);

    int num_services = buf[ptr++];

    debuglog(("service announcement: status = %d\n", announce->node_status));

    // Get all the services & register them
    for (int i=1; i<=num_services; i++)
    {
	int rating       = buf[ptr++];

	get_string(buf, &ptr, service);
	get_string(buf, &ptr, ident);

	if ( (announce->node_status & 1) == 0)
	{
	    LATServices::Instance()->add_service(std::string((char*)nodename),
						 std::string((char*)service),
						 std::string((char*)ident),
						 rating,
						 interface, macaddr);
	}
	else
	{
	    LATServices::Instance()->remove_node(std::string((char*)nodename));
	}
    }
}

// Wait for data available on a client PTY
void LATServer::add_pty(LocalPort *port, int fd)
{
    fdlist.push_back(fdinfo(fd, port, LOCAL_PTY));
}


// Got a new connection from latcp
void LATServer::accept_latcp(int fd)
{
    struct sockaddr_un socka;
    socklen_t sl = sizeof(socka);

    int latcp_client_fd = accept(fd, (struct sockaddr *)&socka, &sl);
    if (latcp_client_fd >= 0)
    {
	debuglog(("Got LATCP connection\n"));
	latcp_circuits[latcp_client_fd] = new LATCPCircuit(latcp_client_fd);

	fdlist.push_back(fdinfo(latcp_client_fd, 0, LATCP_SOCKET));
    }
    else
	syslog(LOG_WARNING, "accept on latcp failed: %m");
}

// Got a new connection from llogin
void LATServer::accept_llogin(int fd)
{
    struct sockaddr_un socka;
    socklen_t sl = sizeof(socka);

    int llogin_client_fd = accept(fd, (struct sockaddr *)&socka, &sl);
    if (llogin_client_fd >= 0)
    {
	debuglog(("Got llogin connection\n"));
	latcp_circuits[llogin_client_fd] = new LLOGINCircuit(llogin_client_fd);

	fdlist.push_back(fdinfo(llogin_client_fd, 0, LLOGIN_SOCKET));
    }
    else
	syslog(LOG_WARNING, "accept on llogin failed: %m");
}

// Read a request from LATCP
void LATServer::read_latcp(int fd)
{
    debuglog(("Got command on latcp socket: %d\n", fd));

    if (!latcp_circuits[fd]->do_command())
    {
	// Mark the FD for removal
	deleted_session s(LATCP_SOCKET, 0, 0, fd);
	dead_session_list.push_back(s);
    }
}

// Read a request from LLOGIN
void LATServer::read_llogin(int fd)
{
    debuglog(("Got command on llogin socket: %d\n", fd));

    if (!latcp_circuits[fd]->do_command())
    {
	// Mark the FD for removal
	deleted_session s(LLOGIN_SOCKET, 0, 0, fd);
	dead_session_list.push_back(s);
    }
}

void LATServer::delete_entry(deleted_session &dsl)
{
    switch (dsl.get_type())
    {
    case INACTIVE:
	break; // do nothing;

    case LAT_SOCKET:
    case LATCP_RENDEZVOUS:
    case LLOGIN_RENDEZVOUS:
	// These never get deleted
	break;

    case LATCP_SOCKET:
    case LLOGIN_SOCKET:
	remove_fd(dsl.get_fd());
	close(dsl.get_fd());
	delete latcp_circuits[dsl.get_fd()];
	latcp_circuits.erase(dsl.get_fd());
	break;

    case LOCAL_PTY:
    case DISABLED_PTY:
	remove_fd(dsl.get_fd());
	if (connections[dsl.get_conn()])
	    connections[dsl.get_conn()]->remove_session(dsl.get_id());
	break;

    default:
       debuglog(("Unknown socket type: %d\n", dsl.get_type()));
       break;
    }
}

void LATServer::process_data(fdinfo &fdi)
{
    switch (fdi.get_type())
    {
    case INACTIVE:
    case DISABLED_PTY:
	break; // do nothing;

    case LOCAL_PTY:
	fdi.get_localport()->do_read();
	break;

    case LAT_SOCKET:
	read_lat(fdi.get_fd());
	break;

    case LATCP_RENDEZVOUS:
	accept_latcp(fdi.get_fd());
	break;

    case LLOGIN_RENDEZVOUS:
	accept_llogin(fdi.get_fd());
	break;

    case LATCP_SOCKET:
	read_latcp(fdi.get_fd());
	break;

    case LLOGIN_SOCKET:
	read_llogin(fdi.get_fd());
	break;
    }
}

// Send a circuit disconnect with error code
void LATServer::send_connect_error(int reason, LAT_Header *msg, int interface,
				   unsigned char *macaddr)
{
    unsigned char buf[1600];
    LAT_Header *header = (LAT_Header *)buf;
    int ptr=sizeof(LAT_Header);

    memset(buf, 0, sizeof(buf));

    header->cmd             = LAT_CCMD_DISCON;
    header->num_slots       = 0;
    header->local_connid    = 0; // Servers expect this to be clear
    header->remote_connid   = msg->local_connid;
    header->sequence_number = msg->sequence_number;
    header->ack_number      = msg->ack_number;
    buf[ptr++]              = reason;
    send_message(buf, ptr, interface, macaddr);
}

// Shutdown by latcp
void LATServer::Shutdown()
{
    // Shutdown all the connections first
    for (int i=1; i<MAX_CONNECTIONS; i++)
    {
	if (connections[i])
	    dead_connection_list.push_back(i);
    }

    // Exit the main loop.
    do_shutdown = true;
}

// Return true if 'name' is a local service
bool LATServer::is_local_service(char *name)
{
    // Look for it.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii != servicelist.end()) return true;

    return false;
}

// Return the command info for a service
int LATServer::get_service_info(char *name, std::string &cmd, int &maxcon, int &curcon, uid_t &uid, gid_t &gid)
{
    // Look for it.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii != servicelist.end())
    {
	cmd = sii->get_command();
	maxcon = sii->get_max_connections();
	curcon = sii->get_cur_connections();
	uid = sii->get_uid();
	gid = sii->get_gid();

	return 0;
    }

    return -1;
}

// Add a new service from latcp
bool LATServer::add_service(char *name, char *ident, char *command, int max_conn,
			    uid_t uid, gid_t gid, int _rating, bool _static_rating)
{
    // Look for it.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii != servicelist.end()) return false; // Already exists

    // if rating is 0 then use the node default.
    if (!_rating) _rating = rating;

    servicelist.push_back(serviceinfo(name,
				      _rating,
				      _static_rating,
				      ident,
				      max_conn,
				      command,
				      uid, gid));

    // Resend the announcement message.
    send_service_announcement(-1);

    return true;
}


// Change the rating of a service
bool LATServer::set_rating(char *name, int _rating, bool _static_rating)
{
    // Look for it.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii == servicelist.end()) return false; // Not found it

    sii->set_rating(_rating, _static_rating);

    // Resend the announcement message.
    send_service_announcement(-1);
    return true;
}

// Change the ident of a service
bool LATServer::set_ident(char *name, char *ident)
{
    // Look for it.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii == servicelist.end()) return false; // Not found it

    debuglog(("Setting ident for %s to '%s'\n", name, ident));

    sii->set_ident(ident);

    // Resend the announcement message.
    send_service_announcement(-1);
    return true;
}


// Remove reverse-LAT port via latcp
bool LATServer::remove_port(char *name)
{
    debuglog(("remove port %s\n", name));

    // Search for it.
    std::list<LocalPort>::iterator p(portlist.begin());
    for (; p != portlist.end(); p++)
    {
	if (strcmp(p->get_devname().c_str(), name) == 0)
	{
	    p->close_and_delete();
	    portlist.erase(p);
	    return true;
	}

    }
    return false;
}



// Remove service via latcp
bool LATServer::remove_service(char *name)
{
    // Only add the service if it does not already exist.
    std::list<serviceinfo>::iterator sii;
    sii = find(servicelist.begin(), servicelist.end(), name);
    if (sii == servicelist.end()) return false; // Does not exist

    servicelist.erase(sii);

    // This is overkill but it gets rid of the service in the known
    // services table.
    LATServices::Instance()->remove_node(std::string((char*)get_local_node()));

    // Resend the announcement message -- this will re-add our node
    // services back into the known services list.
    send_service_announcement(-1);

    return true;
}

// Change the multicast timer
void LATServer::set_multicast(int newtime)
{
    if (newtime)
    {
	multicast_timer = newtime;
	alarm(newtime);
    }
}

// Change the node name
void LATServer::set_nodename(unsigned char *name)
{
    debuglog(("New node name is %s\n", name));

    // Remove all existing node services
    LATServices::Instance()->remove_node(std::string((char*)get_local_node()));

    // Set the new name
    strcpy((char *)local_name, (char *)name);

    // Resend the announcement message -- this will re-add our node
    // services back into the known services list.
    send_service_announcement(-1);

}

// Start sending service announcements
void LATServer::unlock()
{
    sig_blk_t _block(SIGALRM);
    locked = false;
    alarm_signal(SIGALRM);
}

// Generic make_connection code for llogin & port sessions.
int LATServer::make_connection(int fd, const char *service, const char *rnode, const char *port,
			       const char *localport, const char *password, bool queued)
{
    unsigned char macaddr[6];
    std::string servicenode;
    int this_int;
    char node[255];

    // Take a local copy of the node so we can overwrite it.
    strcpy(node, rnode);

    // If no node was specified then use the highest rated one
    if (node[0] == '\0')
    {
debuglog(("make_connection : no node, use highest\n"));
	if (!LATServices::Instance()->get_highest(std::string((char*)service),
						  servicenode, macaddr,
						  &this_int))
	{
	    debuglog(("Can't find service %s\n", service));
	    return -2; // Never eard of it!
	}
	strcpy((char *)node, servicenode.c_str());
    }
    else
    {
debuglog(("make_connection : node : %s\n", node));
	// Try to find the node
	if (!LATServices::Instance()->get_node(std::string((char*)service),
					       std::string((char*)node), macaddr,
					       &this_int))
	{
	    debuglog(("Can't find node %s in service\n", node, service));

	    return -2;
	}
    }

// Look for a connection that's already in use for this node, unless we
// are queued in which case we need to allocate a new connection for
// initiating the connection and receiving status requests on.
    int connid = -1;
    if (!queued) connid = find_connection_by_node(node);
    if (connid == -1)
    {
	// None: create a new one
	connid = get_next_connection_number();
	connections[connid] = new LATConnection(connid,
						(char *)service,
						(char *)port,
						(char *)localport,
						(char *)node,
						queued,
						false);
    }

    return connid;
}


int LATServer::make_llogin_connection(int fd, const char *service, const char *rnode, const char *port,
				      const char *localport, const char *password, bool queued)
{
    int ret;
    int connid;

    connid = make_connection(fd, service, rnode, port, localport, password, queued);
    if (connid < 0)
	return connid;

    debuglog(("lloginSession for %s has connid %d\n", service, connid));

    ret = connections[connid]->create_llogin_session(fd, service, port, localport, password);

    // Remove LLOGIN socket from the list as it's now been
    // added as a PTY (honest!)
    deleted_session s(LLOGIN_SOCKET, 0, 0, fd);
    dead_session_list.push_back(s);

    return ret;
}


// Called when activity is detected on a LocalPort - we connect it
// to the service.
int LATServer::make_port_connection(int fd, LocalPort *lport,
				    const char *service, const char *rnode,
				    const char *port,
				    const char *localport,
				    const char *password,
				    bool queued)
{
    int ret;
    int connid;
    debuglog(("LATServer::make_port_connection : fd %d, lport '0x%X', service '%s', rnode '%s', port '%s', localport '%s', pwd '%s'\n",
	      fd, lport, service, rnode, port, localport, password));

    connid = make_connection(fd, service, rnode, port, localport, password, queued);
    if (connid < 0)
	return connid;

    debuglog(("localport for %s has connid %d\n", service, connid));

    ret = connections[connid]->create_localport_session(fd, lport, service,
							port, localport, password);

    return ret;
}


// Called from latcp to create a /dev/lat/ port
// Here we simply add it to a lookaside list and wait
// for it to be activated by a user.
int LATServer::create_local_port(unsigned char *service,
				 unsigned char *portname,
				 unsigned char *devname,
				 unsigned char *remnode,
				 bool queued,
				 bool clean,
				 unsigned char *password)
{
    debuglog(("Server::create_local_port: %s\n", devname));

// Don't create it if it already exists.
    std::list<LocalPort>::iterator i(portlist.begin());
    for (; i != portlist.end(); i++)
    {
	if (strcmp(i->get_devname().c_str(), (char *)devname) == 0)
	{
	    return 1; // already in use
	}
    }

    portlist.push_back(LocalPort(service, portname, devname, remnode, queued, clean, password));

// Find the actual port in the list and start it up, this is because
// the STL containers hold actual objects rather then pointers
    std::list<LocalPort>::iterator p(portlist.begin());
    for (; p != portlist.end(); p++)
    {
	if (strcmp(p->get_devname().c_str(), (char *)devname) == 0)
	{
	    p->init_port();
	    break;
	}
    }

    return 0;
}


// Make this as much like VMS LATCP SHOW NODE as possible.
bool LATServer::show_characteristics(bool verbose, std::ostringstream &output)
{
    output <<std::endl;
    output.setf(std::ios::left, std::ios::adjustfield);

    output << "Node Name:  ";
    output.width(15);
    output << get_local_node() << " " << "    LAT Protocol Version:       " << LAT_VERSION << "." << LAT_VERSION_ECO << std::endl;
    output.setf(std::ios::right, std::ios::adjustfield);
    output << "Node State: On " << "                 LATD Version:               " << VERSION << std::endl;
    output << "Node Ident: " << greeting << std::endl;
    output << std::endl;

    output << "Service Responder : " << (responder?"Enabled":"Disabled") << std::endl;
    output << "Interfaces        : ";
    for (int i=0; i<num_interfaces; i++)
    {
	output << iface->ifname(interface_num[i]) << " ";
    }
    output << std::endl << std::endl;

    output << "Circuit Timer (msec): " << std::setw(6) << circuit_timer*10 << "    Keepalive Timer (sec): " << std::setw(6) << keepalive_timer << std::endl;
    output << "Retransmit Limit:     " << std::setw(6) << retransmit_limit << std::endl;
    output << "Multicast Timer (sec):" << std::setw(6) << multicast_timer << std::endl;
    output << std::endl;

    // Show groups
    output << "User Groups:     ";
    print_bitmap(output, true, user_groups);
    output << "Service Groups:  ";
    print_bitmap(output, groups_set, groups);
    output << std::endl;

    // Show services we are accepting for.
    output << "Service Name   Status   Rating  Identification" << std::endl;
    std::list<serviceinfo>::iterator i(servicelist.begin());
    for (; i != servicelist.end(); i++)
    {
	output.width(15);
	output.setf(std::ios::left, std::ios::adjustfield);
	output << i->get_name().c_str();
	output.setf(std::ios::right, std::ios::adjustfield);
	output << "Enabled" << std::setw(6) << i->get_rating() <<
	    (i->get_static()?"    ":" D  ") << i->get_id() << std::endl;
    }

    output << std::endl << "Port                    Service         Node            Remote Port     Queued" << std::endl;

    // Show allocated ports
    std::list<LocalPort>::iterator p(portlist.begin());
    for (; p != portlist.end(); p++)
    {
	p->show_info(verbose, output);
    }

    // NUL-terminate it.
    output << std::endl << std::ends;

    return true;
}

bool LATServer::show_nodes(bool verbose, std::ostringstream &output)
{
    return LATServices::Instance()->list_dummy_nodes(verbose, output);
}

// Return a number for a new connection
int LATServer::get_next_connection_number()
{
    int i;
    for (i=next_connection; i < MAX_CONNECTIONS; i++)
    {
	if (!connections[i])
	{
	    next_connection = i+1;
	    return i;
	}
    }

// Didn't find a slot here - try from the start
    for (i=1; i < next_connection; i++)
    {
	if (!connections[i])
	{
	    next_connection = i+1;
	    return i;
	}
    }
    return -1;
}

int LATServer::set_servergroups(unsigned char *bitmap)
{
    // Assume all unset if this is the first mention of groups
    if (!groups_set)
    {
	memset(groups, 0, 32);
	user_groups[0] = 1; // But enable group 0
    }
    groups_set = true;

    for (int i=0; i<32; i++)
    {
	groups[i] |= bitmap[i];
    }
    return true;
}

int LATServer::unset_servergroups(unsigned char *bitmap)
{
    // Assume all set if this is the first mention of groups
    if (!groups_set)
    {
	memset(groups, 0xFF, 32);
    }
    groups_set = true;

    for (int i=0; i<32; i++)
    {
	groups[i] &= ~bitmap[i];
    }
    return true;
}

int LATServer::set_usergroups(unsigned char *bitmap)
{
    for (int i=0; i<32; i++)
    {
	user_groups[i] |= bitmap[i];
    }
    return true;
}

int LATServer::unset_usergroups(unsigned char *bitmap)
{
    for (int i=0; i<32; i++)
    {
	user_groups[i] &= ~bitmap[i];
    }
    return true;
}

// Look for a connection for this node name, if not found then
// return -1
int LATServer::find_connection_by_node(const char *node)
{
    debuglog(("Looking for connection to node %s\n", node));
    for (int i=1; i<MAX_CONNECTIONS; i++)
    {
	if (connections[i] &&
	    connections[i]->node_is(node) &&
	    connections[i]->num_clients() < LATConnection::MAX_SESSIONS-1)
	{
	    debuglog(("Reusing connection for node %s\n", node));
	    return i;
	}
    }
    return -1;
}

// Print a groups bitmap
// TODO: print x-y format like we accept in latcp.
void LATServer::print_bitmap(std::ostringstream &output, bool isset, unsigned char *bitmap)
{
    if (!isset)
    {
	output << "0" << std::endl;
	return;
    }

    bool printed = false;

    for (int i=0; i<32; i++) // Show bytes
    {
	unsigned char thebyte = bitmap[i];

	for (int j=0; j<8; j++) // Bits in the byte
	{
	    if (thebyte&1)
	    {
		if (printed) output << ",";
		printed = true;
		output << i*8+j;
	    }
	    thebyte = thebyte>>1;
	}
    }
    output << std::endl;
}

LATServer *LATServer::instance = NULL;
unsigned char LATServer::greeting[255] = {'\0'};
