/*

			Speak Freely for Unix
			   Echo Back Server

     Designed and implemented in January of 1996 by John Walker.

*/

#include "speakfree.h"
#include "vat.h"

#define Echo_Base   0		      /* Echo server offset from speaker port */

#define Echo_Time   10		      /* Echo retransmit time in seconds */

static int debugforce = 0;	      /* Debugging forced / prevented ? */
static int whichport = Internet_Port + Echo_Base; /* Port to listen on (base address) */
static int sock;		      /* Input socket */
static int ssock;		      /* Control socket (RTP/VAT) */
static struct sockaddr_in from;       /* Sending host address */
static struct sockaddr_in name;       /* Address of destination host */
static int fromlen;		      /* Length of sending host address */
static int showhosts = FALSE;	      /* Show host names that connect */
static int hosttimeout = 180 * 1000000L; /* Consider host idle after this time */
static char *prog;		      /* Program name */ 
static long timebase;		      /* Time in seconds at start of program */

static struct sockaddr_in lookhost;   /* Look who's listening host, if any */
static char *sdes = NULL;	      /* RTP SDES packet */
static int sdesl;		      /* RTP SDES packet length */
static unsigned long ssrc;	      /* RTP synchronisation source identifier */
static double lwltimer; 	      /* Time next LWL retransmit scheduled */
static int actives = 0; 	      /* Currently active hosts */

#define LWL_RETRANSMIT	(5 * 60 * 1000000.0) /* Microseconds between LWL updates */

static struct in_addr lwl_sites[LWL_MAX_SITES]; /* LWL site addresses */
static long lwl_ports[LWL_MAX_SITES]; /* Ports for LWL hosts */
static int lwl_nsites = 0;	      /* Number of LWL sites published on */

/* Open connection state. */

#define connection e_connection

struct connection {
    struct connection *con_next;      /* Next connection */
    struct in_addr con_addr;	      /* Host Internet address */
    char pgpkey[17];		      /* PGP key for connection */
    char keymd5[16];		      /* Digest of key file */
    double con_timeout; 	      /* Connection timeout time */
    char con_hostname[264];	      /* Host name */
    char *con_compmodes;	      /* Last compression modes */
    int con_control;		      /* Control packet seen ? */
    int con_bye;		      /* BYE packet received ? */

    char face_filename[300];	      /* Face temporary file name */
    FILE *face_file;		      /* Face file, when open */
    pid_t face_viewer;		      /* Face file viewer PID */
    int face_stat;		      /* Face retrieval status */
    long face_address;		      /* Address of current block request */
    int face_retry;		      /* Timeout retry count */
    int face_timeout;		      /* Timeout interval */
};

struct connection *conn = NULL;       /* Chain of current connections */

#define Debug	    (debugforce != 0) /* Generate debug output */

#define TickTock    (10 * 1000000L)   /* Alarm interval in microseconds */
#define TockTock    (60 * 1000000L)   /* Alarm interval when no connections open */

struct queuedPacket {
    struct queuedPacket *next;	      /* Next packet in chain */
    double when;		      /* When packet should be sent */
    struct sockaddr_in where;	      /* Where packet should be sent */
    int pktlen; 		      /* Packet length */
    char pktdata[2];		      /* Packet data */
};

struct queuedPacket *qph = NULL,      /* Queue of packets waiting to be echoed */
		    *qptail = NULL;
static int crit = FALSE;	      /* Queue critical section lock */
static int clash = FALSE;	      /* Critical section clash retry flag */

/*  ETIME  --  Edit time and date for log messages.  */

static char *etime()
{
    struct tm *t;
    time_t clock;
    static char s[20];

    time(&clock);
    t = localtime(&clock);
    sprintf(s, "%02d-%02d %02d:%02d", t->tm_mon + 1, t->tm_mday,
	       t->tm_hour, t->tm_min);
    return s;
}

/*  COMPRESSIONTYPE  --  Return a string describing the type of
			 compression employed in this buffer.  We
			 attempt to distinguish RTP and VAT
			 messages from our own here.  This isn't
			 100% reliable in the case of VAT, but
			 nothing really terrible will happen if
			 we guess wrong.  We only test a packet for
			 VAT if given the clue that we've seen a
			 packet arrive on the control channel. */

static char *compressionType(msg, clue)
  soundbuf *msg;
  int clue;
{
    unsigned char *p = (unsigned char *) msg;

    if (((p[0] >> 6) & 3) == RTP_VERSION) {
	char *r;

	switch (p[1] & 0x7F) {
	    case 0:
		r = "RTP PCMU";
		break;

	    case 1:
		r = "RTP 1016";
		break;

	    case 2:
		r = "RTP G721";
		break;

	    case 3:
		r = "RTP GSM";
		break;

	    case 5:
	    case 6:
		r = "RTP DVI4";
		break;

	    case 7:
		r = "RTP LPC";
		break;

	    case 8:
		r = "RTP PCMA";
		break;

	    case 9:
		r = "RTP G722";
		break;

	    case 10:
	    case 11:
		r = "RTP L16";
		break;

	    default:
		r = "RTP Unknown";
		break;
	}
	return r;
    }

    /* Hokey attempt to detect VAT packets.  We can't tell
       VAT PCMU packets from unmarked Speak Freely packets, so
       we let them fall through. */

    if (clue && (p[0] & 0xC0) == 0) {
	char *r = NULL;

	switch (p[1] & 0x7F) {
	    case VAT_AUDF_GSM:
		r = "VAT GSM";
		break;

	    case VAT_AUDF_LPC4:
		r = "VAT LPC";
		break;

	    case VAT_AUDF_IDVI:
		r = "VAT IDVI";
		break;

	    case VAT_AUDF_L16_16:
	    case VAT_AUDF_L16_44:
		r = "VAT L16";
		break;
	}
	if (r != NULL) {
	    return r;
	}
    }

    return ((msg->compression & (fComp2X | fCompGSM)) ==
				(fComp2X | fCompGSM)) ?
				"GSM+2X compressed" :
	   ((msg->compression & (fComp2X | fCompADPCM)) ==
				(fComp2X | fCompADPCM)) ?
				"ADPCM+2X compressed" :
	   ((msg->compression & (fComp2X | fCompLPC)) ==
				(fComp2X | fCompLPC)) ?
				"LPC+2X compressed" :
	   ((msg->compression & (fComp2X | fCompLPC10)) ==
				(fComp2X | fCompLPC10)) ?
				"LPC10+2X compressed" :
	   ((msg->compression & (fComp2X | fCompVOX)) ==
				(fComp2X | fCompVOX)) ?
				"VOX+2X compressed" :
	   ((msg->compression & fCompADPCM) ? "ADPCM compressed" :
	   ((msg->compression & fCompLPC) ? "LPC compressed" :
	   ((msg->compression & fCompLPC10) ? "LPC10 compressed" :
	   ((msg->compression & fComp2X) ? "2X compressed" :
	   ((msg->compression & fCompGSM) ? "GSM compressed" :
	   ((msg->compression & fCompVOX) ? "VOX compressed" :
					    "uncompressed"))))));
}

/*  MAKESESSIONKEY  --	Generate session key.  */

static void makeSessionKey(key)
  char *key;
{
    struct MD5Context md5c;
    char s[1024];

    s[0] = 0;
    sprintf(s + strlen(s), "%u", getpid());
    sprintf(s + strlen(s), "%u", getppid());
    V getcwd(s + strlen(s), 256);
    sprintf(s + strlen(s), "%u", clock());
    V cuserid(s + strlen(s));
    sprintf(s + strlen(s), "%u", time(NULL));
#ifdef Solaris
    sysinfo(SI_HW_SERIAL, s + strlen(s), 12);
#else
    sprintf(s + strlen(s), "%u", gethostid());
#endif
    getdomainname(s + strlen(s), 256);
    gethostname(s + strlen(s), 256);
    sprintf(s + strlen(s), "%u", getuid());
    sprintf(s + strlen(s), "%u", getgid());
    MD5Init(&md5c);
    MD5Update(&md5c, s, strlen(s));
    MD5Final(key, &md5c);
}

/*  ULARM  --  Wrapper for setitimer() that looks like alarm()
	       but accepts a time in microseconds.  */

static void ularm(t)
  long t;
{
    struct itimerval it;

    it.it_value.tv_sec = t / 1000000L;
    it.it_value.tv_usec = t % 1000000L;
    it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &it, NULL);
}

/*  SENDLWLMESSAGE  --	If enabled, send a message identifying us
			to each selected Look Who's Listening server.  */

static void sendLwlMessage(dobye)
  int dobye;
{
    int i, sock;
    struct sockaddr_in name;

    for (i = 0; i < lwl_nsites; i++) {
	if (lwl_ports[i] >= 0) {
	    sock = socket(AF_INET, SOCK_STREAM, 0);
	    if (sock < 0) {
		perror("opening look who's listening socket");
		sdes = NULL;
		return;
	    }

	    lookhost.sin_port = htons(lwl_ports[i]);
	    bcopy((char *) (&lwl_sites[i]), (char *) &lookhost.sin_addr.s_addr,
		  sizeof lookhost.sin_addr.s_addr);

	    if (connect(sock, (struct sockaddr *) &(lookhost), sizeof lookhost) >= 0) {
		if (dobye) {
		    char v[1024];
		    int l;

		    l = rtp_make_bye(v, ssrc, "Exiting sfspeaker", FALSE);
		    if (send(sock, v, l, 0) < 0) {
			perror("sending look who's listening BYE packet");
		    }
		} else {
		    if (send(sock, (char *) sdes, sdesl, 0) < 0) {
			perror("sending look who's listening source ID message");
		    }
		}
	    } else {
		perror("connecting look who's listening socket");
	    }
	}
    }
    lwltimer = LWL_RETRANSMIT;
    close(sock);
}

/*  RELEASE  --  Alarm signal-catching function to retransmit
		 packets at the correct time and to time out
		 idle hosts.  */

static void release()
{
    struct connection *c, *l, *n;

    if (crit) {
	clash = TRUE;
	ularm(60000000L);
    } else {
	struct timeval tp;
	struct timezone tzp;
	struct queuedPacket *qp;
	double td;
	long timerStep;

	gettimeofday(&tp, &tzp);
	td = (tp.tv_sec - timebase) * 1000000.0 + tp.tv_usec;

	/* Transmit any queued packets scheduled to go out within
	   epsilon of the current time. */

#define Timer_Epsilon	4000	      /* Timer epsilon in microseconds */
#define Timer_Latency	2000	      /* Dispatch latency adjustment */

#if (Timer_Latency >= Timer_Epsilon)
	error = "Timer epsilon must be greater than latency.";
#endif

	while (qph != NULL && ((qph->when - td) < Timer_Epsilon)) {
	    if (qph->pktlen > 0) {
		if (sendto(sock, qph->pktdata, qph->pktlen,
		    0, (struct sockaddr *) &(qph->where),
		    sizeof(struct sockaddr_in)) < 0) {
		    perror("sending datagram message");
		}
		if (Debug) {
		    fprintf(stderr, "%s: returning %d bytes to %s/%d.\n",
			prog, qph->pktlen, inet_ntoa(qph->where.sin_addr),
			ntohs(qph->where.sin_port));
		}
	    }
	    qp = qph;
	    qph = qp->next;
	    if (qph == NULL) {
		qptail = NULL;
	    }
	    free(qp);
	}

	/* Mark idle any hosts that haven't sent us anything recently. */

	c = conn;
	l = NULL;
	actives = 0;
	while (c != NULL) {
	    n = c->con_next;
	    if (!c->con_bye && (td < (c->con_timeout + hosttimeout))) {
		actives++;
		l = c;
	    } else {
		if (showhosts) {
		    fprintf(stderr, "%s: %s %s idle\n", prog, etime(), c->con_hostname);
		}
		if (l == NULL) {
		    conn = n;
		} else {
		    l->con_next = n;
		}
		free(c);
	    }
	    c = n;
	}

	/* Update our Look Who's Listening information if the
	   timeout has expired. */

	if (sdes != NULL && td >= lwltimer) {
	    sendLwlMessage(FALSE);
	    lwltimer = td + LWL_RETRANSMIT;
	}

	/* Reset the time to the next event in the queue. */

	if (qph != NULL) {
	    timerStep = (long) ((qph->when - td) - Timer_Latency);
	} else {
	    if (actives > 0) {
		timerStep = TickTock;
	    } else {
		timerStep = TockTock;
	    }
	}
	ularm(timerStep);
	if (Debug) {
	    fprintf(stderr, "Tick: %.2f...\n", timerStep / 1000000.0);
	}
    }
    signal(SIGALRM, release);	      /* Set signal to handle timeout */
}

/*  EXITING  --  Catch as many program termination signals as
		 possible and clean up before exit.  */

static void exiting()
{

    if (sdes) {
	sendLwlMessage(TRUE);
    }
    exit(0);
}

/*  PROG_NAME  --  Extract program name from argv[0].  */

static char *prog_name(arg)
  char *arg;
{
    char *cp = strrchr(arg, '/');

    return (cp != NULL) ? cp + 1 : arg;
}

/*  USAGE  --  Print how-to-call information.  */

static void usage()
{
    V fprintf(stderr, "%s  --  Speak Freely echo server.\n", prog);
    V fprintf(stderr, " 	   %s.\n", Relno);
    V fprintf(stderr, "\n");
    V fprintf(stderr, "Usage: %s [options]\n", prog);
    V fprintf(stderr, "Options:\n");
    V fprintf(stderr, " 	  -D		   Enable debug output\n");
    V fprintf(stderr, " 	  -Pport	   Listen on given port\n");
    V fprintf(stderr, " 	  -U		   Print this message\n");
    V fprintf(stderr, " 	  -Vtimeout	   Show hostnames that connect\n");
    V fprintf(stderr, "\n");
    V fprintf(stderr, "by John Walker\n");
    V fprintf(stderr, "   WWW:	  http://www.fourmilab.ch/\n");
}

/*  Main program.  */

main(argc, argv)
  int argc;
  char *argv[];
{
    int i, length;
    struct soundbuf sb;
    struct connection *c;
    char *cp;
    int newconn;
    struct auhdr {		      /* .au file header */
	char magic[4];
	long hsize, dsize, emode, rate, nchan;
    };
    struct timeval tp;
    struct timezone tzp;
    struct queuedPacket *qp;

    prog = prog_name(argv[0]);
    gettimeofday(&tp, &tzp);
    timebase = tp.tv_sec;

    /* First pass option processing.  We have to first scan
       the options to handle any which affect creation of the
       socket.	One the second pass we can assume the socket
       already exists, allowing us to join multicast groups,
       etc. */

    for (i = 1; i < argc; i++) {
	char *op, opt;

	op = argv[i];
	if (*op == '-') {
	    opt = *(++op);
	    if (islower(opt)) {
		opt = toupper(opt);
	    }

	    switch (opt) {

		case 'P':	      /* -Pport  --  Port to listen on */
		    whichport = atoi(op + 1);
		    break;

		case 'U':	      /* -U  --  Print usage information */
		case '?':	      /* -?  --  Print usage information */
		    usage();
		    return 0;
	    }
	} else {
	    usage();
	    return 2;
	}
    }

    /* Create the sockets from which to read */

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
	perror("opening data socket");
	return 1;
    }

    ssock = socket(AF_INET, SOCK_DGRAM, 0);
    if (ssock < 0) {
	perror("opening control socket");
	return 1;
    }

    /* Create name with wildcards. */

    name.sin_family = AF_INET;
    name.sin_addr.s_addr = INADDR_ANY;
    name.sin_port = htons(whichport);
    if (bind(sock, (struct sockaddr *) &name, sizeof name) < 0) {
	perror("binding data socket");
	return 1;
    }
    name.sin_port = htons(whichport + 1);
    if (bind(ssock, (struct sockaddr *) &name, sizeof name) < 0) {
	perror("binding control socket");
	return 1;
    }
    name.sin_port = htons(whichport);

    for (i = 1; i < argc; i++) {
	char *op, opt;

	op = argv[i];
	if (*op == '-') {
	    opt = *(++op);
	    if (islower(opt)) {
		opt = toupper(opt);
	    }

	    switch (opt) {

		case 'D':	      /* -D  --  Force debug output */
		    debugforce = 1;
		    break;

		case 'V':	      /* -V  --  Show hostnames that connect */
		    showhosts = TRUE;
		    if (op[1] != 0) {
			int t = atoi(op + 1) * 1000000L;

			if (t > 0) {
			    if (t < (TickTock + 1)) {
				t = TickTock + 1;
			    }
			    hosttimeout = (t / TickTock) * TickTock;
			}
		    }
		    break;

	    }
	}
    }

    /* Find assigned port value and print it. */

    length = sizeof(name);
    if (getsockname(sock, (struct sockaddr *) &name, &length) < 0) {
	perror("getting socket name");
	return 1;
    }
#ifdef SHOW_SOCKET
    fprintf(stderr, "%s: socket port #%d\n", prog, ntohs(name.sin_port));
#endif

    /* Contact look who's listening host, if requested. */

    cp = getenv("SPEAKFREE_LWL_TELL");
    if (cp != NULL) {
	struct hostent *h;
	char md5key[16];
	char *ep, *np;
	long iadr;
	int n;

	makeSessionKey(md5key);
	bcopy(md5key, (char *) &ssrc, sizeof ssrc);
	sdesl = rtp_make_sdes(&sdes, ssrc, whichport, FALSE);
	lookhost.sin_family = AF_INET;

	while (lwl_nsites < LWL_MAX_SITES) {
	    n = lwl_nsites;
	    while (*cp != 0 && isspace(*cp)) {
		cp++;
	    }
	    if (*cp == 0) {
		break;
	    }
	    if ((np = strchr(cp, ',')) != NULL) {
		*np++ = 0;
	    }
	    lwl_ports[lwl_nsites] = Internet_Port + 2;
	    if ((ep = strchr(cp, ':')) != NULL) {
		*ep = 0;
		lwl_ports[lwl_nsites] = atoi(ep + 1);
	    }
	    if (isdigit(*cp) && (iadr = inet_addr(cp)) != -1) {
		bcopy((char *) &iadr, (char *) (&lwl_sites[lwl_nsites]),
		      sizeof iadr);
		lwl_nsites++;
	    } else {
		h = gethostbyname(cp);
		if (h != NULL) {
		    bcopy((char *) (h->h_addr), 
			  (char *) (&lwl_sites[lwl_nsites]),
			  sizeof(unsigned long));
		    lwl_nsites++;
		} else {
		    fprintf(stderr, "%s: warning, SPEAKFREE_LWL_TELL host %s unknown.\n",
			prog, cp);
		}
	    }
	    if (Debug && lwl_nsites > n) {
		fprintf(stderr, "%s: publishing on LWL server %s: %s.\n", prog,
		    inet_ntoa(lwl_sites[n]), cp);
	    }
	    if (np == NULL) {
		break;
	    }
	    cp = np;
	}
	if (lwl_nsites > 0) {
	    sendLwlMessage(FALSE);
	}
    }

    signal(SIGHUP, exiting);	      /* Set signal to handle termination */
    signal(SIGINT, exiting);	      /* Set signal to handle termination */
    signal(SIGTERM, exiting);	      /* Set signal to handle termination */
    signal(SIGALRM, release);	      /* Set signal to handle timeout */
    ularm(TockTock);		      /* Start periodic timer */

    /* Read from the socket. */

    while (TRUE) {
	int rll;
	double pktime;		      /* Time packet is to be echoed */
	fd_set fdset;
	int wsock, control;
	unsigned char *p = (unsigned char *) &sb;

	FD_ZERO(&fdset);
	FD_SET(sock, &fdset);
	FD_SET(ssock, &fdset);
	if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) <= 0) {
	    continue;
	}
	wsock = FD_ISSET(sock, &fdset) ? sock :
		    (FD_ISSET(ssock, &fdset) ? ssock : -1);
	if (wsock < 0) {
	    continue;
	}
	control = wsock == ssock;
	fromlen = sizeof(from);
	if ((rll = recvfrom(wsock, (char *) &sb, sizeof sb, 0, (struct sockaddr *) &from, &fromlen)) < 0) {
	    if (errno != EINTR) {
		perror(!control ? "receiving data packet" :
				  "receiving control packet");
	    }
	    continue;
	}
	gettimeofday(&tp, &tzp);      /* Get time packet received */
	pktime = ((tp.tv_sec + Echo_Time) - timebase) * 1000000.0 + tp.tv_usec;
#ifndef SHOW_SOCKET
	if (Debug) {
	    fprintf(stderr, "%s: %d bytes read from %s socket.\n", prog, rll,
		    control ? "control" : "data", rll);
	}
#endif

	/* See if this connection is active.  If not, initialise a new
	   connection. */

	newconn = FALSE;
	crit = TRUE;		      /* Set connection list critical section lock */
	c = conn;
	while (c != NULL) {
	    if (memcmp(&from.sin_addr, &(c->con_addr),
		       sizeof(struct in_addr)) == 0) {
		break;
	    }
	    c = c->con_next;
	}
	if (c == NULL) {
	    c = (struct connection *) malloc(sizeof(struct connection));
	    if (c != NULL) {
		struct hostent *h;

		newconn = TRUE;
		c->con_next = conn;
		c->pgpkey[0] = FALSE;
		bzero(c->keymd5, 16);
		conn = c;
		bcopy(&from.sin_addr, &(c->con_addr),
		    sizeof(struct in_addr));
		h = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr),
				  AF_INET);
		if (h == NULL) {
		    strcpy(c->con_hostname, inet_ntoa(from.sin_addr));
		} else {
		    strcpy(c->con_hostname, h->h_name);
		}
	    }
	} else if (c->con_timeout == -1) {
	    newconn = TRUE;
	}

	/* Initialise fields in connection.  Only fields which need to
	   be reinitialised when a previously idle host resumes activity
	   need be set here. */

	if (newconn) {
	    c->face_file = NULL;
	    c->face_filename[0] = 0;
	    c->face_viewer = 0;
	    c->face_stat = FSinit;
	    c->face_address = 0L;
	    c->face_retry = 0;
	    c->con_compmodes = NULL;
	    c->con_control = FALSE;
	    c->con_bye = FALSE;
	}

	if (c != NULL) {
	    /* Reset connection timeout. */
	    c->con_timeout = pktime;
	    if (newconn || c->con_bye) {
		if (showhosts) {
		    fprintf(stderr, "%s: %s %s connect\n", prog, etime(), c->con_hostname);
		}
		c->con_bye = FALSE;
	    }
	} else {
	    crit = FALSE;
	    if (clash) {
		release();
	    }
	    continue;
	}

	/* Detect BYE packets and don't retransmit them, as they'll
	   reopen the connection on the other end. */

	if (control && ((isValidRTCPpacket((unsigned char *) &sb, rll) &&
			 isRTCPByepacket((unsigned char *) &sb, rll)) ||
			(((p[0] & 0xC0) == 0) && (p[1] == 2)))) {
	    struct queuedPacket *qp;

	    if (showhosts) {
		fprintf(stderr, "%s: %s %s bye\n", prog, etime(), c->con_hostname);
	    }
	    c->con_bye = TRUE;

	    /* Discard any packets queued to this host. */

	    qp = qph;
	    while (qp != NULL) {
		if (memcmp(&(qp->where.sin_addr), &(c->con_addr),
			   sizeof(struct in_addr)) == 0) {
		    if (Debug) {
			fprintf(stderr, "%s: discarding %d bytes to %s/%d.\n",
			    prog, qph->pktlen, inet_ntoa(qph->where.sin_addr),
			    ntohs(qph->where.sin_port));
		    }
		    qp->pktlen = 0;
		}
		qp = qp->next;
	    }
	    crit = FALSE;
	    if (clash) {
		release();
	    }
	    continue;
	}

	/* Add the packet to the queue of packets awaiting
	   retransmission. */

	qp = (struct queuedPacket *) malloc(sizeof(struct queuedPacket) + rll);
	if (qp != NULL) {
	    qp->next = NULL;
	    qp->when = pktime;
	    bcopy((char *) &from, (char *) &(qp->where),
		sizeof(struct sockaddr_in));
	    qp->where.sin_port = htons(whichport + (control ? 1 : 0));
	    qp->pktlen = rll;
	    bcopy((char *) &sb, qp->pktdata, rll);
	    if (qptail == NULL) {
		qph = qptail = qp;
		ularm(Echo_Time * 1000000L);
	    } else {
		qptail->next = qp;
		qptail = qp;
	    }
	}

	c->con_control |= control;
	if (showhosts && !control) {
	    char *cmodes = compressionType(&sb, c->con_control);

	    if (c->con_compmodes != cmodes) {
		c->con_compmodes = cmodes;
		fprintf(stderr, "%s: %s sending %s.\n", prog, c->con_hostname,
			cmodes);
	    }
	}
	if (Debug) {
	    fprintf(stderr, "%s: echoing %d %s bytes from %s.\n",
		    prog, rll,
		    control ? "RTP/VAT control" :
			      compressionType(&sb, c->con_control),
		    c->con_hostname);
	}

	crit = FALSE;
	if (clash) {
	    release();
	}
    }
#ifdef MEANS_OF_EXIT
    close(sock);
    exiting();
    return 0;
#endif
}
