/*

			Speak Freely for Unix
                     Look Who's Listening Server

*/

#include "speakfree.h"
#include "version.h"

#ifdef THREADS
#include <pthread.h>
#define Lock(x) pthread_mutex_lock(&(x))
#define Unlock(x) pthread_mutex_unlock(&(x))
#define LockConn()  Lock(connLock)
#define UnlockConn() Unlock(connLock)
#else
#define LockConn()
#define UnlockConn()
#endif

#define TockTock    120 	      /* Timeout check frequency, seconds */
#define TimeoutTime 15 * 60	      /* No-response timeout interval, seconds */
#define ServiceThreadTimeout	60    /* Service thread timeout interval, seconds  */

#define MaxReplyPacket 512	      /* Maximum length of reply data */

static int sock;		      /* Our socket */
static char *prog;		      /* Program name */ 

static int lwlport = Internet_Port + 2; /* Look Who's Listening port */
static int debugging = FALSE;	      /* Debug mode enabled */
static int verbose = FALSE;	      /* Show connections/disconnects */
static int prolix = FALSE;	      /* Extremely verbose (show queries) */
#ifdef HEXDUMP
static int hexdump = FALSE;	      /* Dump received packets in hex ? */
#endif
static char *htmlFile = NULL;	      /* HTML file name base */
static char *htmlPrivateFile = NULL;  /* HTML private directory file name base */
static int htmlTime = 1 * 60;	      /* HTML file update time */
static time_t htmlLast = 0;	      /* HTML last update time */
static int htmlChange = TRUE;	      /* Change since last HTML update ? */
static int htmlRefresh = 0;	      /* HTML client-pull refresh interval */
#define HTML_REFRESH    "<meta http-equiv=\"Refresh\" content=\"%d\">\n"
static char *message = NULL;	      /* Server information message */
static int messagel;		      /* Length of server information message */

#ifdef THREADS

#define ForwarderSleep	30	      /* How often to update forwarded hosts */
#define ForwarderMaxQ	15	      /* Maximum items in forward queue */

struct lwl_queueitem {
    struct lwl_queueitem *next;       /* Next item in LWL queue */
    int q_rll;			      /* Length of message */
    char q_pkt[2];		      /* Message body */
};

struct forwarder {
    struct forwarder *next;	      /* Next forwarder in chain */
    int status; 		      /* Site status (0 = normal) */
    char *sitename;		      /* Site name specification */
    struct in_addr site;	      /* Internet address of target host */
    long port;			      /* Destination port on that host */
    pthread_t thread;		      /* ID of forwarder thread */
    int queuelen;		      /* Length of queue for forwarder */
    struct lwl_queueitem *head, *tail;/* Queue of messages to be forwarded */
};

static pthread_attr_t detached;       /* Attributes for our detached threads */
static pthread_mutex_t fwdlistLock = PTHREAD_MUTEX_INITIALIZER;
static struct forwarder *fwdlist = NULL; /* List of forwarding destinations */

#define lwl_nsites 1		      /* Threaded version driven from list */

struct ballOstring {
    struct ballOstring *next, *prev;  /* Forward and back queue links */
    pthread_t thread;		      /* Service thread ID */
    time_t startTime;		      /* When did thread start ? */
    struct in_addr site;	      /* Internet address of requesting site */
    struct service_arg *sa;	      /* Service thread argument */
};

static struct ballOstring bshead;     /* Ball of string queue head */
static pthread_mutex_t bsLock = PTHREAD_MUTEX_INITIALIZER;

#else
static struct sockaddr_in lookhost;   /* Look who's listening host, if any */
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 */
#endif

struct lwl {
    struct lwl *next;		      /* Next connection */
    time_t ltime;		      /* Time of last update */
    unsigned long ssrc; 	      /* Session source descriptor */
    long naddr; 		      /* Internet address */
    short port; 		      /* Port address */
    char *cname;		      /* Canonical name */ 
    char *name; 		      /* User name */
    char *email;		      /* Electronic mail address */
    char *phone;		      /* Telephone number */
    char *loc;			      /* Geographic location */
    char *tool; 		      /* Application name */
};

#ifdef THREADS
				      /* Connection chain critical section */
static pthread_mutex_t connLock = PTHREAD_MUTEX_INITIALIZER;
#endif
static struct lwl *conn = NULL;       /* Chain of current connections */

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

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

    time(&clock);
    if (gmt) {
	t = gmtime(&clock);
    } else {
	t = localtime(&clock);
    }
    sprintf(s, "%d-%02d-%02d %02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
	       t->tm_hour, t->tm_min);
    return s;
}

/*  ESTIME  --	Edit short time.  */

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

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

/*  DUPSDESITEM  --  Make a copy of an SDES item and advance the pointer
		     past it.  */

static char *dupSdesItem(cp)
  char **cp;
{
    char *ip = *cp, *bp;
    int l = ip[1] & 0xFF;

    bp = malloc(l + 1);
    if (bp != NULL) {
	bcopy(ip + 2, bp, l);
	bp[l] = 0;
    }
    *cp = ip + l + 2;
    return bp;
}

/*  DESTROYLWL	--  Release storage associated with an LWL list item.  */

static void destroyLwl(lw)
  struct lwl *lw;
{
    if (lw->cname != NULL) {
	free(lw->cname);
    }
    if (lw->name != NULL) {
	free(lw->name);
    }
    if (lw->email != NULL) {
	free(lw->email);
    }
    if (lw->phone != NULL) {
	free(lw->phone);
    }
    if (lw->loc != NULL) {
	free(lw->loc);
    }
    if (lw->tool != NULL) {
	free(lw->tool);
    }
    free(lw);
}

/*  DUMPLWL  --  Dump an LWL on the specified stream.  */

static void dumpLwl(fo, lw)
  FILE *fo;
  struct lwl *lw;
{
    struct sockaddr_in s;

    s.sin_addr.s_addr = lw->naddr;
    fprintf(fo, "SSRC = %lX IP = %s Port %d %s", lw->ssrc, inet_ntoa(s.sin_addr),
		lw->port, ctime(&(lw->ltime)));
    if (lw->cname != NULL) {
        fprintf(fo, "    CNAME = %s\n", lw->cname);
    }
    if (lw->name != NULL) {
        fprintf(fo, "     NAME = %s\n", lw->name);
    }
    if (lw->email != NULL) {
        fprintf(fo, "    EMAIL = %s\n", lw->email);
    }
    if (lw->phone != NULL) {
        fprintf(fo, "    PHONE = %s\n", lw->phone);
    }
    if (lw->loc != NULL) {
        fprintf(fo, "      LOC = %s\n", lw->loc);
    }
    if (lw->tool != NULL) {
        fprintf(fo, "     TOOL = %s\n", lw->tool);
    }
}

#ifdef NEEDED

/*  DUMPLWLCHAIN  --  Dump all current connections.  */

static void dumpLwlChain(fo)
  FILE *fo;
{
    struct lwl *lw;

    LockConn();
    lw = conn;
    fprintf(fo, "\n==========  %s  ==========\n", etime(FALSE));
    while (lw != NULL) {
        fprintf(fo, "\n");
	dumpLwl(fo, lw);
	lw = lw->next;
    }
    UnlockConn();
}
#endif

/*  LOGLWL  --	Generate log entry for LWL event.  */

static void logLwl(lw, event)
  struct lwl *lw;
  char *event;
{
    char ipport[40], deef[256];
    struct sockaddr_in u;

    u.sin_addr.s_addr = lw->naddr;
    sprintf(ipport, "%s:%d", inet_ntoa(u.sin_addr), lw->port);
    sprintf(deef, "%s %s", ipport, lw->email ? lw->email : lw->cname);
    if (lw->name) {
        sprintf(deef + strlen(deef), " (%s)", lw->name);
    }
    printf("%s %s%s\n", estime(), event, deef);
}

/*  GARDOL  -- Validity-check an RTCP packet to make sure we don't
	       crash if some bozo sends us garbage.  */

static int gardol(p, len)
  unsigned char *p;
  int len;
{
    unsigned char *end;

    if ((((p[0] >> 6) & 3) != RTP_VERSION) ||	   /* Version incorrect ? */
	((p[0] & 0x20) != 0) || 		   /* Padding in first packet ? */
	((p[1] != RTCP_SR) && (p[1] != RTCP_RR) &&
	 (p[1] != RTCP_SDES) && (p[1] != RTCP_BYE) &&
	 (p[1] != RTCP_APP))) { 		   /* Item type valid ? */
	return FALSE;
    }
    end = p + len;

    do {
	/* Advance to next subpacket */
	p += (ntohs(*((short *) (p + 2))) + 1) * 4;
    } while (p < end && (((p[0] >> 6) & 3) == RTP_VERSION));

    return p == end;
}

/*  MAKEHTML  --  Create an HTML file showing current connections.  If
                  "private" is set, exact-match names are included
		  in the HTML file; this allows creation of a non-exported
                  active site file for the system manager's use.  */

static void makeHTML(fname, private)
  char *fname;
  int private;
{
    FILE *of;
    char f[132], fn[132];

    strcpy(f, fname);
    strcat(f, ".new");
    of = fopen(f, "w");
    if (of != NULL) {
	struct lwl *lw;

#define P(x) fprintf(of, x)

	LockConn();
	lw = conn;
        P("<html>\n<head>\n");
	if (htmlRefresh > 0) {
	    fprintf(of, HTML_REFRESH, htmlRefresh);
	}
        P("<title>\nSpeak Freely: Active Sites\n");
        P("</title>\n</head>\n\n<body>\n<center>\n<h1>");
        P("Speak Freely: Active Sites</h1>\n<h2>");
        fprintf(of, "As of %s UTC</h2>\n</center>\n<p>\n", etime(TRUE));

	if (lw == NULL) {
            P("<h2>No sites active.</h2>\n");
	} else {
	    int i = 0;

	    while (lw != NULL) {
                if (private || ((lw->email == NULL || lw->email[0] != '*') &&
                    (lw->cname[0] != '*'))) {
		    i++;
		}
		lw = lw->next;
	    }
	    lw = conn;
	    if (i == 0) {
		/* Fib about number of sites active if all active sites
                   don't want a directory listing. */
                P("<h2>No sites active.</h2>\n");
	    } else {
                fprintf(of, "<h2>%d site%s active.</h2>\n",
                    i, i == 1 ? "" : "s");
                P("<pre>\n");
		while (lw != NULL) {
                    if (private || ((lw->email == NULL || lw->email[0] != '*') &&
                        (lw->cname[0] != '*'))) {
			char ipport[40];
			struct tm *lt;
			struct sockaddr_in u;
			char s[384];

			lt = gmtime(&lw->ltime);
			u.sin_addr.s_addr = lw->naddr;
                        sprintf(ipport, "%s:%d", inet_ntoa(u.sin_addr), lw->port);
                        sprintf(s, "\n%-24s %-48s %02d:%02d\n", ipport, lw->cname,
			    lt->tm_hour, lt->tm_min);
			outHTML(of, s);
			if (lw->name != NULL) {
                            sprintf(s, "%25s%s\n", "", lw->name);
			    outHTML(of, s);
			}
			if (lw->loc != NULL) {
                            sprintf(s, "%25s%s\n", "", lw->loc);
			    outHTML(of, s);
			}
			if (lw->phone != NULL) {
                            sprintf(s, "%25sPhone:  %s\n", "", lw->phone);
			    outHTML(of, s);
			}
			if (lw->email != NULL) {
                            sprintf(s, "%25sE-mail: %s\n", "", lw->email);
			    outHTML(of, s);
			}
		    }
		    lw = lw->next;
		}
                P("</pre>\n");
	    }
	}
	UnlockConn();

        P("</body>\n</html>\n");
	fclose(of);
	strcpy(fn, fname);
        strcat(fn, ".html");
	rename(f, fn);
	if (debugging) {
            fprintf(stderr, "%s: updated %s\n", prog, fn);
	}
    }
}

/*  UPDHTML  --  Update HTML if necessary.  */

static void updHTML()
{
    time_t now;

    if (((htmlFile != NULL) || (htmlPrivateFile != NULL)) && htmlChange && 
	((htmlTime <= 0) || (((now = time(NULL)) - htmlLast) > htmlTime))) {
	htmlLast = now;
	htmlChange = FALSE;
	if (htmlFile != NULL) {
	    makeHTML(htmlFile, FALSE);
	}
	if (htmlPrivateFile != NULL) {
	    makeHTML(htmlPrivateFile, TRUE);
	}
    }
}

/*  CHANGED  --  Indicate a change which may require updating
		 the HTML file.  */

static void changed()
{
    htmlChange = TRUE;
#ifndef THREADS
    updHTML();
#endif
}

/*  LCASE  --  Convert a string to lower case.	*/

static void lcase(s)
  char *s;
{
    while (*s) {
	if (isupper(*s)) {
	    *s = tolower(*s);
	}
	s++;
    }
}

#ifdef THREADS

/*  FORWARDER_THREAD  --  Thread which manages forwarding to a given
			  destination.	*/

static void *forwarder_thread(arg)
  void *arg;
{
    struct forwarder *f = (struct forwarder *) arg;
    struct hostent *h;
    long iadr;

#ifdef DBTHREAD
fprintf(stderr, "Started forwarder thread for %s\n", f->sitename);
#endif
    if (isdigit(f->sitename[0]) && (iadr = inet_addr(f->sitename)) != -1) {
	bcopy((char *) &iadr, (char *) (&(f->site)),
	      sizeof iadr);
	f->status = 0;
    } else {
	h = gethostbyname(f->sitename);
	if (h != NULL) {
	    bcopy((char *) (h->h_addr), 
		  (char *) (&(f->site)),
		  sizeof(unsigned long));
	    f->status = 0;
	} else {
            fprintf(stderr, "%s: warning, forward destination %s unknown.\n",
		prog, f->sitename);
	    f->status = -2;
	    return NULL;
	}
    }
    if (debugging && f->status == 0) {
        fprintf(stderr, "%s: forwarding to LWL server %s: %s.\n", prog,
	    inet_ntoa(f->site), f->sitename);
    }

    while (f->status == 0) {
	struct lwl_queueitem *q;
	int sock = -1;

	while ((q = f->head) != NULL) {
	    int cstat;
	    struct sockaddr_in lookhost;

	    sock = socket(AF_INET, SOCK_STREAM, 0);
	    if (sock < 0) {
                perror("opening forwarding socket");
		break;
	    }

	    lookhost.sin_port = htons(f->port);
	    bcopy((char *) (&(f->site)), (char *) &lookhost.sin_addr.s_addr,
		  sizeof lookhost.sin_addr.s_addr);

	    errno = 0;
	    do {
		cstat = connect(sock, (struct sockaddr *) &(lookhost), sizeof lookhost);
		if (cstat >= 0) {
		    break;
		}
	    } while (errno == EINTR);
	    if (cstat >= 0) {
		if (send(sock, q->q_pkt, q->q_rll, 0) < 0) {
                    perror("forwarding look who's listening source ID message");
		    break;
		} else {
		    Lock(fwdlistLock);
		    f->head = q->next;
		    if (f->head == NULL) {
			f->tail = NULL;
		    }
		    free(q);
		    f->queuelen--;
		    if (f->queuelen < 0) {
                        fprintf(stderr, "%s: forward queue underflow to %s.\n", 
			    prog, f->sitename);
			f->queuelen = 0;
		    }
		    Unlock(fwdlistLock);
		    if (debugging) {
                        fprintf(stderr, "%s: forwarded packet to server %s. Queue length now %d.\n",
			    prog, f->sitename, f->queuelen);
		    }
		}
	    } else {

		/* As LWL servers go up and down, failures to connect
		   to forwarding destinations are not uncommon.  Further,
		   while a server is down, we get here on every packet
		   we attempt to forward.  To avoid blithering all
		   over the log, we only note such failure if in
		   prolix or debugging mode. */

		if (prolix || debugging) {
                    perror("connecting forwarding socket");
		}
		break;
	    }
	    close(sock);
	    sock = -1;
	}

	/* If we bailed out of the above loop due to an error in
	   forwarding the packet, make sure the socket is closed. */

	if (sock >= 0) {
	    close(sock);
	    sock = -1;
	}
	sleep(ForwarderSleep);
    }

#ifdef DBTHREAD
fprintf(stderr, "Exited forwarder thread for %s\n", f->sitename);
#endif
    f->status = -1;		      /* Forwarder terminated */
    return NULL;
}
#endif

/*  FORWARDLIST  --  Parse a list of servers to forward packets to. */

static void forwardList(cp)
  char *cp;
{
#ifdef THREADS
    struct forwarder *f;
    char *ep, *np;

    while (TRUE) {
	while (*cp != 0 && isspace(*cp)) {
	    cp++;
	}
	if (*cp == 0) {
	    break;
	}
        if ((np = strchr(cp, ',')) != NULL) {
	    *np++ = 0;
	}
	f = (struct forwarder *) malloc(sizeof(struct forwarder));
	if (f != NULL) {
	    bzero(f, sizeof(struct forwarder));
	    f->status = 1;
	    f->sitename = cp;
	    f->port = Internet_Port + 2;
            if ((ep = strchr(cp, ':')) != NULL) {
		*ep = 0;
		f->port = atoi(ep + 1);
	    }
	    f->next = fwdlist;
	    fwdlist = f;
	    if (pthread_create(&(f->thread), &detached, forwarder_thread, f) != 0) {
                fprintf(stderr, "%s: Cannot launch forwarder thread for %s: %s",
		    prog, cp, strerror(errno));
		free(f);
	    }
	} else {
            fprintf(stderr, "%s: out of memory allocating forwarder for %s\n",
		prog, cp);
	    break;
	}
	if (np == NULL) {
	    break;
	}
	cp = np;
    }
#else
    struct hostent *h;
    char *ep, *np;
    long iadr;
    int n;

    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, forward destination %s unknown.\n",
		    prog, cp);
	    }
	}
	if (debugging && lwl_nsites > n) {
            fprintf(stderr, "%s: forwarding to LWL server %s: %s.\n", prog,
		inet_ntoa(lwl_sites[n]), cp);
	}
	if (np == NULL) {
	    break;
	}
	cp = np;
    }
#endif
}

/*  FORWARDLWLMESSAGE  --  If forwarding is enabled, pass this message
			   on to hosts in our forward list, changing the
			   packet protocol header to indicate the packet
			   was forwarded and appending the IP address
			   of the originating host to the end of the packet. */

static void forwardLwlMessage(zp, rll, addr)
  char *zp;			/* Packet address */
  int rll;			/* Total packet length (starting at zp) */
  struct in_addr addr;		/* IP address of originator of this packet */
{
#ifndef THREADS
    int i, sock;
#endif

    zp[0] |= 0x40;		/* Set forwarded packet flag bit */
    bcopy(&addr, zp + rll, sizeof(struct in_addr));
    rll += sizeof(struct in_addr);
#ifdef THREADS
    {
	struct forwarder *f = fwdlist;

	while (f != NULL) {
	    if (f->status == 0) {
		if (f->queuelen > ForwarderMaxQ) {
                    fprintf(stderr, "%s: %s queue too long; forward message discarded.\n",
			prog, f->sitename);
		} else {
		    struct lwl_queueitem *q = (struct lwl_queueitem *) malloc(
			sizeof(struct lwl_queueitem) + rll);

		    if (q != NULL) {
			q->next = NULL;
			q->q_rll = rll;
			bcopy(zp, q->q_pkt, rll);
			Lock(fwdlistLock);
			f->queuelen++;
			if (f->head == NULL) {
			    f->head = f->tail = q;
			} else {
			    f->tail->next = q;
			    f->tail = q;
			}
			Unlock(fwdlistLock);
		    }
		}
	    }
	    f = f->next;
	}
    }
#else
    for (i = 0; i < lwl_nsites; i++) {
	if (lwl_ports[i] >= 0) {
	    int cstat;

	    sock = socket(AF_INET, SOCK_STREAM, 0);
	    if (sock < 0) {
                perror("opening forwarding socket");
		continue;
	    }

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

	    errno = 0;
	    do {
		cstat = connect(sock, (struct sockaddr *) &(lookhost), sizeof lookhost);
		if (cstat >= 0) {
		    break;
		}
	    } while (errno == EINTR);
	    if (cstat >= 0) {
		if (send(sock, zp, rll, 0) < 0) {
                    perror("forwarding look who's listening source ID message");
		} else {
		    if (debugging) {
                        fprintf(stderr, "%s: forwarded packet to server %s.\n",
			    prog, inet_ntoa(lookhost.sin_addr));
		    }
		}
	    } else {

		/* As LWL servers go up and down, failures to connect
		   to forwarding destinations are not uncommon.  Further,
		   while a server is down, we get here on every packet
		   we attempt to forward.  To avoid blithering all
		   over the log, we only note such failure if in
		   prolix or debugging mode. */

		if (prolix || debugging) {
                    perror("connecting forwarding socket");
		}
	    }
	    close(sock);
	}
    }
#endif
}

/*  QUERYMATCH	--  Determing if query matches a given item.  */

static int queryMatch(q, l)
  char *q;
  struct lwl *l;
{
    char ts[1024];
    int exact = FALSE;
    char *s;

    s = ts;

    /* We give preference to an E-mail address, if given, to a
       canonical address since it's the user's best known published
       identity on the net.  This, of course, runs the risk of
       spoofing, but since anybody can send us an RTCP packet with
       whatever cname they like, there's no added security in insisting
       on using it. */

    strcpy(s, (l->email == NULL) ? l->cname : l->email);
    lcase(s);

    if (*q == '*') {
	exact = TRUE;
	q++;
    }
    if (*s == '*') {
	exact = TRUE;
	s++;
    }
    /* Even if we're using the E-mail name, allow an asterisk on the
       canonical name to require an exact match. */
    if (l->cname[0] == '*') {
	exact = TRUE;
    }

    if (exact) {
	return strcmp(s, q) == 0;
    }

    if (strstr(s, q)) {
	return TRUE;
    }
    if (l->name != NULL) {
	strcpy(s, l->name);
	lcase(s);
	if (strstr(s, q)) {
	   return TRUE;
	}
    }
    return FALSE;
}

/*  RELEASE  --  Check for connections who haven't send us an update
		 once in the timeout interval and close them.  They
		 probably went away without having the courtesy to
		 say good-bye.	*/

static void release()
{
    struct lwl *lw, *llr = NULL, *lf;
    time_t now = time(NULL);

    LockConn();
    lw = conn;
    while (lw != NULL) {
	if ((now - lw->ltime) > TimeoutTime) {
	    lf = lw;
	    if (llr == NULL) {
		conn = lw->next;
		lw = conn;
	    } else {
		llr->next = lw->next;
		lw = lw->next;
	    }
/*
fprintf(stderr, "\nTiming out:\n");
dumpLwl(stderr, lf);
*/
	    if (verbose) {
                logLwl(lf, "Timeout: ");
	    }
	    htmlChange = TRUE;
	    destroyLwl(lf);
	} else {
	    llr = lw;
	    lw = lw->next;
	}
    }
    UnlockConn();
#ifndef THREADS
    updHTML();                        /* Update HTML if something's changed recently */
    signal(SIGALRM, release);	      /* Reset signal to handle timeout (Sys V) */
    alarm(TockTock);		      /* Reset the timer */
#endif
}

/*  PLUMBER  --  Catch SIGPIPE signal which occurs when remote user
		 disconnects before we try to send the reply.  */

static void plumber()
{
#ifdef DBTHREAD
fprintf(stderr, "Caught SIGPIPE--continuing.\n");
#endif
    signal(SIGPIPE, plumber);	      /* Reset signal just in case */
}

/*  EXITING  --  Release socket in case of termination.  */

static void exiting()
{
#ifdef DBTHREAD
fprintf(stderr, "Exiting.\n");
#endif
    shutdown(sock, 2);
    close(sock);
    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: Look Who's Listening Server.\n", prog);
#ifdef THREADS
    V fprintf(stderr, "               (Multi-threaded version)\n");
#endif
    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         Debugging output on stderr\n");
    V fprintf(stderr, "           -Fserv,... Forward to listed servers\n");
    V fprintf(stderr, "           -Hpath     Write HTML status on base path\n");
    V fprintf(stderr, "           -Insec     Interval between HTML updates\n");
    V fprintf(stderr, "           -Mfile     Load server message from file\n");
    V fprintf(stderr, "           -Pport     Listen on given port\n");
    V fprintf(stderr, "           -Rnsec     Client-pull HTML refresh every nsec seconds\n");
    V fprintf(stderr, "           -U         Print this message\n");
    V fprintf(stderr, "           -V[V]      List connections and disconnects [-VV: all packets]\n");
#ifdef HEXDUMP
    V fprintf(stderr, "           -X         Dump packets in hex\n");
#endif
    V fprintf(stderr, "           -Zpath     Write HTML private directory on base path\n");
    V fprintf(stderr, "\n");
    V fprintf(stderr, "by John Walker\n");
    V fprintf(stderr, "   http://www.fourmilab.ch/\n");
}

/*  ADD_SDES_ITEM  --  Include an SDES item in a reply packet,
		       pruning illegal characters and content,
		       checking for packet overflow, and updating
		       the packet pointer.  */

#define addSDES(item, text) add_sdes_item(item, text, &ap)

static void add_sdes_item(item, text, app)
  int item;
  char *text;
  char **app;
{
    int l, state;
    char prune[258];
    char *ap = *app, *hp, *op = prune;

    /* Copy the text to the "prune" buffer using a simple
       state machine parser, filtering extraneous
       information. */

    state = 0;

    while (*text) {
	unsigned char ch = (unsigned char) *text++;

	if (item == RTCP_SDES_PRIV && op == prune) {
	    /* Allow PRIV item binary prefix field to pass. */
	    *op++ = (char) ch;
	    continue;
	}

	/* Discard non-graphic characters (ISO 8859 standard). */

        if (!isspace(ch) && (ch < ' ' || (ch > '~' && ch < 161))) {
	    continue;
	}

	switch (state) {

	    case 0:		      /* Skipping initial white space */
		if (!isspace(ch)) {
		    state = 1;
		} else {
		    break;
		}
		/* Note fall-through */

	    case 1:		      /* Transcribing to output */
s1:             if (ch == '<') {
                    if ((hp = strchr(text, '>')) != NULL) {
			text = hp + 1;
			if (op > prune && isspace(op[-1])) {
			    state = 2;
			}
			continue;
		    }
		}
		if (isspace(ch)) {
                    ch = ' ';         /* Convert all white space into blanks */
		    state = 2;
		}
		*op++ = (char) ch;
		break;

	    case 2:		      /* Collapsing multiple white space */
		if (!isspace(ch)) {
		    state = 1;
		    goto s1;
		}
		break;
	}
    }
    *op = 0;

    /* Drop trailing blanks. */

    while (strlen(prune) > 0 && isspace(op[-1])) {
	*(--op) = 0;
    }

    l = strlen(prune);
    if (l == 0) {
	return;
    } else if (l > 48) {
	int n;

	/* String is too long.	Try to trim it at a word boundary,
	   but if that fails just brutally lop it off. */

	for (n = 47; n > 31; n--) {
	    if (isspace(prune[n])) {
		prune[n] = 0;
		n = 0;
		break;
	    }
	}
	if (n > 0) {
	    prune[47] = 0;
	}
    }

    *ap++ = item;

    *ap++ = l = strlen(prune);
    bcopy(prune, ap, l);
    ap += l;

    *app = ap;
}

/*  SERVICEPACKET  --  Process a packet from a connection.  */

static void servicePacket(csock, from)
  int csock;
  struct sockaddr_in from;	 /* Sending host address */
{
    int rll, forward, pvalid;
    char zp[1024];
    rtcp_t *rp;
    short srp;
    struct lwl *lw;		  /* Parsed packet */
    char *p, *pend;
    struct sockaddr_in fwdh;

    forward = FALSE;
    rll = recv(csock, zp, sizeof zp, 0);
#ifdef HEXDUMP
    if (hexdump) {
        fprintf(stderr, "%s: %d bytes read from socket.\n", prog, rll);
	xd(stderr, zp, rll, TRUE);
    }
#endif

    /* If packet was forwarded from another server, extract the
       embedded original source address and make the packet
       look like it came directly from the sender. */

    if (rll > 4 && (zp[0] & 0xC0) == 0xC0) {
	forward = TRUE;
	zp[0] &= ~0x40;
	bcopy(zp + (rll - 4), &fwdh.sin_addr, 4);
	rll -= 4;
	if (debugging) {
            fprintf(stderr, "%s: received forwarded packet from %s.\n",
		prog, inet_ntoa(from.sin_addr));
	}
	from.sin_addr = fwdh.sin_addr ;
    }

    /* Some version of Speak Freely for Windows don't always
       pad request packets to a multiple of four bytes.  If
       this is the case, insert the pad ourselves. */

    while (((zp[1] & 0xFF) == RTCP_APP) && ((rll & 3) != 0)) {
	zp[rll++] = 0;
    }

    /* Validate this packet.  If it fails the validity check,
       ignore it do it can't crash the code below. */

    if (!gardol((unsigned char *) zp, rll)) {
	if (debugging) {
            fprintf(stderr, "%s: discarded invalid RTCP packet from %s.\n",
		prog, inet_ntoa(from.sin_addr));
	}
	close(csock);
	return;
    }

    /* Walk through the individual items in a possibly composite
       packet until we locate an item we're interested in.  This
       allows us to accept packets that comply with the RTP standard
       that all RTCP packets begin with an SR or RR.  */

    p = zp;
    pend = p + rll;
    pvalid = FALSE;

    while ((p < pend) && (p[0] >> 6 & 3) == RTP_VERSION) {
	int pt = p[1] & 0xFF;

	if (pt == RTCP_SDES || pt == RTCP_BYE || pt == RTCP_APP) {
	    pvalid = TRUE;
	    break;
	}
	/* If not of interest to us, skip to next subpacket. */
	p += (ntohs(*((short *) (p + 2))) + 1) * 4;
    }

    if (!pvalid) {
	if (debugging) {
            fprintf(stderr, "%s: discarded RTCP packet with unknown payload from %s.\n",
		prog, inet_ntoa(from.sin_addr));
	}
	close(csock);
	return;
    }

    /* Examine the packet to see what kind of request it is.
       Note that since all the packets we're interested in can
       be parsed without knowing their actual length in bytes, we
       can ignore the possible presence of padding. */

    rp = (rtcp_t *) p;
    srp = ntohs(*((short *) p));
    if (
#ifdef RationalWorld
	rp->common.version == RTP_VERSION && /* Version ID correct */
	rp->common.count == 1
#else
	(((srp >> 14) & 3) == RTP_VERSION) &&
	(((srp >> 8) & 0x1F) == 1)
#endif
       ) {

	switch (
#ifdef RationalWorld
		rp->common.pt
#else
		srp & 0xFF
#endif
	       ) {

	    /*	SDES packet.  This is a notification by a listening
                              that it's just started or is still
			      listening.  If the host was previously
			      active we replace the old parameters
			      with the new in case something has
			      changed.	The timeout is reset.  Identity
			      of host is by canonical name, not SSRC,
			      since the host may have restarted with
			      a new SSRC without having sent us a BYE.	*/

	    case RTCP_SDES:

		/* Parse the fields out of the SDES packet. */

		{
		    char *cp = (char *) rp->r.sdes.item,
			 *lp = cp + (ntohs(rp->common.length) * 4);
		    struct lwl *lr, *llr;

		    lw = (struct lwl *) malloc(sizeof(struct lwl));
		    if (lw != NULL) {
			bzero((char *) lw, sizeof(struct lwl));

			lw->ssrc = rp->r.sdes.src;
			lw->naddr = from.sin_addr.s_addr;
			lw->port = from.sin_port;
			lw->ltime = time(NULL);
			if (prolix) {
                            printf("%s: %s SDES %08lX\n", prog,
				inet_ntoa(from.sin_addr), lw->ssrc);
			}
			while (cp < lp) {
			    switch ((*cp) & 0xFF) {

				case RTCP_SDES_CNAME:
				    lw->cname = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_NAME:
				    lw->name = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_EMAIL:
				    lw->email = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_PHONE:
				    lw->phone = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_LOC:
				    lw->loc = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_TOOL:
				    lw->tool = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_PRIV:
				    {
					char *zp = dupSdesItem(&cp);

					if (zp != NULL) {
					    if (zp[0] == 1 &&
                                                zp[1] == 'P') {
						lw->port = atoi(zp + 2);
					    }
					    free(zp);
					}
				    }
				    break;

				case RTCP_SDES_END:
				    cp = lp;
				    break;

				default:
				    {
					char *zp = dupSdesItem(&cp);

					if (zp != NULL) {
					    free(zp);
					}
				    }
				    break;
			    }
			}
			if (debugging) {
			    dumpLwl(stderr, lw);
			}

			/* Search chain and see if a user with this
			   name is already know.  If so, replace the
			   entry with this one. */

			if (lw->cname != NULL) {
			    lr = conn;
			    llr = NULL;

			    while (lr != NULL) {
				char *p = lw->cname, *q = lr->cname;

                                if (*p == '*') {
				    p++;
				}
                                if (*q == '*') {
				    q++;
				}
				if (strcmp(p, q) == 0) {
				    lw->next = lr->next;
				    if (llr == NULL) {
					conn = lw;
				    } else {
					llr->next = lw;
				    }
				    destroyLwl(lr);
				    lw = NULL;
				    break;
				}
				llr = lr;
				lr = lr->next;
			    }

                            /* If we didn't find an entry already in the
			       chain, link in the new entry.  */

			    if (lw != NULL) {
				lw->next = conn;
				conn = lw;
				if (verbose) {
                                    logLwl(lw, "Connect: ");
				}
				changed();
			    }
			    if (!forward && lwl_nsites > 0) {
				forwardLwlMessage(zp, rll, from.sin_addr);
			    }
			} else {
			    /* Bogus item with no CNAME -- discard. */
			    if (debugging || verbose) {
                                fprintf(stderr, "Bogus SDES with no CNAME.\n");
				if (!debugging) {
				    dumpLwl(stderr, lw);
				}
			    }
			    destroyLwl(lw);
			}
		    }
		}
		break;

	    /*	BYE packet.  This is sent when a listening host is
			     ceasing to listen in an orderly manner.
			     Identity here is by SSRC, since the
			     host is assumed to have properly announced
                             itself.  Besides, that's the only ID in
			     the RTCP BYE packet, so it had darned
			     well be sufficient.  */

	    case RTCP_BYE:
		{
		    struct lwl *lr = conn, *llr = NULL;

		    if (prolix) {
                        printf("%s: %s BYE %08lX\n", prog,
			    inet_ntoa(from.sin_addr), rp->r.bye.src[0]);
		    }
		    while (lr != NULL) {
			if (rp->r.bye.src[0] == lr->ssrc) {
			    if (llr == NULL) {
				conn = lr->next;
			    } else {
				llr->next = lr->next;
			    }
			    if (debugging) {
                                fprintf(stderr, "Releasing:\n");
				dumpLwl(stderr, lr);
			    }
			    if (verbose) {
                                logLwl(lr, "Bye:     ");
			    }
			    changed();
			    destroyLwl(lr);
			    break;
			}
			llr = lr;
			lr = lr->next;
		    }
		    if (!forward && lwl_nsites > 0) {
			forwardLwlMessage(zp, rll, from.sin_addr);
		    }
		}
		break;

	    /*	Application request packets.  The following application
		extensions implement the various queries a client can make
		regarding the current state of the listening host list.  */

	    case RTCP_APP:
		if (prolix) {
                    printf("%s: %s APP %.4s\n", prog,
			inet_ntoa(from.sin_addr), p + 8);
		}

		/*  SFlq  --  Pattern match in cname and name and
			      return matches, up to the maximum
			      packet size.  If either the query string
			      or the canonical name begins with an
			      asterisk, the asterisk(s) is(/are) ignored
			      and a precise match with the canonical
			      name is required.  */

                if (bcmp(p + 8, "SFlq", 4) == 0) {
		    struct lwl *lr = conn;
		    char b[1500];
		    rtcp_t *rp = (rtcp_t *) b;
		    char *ap, *pap;
		    int l, scandex = 0;

#ifdef RationalWorld
		    rp->common.version = RTP_VERSION;
		    rp->common.p = 0;
		    rp->common.count = 0;
		    rp->common.pt = RTCP_SDES;
#else
		    *((short *) rp) = htons((RTP_VERSION << 14) |
					     RTCP_SDES | (0 << 8));
#endif

		    ap = (char *) &(rp->r.sdes.src);

		    if (prolix) {
                        printf("%s: %s query \"%s\"\n", prog, inet_ntoa(from.sin_addr), p + 12);
		    }
		    lcase(p + 12);
		    while (lr != NULL) {
			if (queryMatch(p + 12, lr)) {
			    char s[20];

			    pap = ap;
			    *((unsigned long *) ap) = lr->ssrc;
			    ap += sizeof(unsigned long);
			    addSDES(RTCP_SDES_CNAME, lr->cname +
                                (lr->cname[0] == '*' ? 1 : 0));

			    if (lr->name != NULL) {
				addSDES(RTCP_SDES_NAME, lr->name);
			    }

			    if (lr->email != NULL) {
				addSDES(RTCP_SDES_EMAIL, lr->email +
                                  (lr->email[0] == '*' ? 1 : 0));
			    }

			    if (lr->phone != NULL) {
				addSDES(RTCP_SDES_PHONE, lr->phone);
			    }

			    if (lr->loc != NULL) {
				addSDES(RTCP_SDES_LOC, lr->loc);
			    }

			    if (lr->tool != NULL) {
				addSDES(RTCP_SDES_TOOL, lr->tool);
			    }

                            sprintf(s, "\001P%d", lr->port);
			    addSDES(RTCP_SDES_PRIV, s);

			    {
				struct sockaddr_in u;

				u.sin_addr.s_addr = lr->naddr;
                                sprintf(s, "\001I%s", inet_ntoa(u.sin_addr));
				addSDES(RTCP_SDES_PRIV, s);
			    }

                            sprintf(s, "\001T%lu", lr->ltime);
			    addSDES(RTCP_SDES_PRIV, s);

#ifdef NEEDED
                            /* If we're over the packet size limit,
                               let the user know there's more to be
			       retrieved starting at the given offset. */

			    if ((ap - b) > MaxReplyPacket) {
                                sprintf(s, "\001M%d", scandex);
				addSDES(RTCP_SDES_PRIV, s);
			    }
#endif
			    *ap++ = RTCP_SDES_END;

			    /* Pad to next 32 bit boundary. */

			    while (((ap - b) & 3) != 0) {
				*ap++ = RTCP_SDES_END;
			    }
			    if ((ap - b) > MaxReplyPacket) {
				ap = pap;
				break;
			    }
#ifdef RationalWorld
			    rp->common.count++;
#else
			    (((unsigned char *) rp)[0])++;
#endif
			}
			lr = lr->next;
			scandex++;
		    }

		    l = ap - b;

		    rp->common.length = htons(((l + 3) / 4) - 1);
		    l = (ntohs(rp->common.length) + 1) * 4;
#ifdef HEXDUMP
		    if (hexdump) {
                        fprintf(stderr, "%s: %d bytes sent to socket.\n", prog, l);
			xd(stderr, b, l, TRUE);
		    }
#endif
		    if (send(csock, b, l, 0) < 0) {
                        perror("sending query match reply");
		    }
		}

                /*  SFms  --  Retrieve the server's information
                              message, if any.  If the server doesn't
			      publish a message, a null string is
			      returned in the reply packet.  */

                else if (bcmp(p + 8, "SFms", 4) == 0) {
#ifdef HEXDUMP
		    if (hexdump) {
                        fprintf(stderr, "%s: %d bytes sent to socket.\n",
				prog, messagel);
			xd(stderr, message, messagel, TRUE);
		    }
#endif
		    if (prolix) {
                        printf("%s: %s server message request\n", prog, inet_ntoa(from.sin_addr));
		    }
		    if (send(csock, message, messagel, 0) < 0) {
                        perror("sending server message");
		    }
		}
		break;

	    default:
		if (debugging || verbose) {
                    fprintf(stderr, "Bogus payload type %d\n",
#ifdef RationalWorld
				  rp->common.pt
#else
				  ((unsigned char *) rp)[1]
#endif
			   );
		}
		break;
	}
    }
    close(csock);
}

#ifdef THREADS

/*  Thread processing functions.  */

#ifdef DBTHREAD

/*  EDIT_THREAD  --  Edit information about a service thread from
		     its packet in the ballOstring chain.  */

static char *edit_thread(b)
  struct ballOstring *b;
{
    static char s[80];

    sprintf(s, "%d %s %s", b->thread, inet_ntoa(b->site), ctime(&b->startTime));
    s[strlen(s) - 1] = 0;
    return s;
}
#endif

/*  TIMEOUT_THREAD  --	Handle timeout of sites that go away without
                        sending a "Bye" message.  */

static void *timeout_thread(arg)
  void *arg;
{
    long ct;
    struct ballOstring *b;

#ifdef DBTHREAD
fprintf(stderr, "Timeout thread running\n");
#endif
    while (TRUE) {
	sleep(120);		      /* Wait for next awake interval */
	time(&ct);
#ifdef DBTHREAD
fprintf(stderr, "Timeout thread scanning connections\n");
#endif
	Lock(bsLock);
	b = bshead.next;
	while (b != &bshead) {
#ifdef DBTHREAD
fprintf(stderr, "    Thread %s\n", edit_thread(b));
#endif
	    if ((b->startTime + ServiceThreadTimeout) < ct) {
#ifdef DBTHREAD
fprintf(stderr, "               Kill sent due to timeout.\n");
#endif
		pthread_kill(b->thread, SIGUSR1);
	    }
	    b = b->next;
	}
	Unlock(bsLock);
	release();
    }
}

/*  HTML_THREAD  --  Update the HTML database(s) if something has
		     changed since the last time.  */

static void *html_thread(arg)
  void *arg;
{
#ifdef DBTHREAD
fprintf(stderr, "HTML update thread running\n");
#endif
    while (TRUE) {
#ifdef DBTHREAD
fprintf(stderr, "HTML update thread examining connections\n");
#endif
	updHTML();

	/* In the threaded version, consider every 15 seconds as
           equivalent to "immediate update on every change".
	   We could accomplish immediate update with a
	   semaphore signalling mechanism, but that would
	   cause the threaded and non-threaded versions to
	   diverge much more.  Besides, nobody runs in immediate
	   update mode anyway, as far as I can determine. */

	sleep((unsigned int) (htmlTime <= 0 ? 15 : htmlTime));
    }
}

/*  SERVICE_THREAD  --	Thread to process user packet.	One of
			these is created every time a connection is
			accepted.  */

struct service_arg {
    int sa_csock;
    struct sockaddr_in sa_from;
    struct ballOstring *sa_b;
};

static void killserv()
{
    pthread_t us = pthread_self();
    struct ballOstring *b;
#ifdef DBTHREAD
fprintf(stderr, "Gaaaak!!!  Killserv received by thread %d.\n", us);
#endif

    /* We're being killed by the timeout handler.  Search for
       our entry in the ballOstring chain. */

    Lock(bsLock);
    b = bshead.next;
    while (b != &bshead) {
	if (b->thread == us) {

	    /* Dechain from active thread list. */

	    b->prev->next = b->next;
	    b->next->prev = b->prev;
	    break;
	}
	b = b->next;
    }
    Unlock(bsLock);
    if (b != &bshead) {
	if (b->sa->sa_csock != -1) {
	    close(b->sa->sa_csock);
	}
#ifdef DBTHREAD
fprintf(stderr, "Thread killed by timeout: %s\n", edit_thread(b));
#endif
	free(b->sa);
	free(b);
	pthread_exit(NULL);
    } else {
#ifdef DBTHREAD
fprintf(stderr, "Oops!!!  Timeout kill request to thread %d, not in ballOstring.\n", us);
#endif
    }
}

static void *service_thread(arg)
  void *arg;
{
    struct service_arg *sa = arg;

#ifdef DBTHREAD
fprintf(stderr, "Server thread %d for host %s launched\n", pthread_self(), inet_ntoa(sa->sa_from.sin_addr));
#endif
    signal(SIGUSR1, killserv);	       /* Set signal to handle timeout */
    sa->sa_b->thread = pthread_self();
    servicePacket(sa->sa_csock, sa->sa_from);
#ifdef DBTHREAD
fprintf(stderr, "Server thread %d for host %s exiting\n", pthread_self(), inet_ntoa(sa->sa_from.sin_addr));
#endif

    /* Dechain this thread's item from the ballOstring list. */

#ifdef DBTHREAD
fprintf(stderr, "Thread exiting: %s\n", edit_thread(sa->sa_b));
#endif
    Lock(bsLock);
    sa->sa_b->prev->next = sa->sa_b->next;
    sa->sa_b->next->prev = sa->sa_b->prev;
    free(sa->sa_b);
    Unlock(bsLock);
    free(sa);			      /* Release request packet */
    pthread_exit(NULL);
}
#endif

/*  Main program.  */

main(argc, argv)
  int argc;
  char *argv[];
{
    int i, length;
    struct sockaddr_in name;
#ifdef THREADS
    pthread_t timeout_tid, html_tid;

    pthread_attr_init(&detached);
    pthread_attr_setdetachstate(&detached, PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&detached, (size_t) 65536);
    bshead.next = bshead.prev = &bshead;
#endif

    /*	Process command line options.  */

    prog = prog_name(argv[0]);

#ifdef PRUNEFACE
    {	/* Interctive debugging of text pruning logic. */
	char s[132], p[132];
	char *zap;

	while (TRUE) {
            printf("--> ");
	    if (fgets(s, sizeof s, stdin) == NULL) {
		break;
	    }
	    zap = p;
	    add_sdes_item(1, s, &zap);
	    *zap = 0;
            printf(    "\"%s\"\n", p + 2);
	}
	exit(0);
    }
#endif

    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  --  Debug output to stderr */
		    debugging = TRUE;
		    break;

                case 'F':             /* -Fserv,...  --  Forward to listed servers. */
		    forwardList(op + 1);
		    break;

                case 'H':             /* -Hname  --  HTML file name base path */
		    htmlFile = op + 1;
		    break;

                case 'I':             /* -Isec  --  Interval between HTML updates */
		    htmlTime = atoi(op + 1);
		    break;

                case 'M':             /* -Mfile  --  Load server message from file */
		    {
                        FILE *fp = fopen(op + 1, "r");
			long fl;

			if (fp == NULL) {
                            fprintf(stderr, "%s: can't open server message file %s\n", prog, op + 1);
			    return 2;
			}
			fseek(fp, 0L, 2);
			fl = ftell(fp);
			rewind(fp);
			messagel = ((int) fl) + 13;
			message = (char *) malloc(messagel);
			if (message != NULL) {
			    rtcp_t *rp = (rtcp_t *) message;

#ifdef RationalWorld
			    rp->common.version = RTP_VERSION;
			    rp->common.p = 0;
			    rp->common.count = 1;
			    rp->common.pt = RTCP_APP;
#else
			    *((short *) rp) = htons((RTP_VERSION << 14) |
						    RTCP_APP | (1 << 8));
#endif
			    rp->r.sdes.src = 0;
                            bcopy("SFmr", message + 8, 4);
			    fread(message + 12, (int) fl, 1, fp);
			    message[messagel - 1] = 0;
			}
			fclose(fp);
		    }
		    break;

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

                case 'R':             /* -Rnsec  --  Interval between HTML client-pull updates */
		    htmlRefresh = atoi(op + 1);
		    break;

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

                case 'V':             /*  -V  -- Show connects/disconnects */
		    verbose = TRUE;
                    if (op[1] == 'v' || op[1] == 'V') {
			prolix = TRUE;
		    }
		    break;

#ifdef HEXDUMP
                case 'X':             /* -X  --  Dump packets in hex */
		    hexdump = TRUE;
		    break;
#endif

                case 'Z':             /* -Zname  --  HTML private file name base path */
		    htmlPrivateFile = op + 1;
		    break;
	    }
	} else {
	    usage();
	    return 2;
	}
    }

    /* If no server message has been loaded, create a void one. */

    if (message == NULL) {
	rtcp_t *rp;

	messagel = 13;
	message = (char *) malloc(messagel);
	rp = (rtcp_t *) message;

#ifdef RationalWorld
	rp->common.version = RTP_VERSION;
	rp->common.p = 0;
	rp->common.count = 1;
	rp->common.pt = RTCP_APP;
#else
	*((short *) rp) = htons((RTP_VERSION << 14) | RTCP_APP | (1 << 8));
#endif
	rp->r.sdes.src = 0;
        bcopy("SFmr", message + 8, 4);
	message[messagel - 1] = 0;
    }


    /* Create the socket from which to read */

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

    /* Create name with wildcards. */

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

    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(SIGPIPE, plumber);         /* Catch "broken pipe" signals from disconnects */

    if (listen(sock, 25) < 0) {
        perror("calling listen for socket");
    }

    /* 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

#ifdef THREADS
    pthread_create(&timeout_tid, &detached, timeout_thread, NULL);
    if (htmlFile != NULL || htmlPrivateFile != NULL) {
	pthread_create(&html_tid, &detached, html_thread, NULL);
    }
#else
    signal(SIGALRM, release);	      /* Set signal to handle timeout */
    alarm(TockTock);		      /* Set alarm clock to purge idle hosts */
#endif
    changed();			      /* Create initial HTML file */
    setlinebuf(stdout); 	      /* Set stdout to line buffering for log */

    /* Process requests from the socket. */

    while (TRUE) {
	int csock;
	struct sockaddr_in from;      /* Sending host address */
	int fromlen;		      /* Length of sending host address */
#ifdef THREADS
	struct service_arg *sap;
	pthread_t service_tid;
#endif

	errno = 0;
	do {
	    fromlen = sizeof from;
	    csock = accept(sock, (struct sockaddr *) &from, &fromlen);
#ifdef DBTHREAD
fprintf(stderr, "Accept %d\n", csock);
#endif
	    if (csock >= 0) {
		break;
	    }
	} while (errno == EINTR);
	if (csock < 0) {
            perror("accepting connection to socket");
	    continue;
	}
	if (prolix) {
            printf("%s: %s accept\n", prog, inet_ntoa(from.sin_addr));
	}
#ifdef THREADS
	sap = (struct service_arg *) malloc(sizeof(struct service_arg));
	if (sap != NULL) {
	    struct ballOstring *b;

	    sap->sa_csock = csock;
	    sap->sa_from = from;
	    b = (struct ballOstring *) malloc(sizeof(struct ballOstring));
	    if (b != NULL) {
		sap->sa_b = b;
		b->sa = sap;
		Lock(bsLock);
		b->next = bshead.next;
		b->prev = &bshead;
		bshead.next = b;
		b->next->prev = b;
		b->thread = -1;
		time(&(b->startTime));
		b->site = from.sin_addr;
		Unlock(bsLock);
		if (pthread_create(&service_tid, &detached, service_thread, sap) != 0) {
                    fprintf(stderr, "%s: Cannot launch server thread for request from %s: %s",
			prog, inet_ntoa(from.sin_addr), strerror(errno));
		    close(csock);
		}
	    } else {
		free(sap);
                fprintf(stderr, "Unable to allocate memory for ballOstring packet.\n");
		close(csock);
	    }
	} else {
            fprintf(stderr, "Unable to allocate memory for service_arg packet.\n");
	    close(csock);
	}
#else
	servicePacket(csock, from);
#endif
    }
#ifdef MEANS_OF_EXIT
    close(sock);
    return 0;
#endif
}
