/*
#define RCS_ID $Id: pb.c,v 1.22 2001/03/16 08:45:37 oz6bl Exp $
 *   Copyright 1992, 1993, 1994 John Melton (G0ORX/N6LYT)
 *              All Rights Reserved
 *
 *   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 1, or (at your option)
 *   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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   This program is modified by Peter Scott oz2aba/oz7sat @ amsat.org
 *   The program is extended with:
 *	No X11 support
 *	Uses ncurses to handle display control
 *	Automatic update of directory
 *	Database of files to be downloaded
 *	Extended control of pb by UDP packets
 *	Automatic selection of files to download based on a rule file 
 *   
 */

/*
	pb.c

	Pacsat Broadcast Protocal Receiver

	This software was written to provide a receive only capability
	for collecting files and directories from satellites running
	the Pacsat Protocols running on the Linux Operating System.

	The TNC must be setup for KISS.
*/

/*	Changes (OZ6BL) :

	V 1.1

	-	Changed strategy for sending fill requests to the satellite:
		When a frame addressed to PBLIST is received:
		IF I'm in the queue THEN
		   IF my request is a file request THEN
			do nothing
		   ELSE
			send a file request (if any)
		ELSE
		   send any request (file or directory)

	-	Changed name of kiss log file to YYMMDDHH.kss
		(e.g. 96123014.kss)

	-	Reverted back to use of sockets

	V 1.2

	-	Introduced color to the displays (thanks KD2BD)

	-	Enabled use of SSID in MYCALL

	V 1.2p1

	-	Cleaned-up display of file names in status window 

	V 1.2p2

	-	Removed duplicate file entries in upper left-hand
		window
		
	V 1.2p3

	-	Accentuated MYCALL in the log window
	-	Cleaned up downlink efficiency logging
	-	Disabled CRC check for TLM frames. TLM frames are not
		CRC protected in AO-16/LO-19, causing the E-counter
		in the status line to increment on every TLM frame
		
	V 1.3
	
	-	Fixed downlink efficiency calculation for satellites
		other than AO-16
		
	V 1.4

	-	Moved the determination of whether or not a file should
		be downloaded to file 'download.c'
	-	Added keyboard command 'r'/'R' to re-initialize
		download control
	-	Added a signal handler for SIGHUP to do the same
	-	Added a ditto UDP request REQ_TYPE_REINIT 
	
	V1.5

	-	Fixed an error in BroadcastDirectory that could prevent
		the directory from becoming 'up-to-date'

	V1.6

	-	Fixed an error that prevented download of files longer 
		than 65535 bytes from completing
		
	V1.98

	-	Made PB daemon-like by moving all screen handling (which 
		uses curses), to program 'pbctrl'
	-	Introduced the concept of a control FIFO based on the
		concept from 'diald'. The FIFO handling is in routine 
		'fifo.c'
	-	Changed to use one unified configuration file for all satellites.
		The configuration file is /etc/pbpg/pb.conf
		The callsign of the satellite (parameter Satellite) must be given,
		either on the command line (-s) or as an environment variable 
		(SATELLITE)

	V1.99

	-	Changed the pbctrl->pb communication to use an XML syntax

	V2.00

	-	Changed the pb->pbctrl communication to also use an XML syntax
	-	Made the control FIFO fixed: it is always /etc/pbpg/pb-fifo
*/

#define VERSION_STRING "version 2.00 by oz2aba and oz6bl"
#define USAGE_STRING "Usage: pb [-s satellite] [-m mycall] [-n satname] \n\t\
[-M maxdays] [-U UDPport] [-k kissfile] [-v]"

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <linux/if_ether.h>
#include <linux/ax25.h>
#include <linux/rose.h>

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <string.h>

#include "ftl0.h" 
#include "pb.h"
#include "header.h"
#include "request.h"
#include "crc.h"
#include "fifo.h"

#include <netax25/axconfig.h>
#include <netax25/axlib.h>

#define REQUEST_NONE 0
#define REQUEST_FILE 1
#define REQUEST_ANY 2

#define	FILL_NONE	0
#define	FILL_UNIQUE	1
#define	FILL_OVERLAP	2

/* Prototypes */

void writelog (int, char *) ;
void UpdateStatus(void) ;
void FillDirectory(void) ;
void FillFile(int *) ;
void CancelFill(void) ;
int ThisFileShouldBeDownloaded(HEADER *) ;
void InitializeDownload(void) ;
void ToMonitor (char *, char *) ;

/* Debug info */

//int debugmask = DEBUG_TO_FIFO ;
//int debugmask = DEBUG_FROM_FIFO | DEBUG_TO_FIFO ;
//int debugmask = DEBUG_IN_TAG | DEBUG_FROM_FIFO | DEBUG_TO_FIFO ;
int debugmask = 0 ;

/* Program parameters as read from pb.conf or the command line*/
char Satellite	[100] ;
char SatName	[100] ;
char MyCall		[100] ;
char MaxDays	[100] ;
char UDPPort	[100] ;
char KissLog	[100] ;


/* variables derived from the configuration parameters */

char MyPBCall    [100];
int  MyPBCallLength ;
unsigned int UdpPort     = 5100;
int	     kisslog     = 0;
unsigned long maxDays = 5;

/* other variables */


FILE *fpkiss = NULL ;
char writebuf[1000];
long id;		/* ID number of the file in pursuit */
unsigned long int curByteRead   = 0;
unsigned long int curByteWrite  = 0;
unsigned long int lastByteRead  = 0;
unsigned long int lastByteWrite = 0;

int s_input;
int s_file;
int s_directory;
struct sockaddr_ax25	src;
struct sockaddr_ax25	dest;

struct sockaddr_in request_addr;
int s_request;		/* Handle for receiving internal requests */

#define	TIMEOUT		2
#define TIMEOUTFLUSH	30

unsigned char	buf [MAXBUFFER];
int		bufSize;


OPENFILES of[MAXFILES + 1];	/* last entry is for the directory file */
int nextof = 0;

int stopFlag = 0;		/* Set to nonzero to terminate program */
int directoryOld = 1;	/* Marks the directory as in unknown state */

int bytes     = 0;		/* Trafic counters */
int fileBytes = 0;
int dirBytes  = 0;
int tlmBytes  = 0;
int crcErrors = 0;

time_t start_time = 0;	/* Used for Bytes/Second/Pass calculations */
time_t end_time   = 0;

						/* Screen numbers */
#define LOG 1
#define FILES 2

/* Download files control structures */
unsigned long download_list [MAXDOWNLOAD];
unsigned long download_active = 0xFFFFFFFF;

/* Variables for FIFO handling */

char FIFOname [] = "/etc/pbpg/pb-fifo" ;
MONITORS *monitors = 0;	/* Monitor pipes */
PIPE *pipes = 0;		/* Command pipes */
PIPE *fifo_pipe ;
fd_set ctrl_fds;

char logFileName [] = "pbdebug.log" ; /* Name of debug log file for PB */
/*
 *	Signal handler for re-initialization of download control
 */
void signal_reinit (int s) {
	signal(SIGHUP, signal_reinit) ;
	InitializeDownload() ;
}
/*
 *	Signal handler to handle the SIGTERM signal
 */
void signal_term (int s) {
	signal(SIGTERM, signal_term) ;
	mon_write ("FINAL") ;
	stopFlag = 1 ;
}

/*
 *	Limit the contents of text to printable chars
 */
void notprintable (char *text)
{
	text--;
	while (*(++text) != '\0') {
		if (*text >= ' ' && *text <= '}') continue;
		if (*text == '\t') continue;
		if (*text == '\r') *text = '\n';
		if (*text == '\n') continue;
		*text = '.';
	}
}
/*
* Write a message on the monitors.
*/
void mon_write(char *message) {
   int len = strlen(message) ;
   MONITORS *c = monitors, *p = 0, *cn;
   
   while (c) {
	   cn = c->next;
	   if (write(c->fd,message,len) < 0) {
		   if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
			   /* Write error. The reader probably got swapped out
			   * or something and the pipe flooded. We'll just "loose"
			   * the data.  */
			   p = c;
			   c = cn;
			   continue;
		   }
		   close(c->fd);
		   if (p) p->next = c->next;
		   else monitors = c->next;
		   free(c->name);
		   free(c);
	   } else {
		   p = c;
	   }
   c = cn;
   }
}
/*
 * Send one line to monitoring programs
 */
void ToMonitor (char *tag, char *text) {
#define TOMON_SIZE 1024
	int textlen = strlen(text) ;
	char buffer [TOMON_SIZE] ;
	if (2*strlen (tag) + textlen + 5 > TOMON_SIZE)
		mon_write("ERROR - text too long") ;
	else {
		if (textlen == 0)
			sprintf (buffer, "<%s/>", tag) ;
		else
			sprintf (buffer, "<%s>%s</%s>", tag, text, tag) ;
		if ((debugmask & DEBUG_TO_FIFO) == DEBUG_TO_FIFO) {
			sprintf (debugline, "To FIFO  :%s", buffer) ; 
			WriteToLog (debugline) ; }
		mon_write (buffer) ; 
	}
}

/*
 *	Write a text line on one of the log screens
 */
void writelog (int scrno, char *text)
{
	char temp [2048] ;
	/* Remove the tail if it is a \n */
	while (text [strlen (text)-1] == '\n' || text [strlen (text)-1] == ' ')
		text [strlen (text) - 1] = '\0';
	encodePCData (text, temp) ;
	if (scrno == LOG) ToMonitor("LOG", temp) ;
	else ToMonitor("FILES", temp);
}

/*
 *	Write a text line on the download screen
 */
void writedownload (int lin, char *text)
{
	char temp [100] ;
	sprintf (temp, "<LNO>%d</LNO><LTXT>%s</LTXT>", lin, text) ;
	ToMonitor("DOWNLOAD", temp);
}

/*
 *	Write an ERROR message
 *
 */
void MessageBox(char *text)
{
	char	s [100];
	sprintf (s, "ERROR: %s", text);
	writelog (LOG, " ");
	writelog (LOG, s);
	writelog (LOG, " ");
}


/*
 *	Convert a call from the shifted ascii form used in an
 *	AX.25 packet.
 */
int ConvertCall(unsigned char *c, char *call)
{
	unsigned char *ep = c + 6;
	int ct = 0;

	while (ct < 6)
	{
		if (((*c >> 1) & 127) == ' ') break;

		*call = (*c >> 1) & 127;
		call++;
		ct++;
		c++;
	}
	
	if ((*ep & 0x1E) != 0)
	{	
		*call = '-';
		call++;
		call += sprintf(call, "%d", (int)(((*ep) >> 1) & 0x0F));
	}

	*call = '\0';
	
	if (*ep & 1) return 0;

	return 1;
}

/*
 *	Load/Reload the download.req file
 */
void CheckDownloadFile (void)
{
	FILE	*fp;
	int	x;

	fp = fopen ("download.req", "r");
	if (fp == NULL) {		/* No download file. Create it */
		memset (download_list, 0xFF, sizeof (unsigned long) * MAXDOWNLOAD);
		fp = fopen ("download.req", "w");
		fwrite (download_list, sizeof (unsigned long), MAXDOWNLOAD, fp);
		fclose (fp);
		return;
	}

	fread (download_list, sizeof (unsigned long), MAXDOWNLOAD, fp);
	x = ferror (fp);
	fclose (fp);
	if (x) return;			/* Error reading. Ignore it */

	/* If download_active is -1 find the oldest file to download */
	if (download_active != 0xFFFFFFFF) return;

	for (x = 0, download_active = 0xFFFFFFFF; x < MAXDOWNLOAD; x++) {
		if (download_list [x] < download_active)
			download_active = download_list [x];
	}
}

/*
 *	Remove a file from the download_list and file
 */
void RemoveDownloadFile (unsigned long file_id)
{
	FILE	*fp;
	int	x;
	long	l;

	fp = fopen ("download.req", "r+");
	if (fp == NULL) return;		/* No download file. Ignore it */

	fread (download_list, sizeof (unsigned long), MAXDOWNLOAD, fp);
	if (ferror (fp)) {		/* Read Error. Ignore it */
		fclose (fp);
		return;
	}

	/* If this request was the active one, clear it */
	if (download_active == file_id) download_active = 0xFFFFFFFF;

	/* Locate the requested ID in the download_list */
	for (x = 0; x < MAXDOWNLOAD; x++) {
		if (download_list [x] == file_id) break;
	}

	if (download_list [x] == file_id) {
		/* Clear the entry in the list and the file */
		download_list [x] = 0xFFFFFFFF;
		l = sizeof (unsigned long) * x;
		fseek (fp, l, SEEK_SET);
		fwrite (&download_list [x], sizeof (unsigned long), 1, fp);
	}
	fclose (fp);
}

/*
 *	Add a file to the download_list and file
 */
void AddDownloadFile (unsigned long fileid)
{
	FILE	*fp;
	int	x;
	long	l;

	fp = fopen ("download.req", "r+");
	if (fp == NULL) return;		/* No file. Ignore it */

	fread (download_list, sizeof (unsigned long), MAXDOWNLOAD, fp);
	if (ferror (fp)) {		/* Read error.  Ignore it */
		fclose (fp);
		return;
	}

	/* Locate a empty slot in the table */
	for (x = 0; x < MAXDOWNLOAD && download_list [x] != 0xFFFFFFFF; x++) ;
	if (download_list [x] == 0xFFFFFFFF) {
		/* An empty slot. Use it and update the file */
		download_list [x] = fileid;
		l = sizeof (unsigned long) * x;
		fseek (fp, l, SEEK_SET);
		fwrite (&download_list [x], sizeof (unsigned long), 1, fp);
	}
	fclose (fp);
}


/*
 *	Creates and sends a request to the satellite
 */
void UpdateRequest (unsigned int REQUEST_TYPE)
{
	static int		req_len;

	/* Create a new request */
	req_len = 0;

	/* Check for files first */
	/* Check if a file is in the download database */
	CheckDownloadFile ();

	if (download_active != 0xFFFFFFFF) {
		id = download_active;	/* Get this file */
		FillFile (&req_len);

		/* Check if this file should be requested */
		if (req_len == 0) RemoveDownloadFile (id);
	}
 	
	if (REQUEST_TYPE == REQUEST_ANY && req_len == 0) {
            /* a directory update request may go */
	    if (directoryOld || of [MAXFILES].holes > 1) {
	        /* The directory needs an update. Create request */
		FillDirectory ();
		if (req_len != 0) id = -2;	/* Requesting Directory */ }
	    else 
	        id = -1 ; /* Nothing is being requested */
	}
	/* Return if the request buffer is empty */
	if (req_len == 0) {
		return;
	}
}

/*
 *	Update status counters in the status line on the display
 */
void UpdateStatus(void)
{
 	char buffer [1000] ;
	int dir_stat ;
	
	/* Directoru status */
	if (directoryOld) {
		dir_stat = -1 ;
	} else if (of [MAXFILES].holes == 1) {
		dir_stat = -2 ;
	} else {
		dir_stat = of [MAXFILES].holes - 1 ;
	}

	sprintf (buffer, "<DSTAT>%d</DSTAT><ID>%ld</ID><BYTES>%d</BYTES>"
			"<FBYTES>%d</FBYTES><DBYTES>%d</DBYTES><TBYTES>%d</TBYTES>"
			"<CRC>%d</CRC>",
			dir_stat, id, bytes, fileBytes, dirBytes, tlmBytes, crcErrors);
	ToMonitor("STATUS", buffer);
}

/*
 * Hole list maintenance
 */
int LoadHoleList(int f, char *holeName, char *fileName)
{
	FILE *holeFile;
	FILE *fileFile;
	HOLE *hole;
	HOLE *prevHole;
	int i;

	holeFile = fopen(holeName, "r");
	fileFile = fopen(fileName, "r");

	/*
	 * handle the case where a .act file or a .hol file exist without
	 * the other. Usually caused by a crash.
	 */

	if (holeFile != NULL && fileFile != NULL)
	{
		fscanf(holeFile, "%d %*s %*s %*s", &of[f].hdrSeen);
		fscanf(holeFile, "%d %*s %*s %*s", &of[f].fileSize);
		fscanf(holeFile, "%d %*s", &of[f].holes);

		prevHole = NULL;

		for (i = 0; i < of[f].holes; i++)
		{
			hole           = (HOLE *)malloc(sizeof(HOLE));
			hole->nextHole = NULL;
			fscanf(holeFile, "%u, %u", &hole->start, &hole->finish);

			if (prevHole == NULL)
				of[f].firstHole = hole;
			else
				prevHole->nextHole = hole;

			prevHole = hole;
		}
	
		fclose(holeFile);
		fclose(fileFile);

		return(1);
	}
	else
	{
		if (fileFile != NULL) fclose(fileFile);
		if (holeFile != NULL) fclose(holeFile);
	
		/* create an empty hole list */
		of[f].firstHole           = (HOLE *)malloc(sizeof(HOLE));
		of[f].firstHole->start    = 0;
		of[f].firstHole->finish   = 0xFFFFFFFF;
		of[f].firstHole->nextHole = NULL;
		of[f].holes               = 1;

		return(0);
	}
}

void LoadDirectoryHoleList(void)
{
	FILE *holeFile;
	HOLE *hole;
	HOLE *prevHole;
	time_t t;
	int holes;
	unsigned oldest;
	unsigned start, finish;

	/* prune back the list */
	time(&t);
	oldest = (unsigned)t - (60 * 60 * 24 * maxDays);

	if ((holeFile = fopen("pfhdir.hol", "r")) != NULL)
	{
		fscanf(holeFile, "%d %*s %*s %*s", &of[MAXFILES].hdrSeen);
		fscanf(holeFile, "%d %*s %*s %*s", &of[MAXFILES].fileSize);
		fscanf(holeFile, "%d %*s", &holes);

		prevHole = NULL;

		of[MAXFILES].holes = 0;

		while (holes-- > 0)
		{
			fscanf(holeFile, "%u, %u", &start, &finish);

			if (finish > oldest)
			{
				hole           = (HOLE *)malloc(sizeof(HOLE));
				hole->nextHole = NULL;

				hole->start    = (start < oldest) ? oldest : start;
				hole->finish   = finish;

				if (prevHole == NULL)
					of[MAXFILES].firstHole = hole;
				else
					prevHole->nextHole = hole;

				prevHole = hole;

				of[MAXFILES].holes++;
			}
		}
	
		fclose(holeFile);
	}
	else
	{
		/* create an empty hole list */
		of[MAXFILES].firstHole           = (HOLE *)malloc(sizeof(HOLE));
		of[MAXFILES].firstHole->start    = oldest;
		of[MAXFILES].firstHole->finish   = 0xFFFFFFFF;
		of[MAXFILES].firstHole->nextHole = NULL;
		of[MAXFILES].holes               = 1;

		/* use file size as file number - start at 1 */
		of[MAXFILES].fileSize            = 1;
	}
}

void SaveHoleList(int f, char *fileName)
{
	char buffer[40];
	FILE *holeFile;
	HOLE *hole;

	if ((holeFile = fopen(fileName, "w")) == NULL)
	{
		sprintf(buffer, "Cannot open file %s", fileName);
		MessageBox(buffer);
		return;
	}
	
	fprintf(holeFile, "%d pfh header received\n", of[f].hdrSeen);
	fprintf(holeFile, "%d pfh file length\n", of[f].fileSize);
	fprintf(holeFile, "%d holes\n", of[f].holes);

	hole = of[f].firstHole;

	while (hole != NULL)
	{
		fprintf(holeFile, "%u, %u\n", hole->start, hole->finish);
		hole = hole->nextHole;
	}

	fclose(holeFile);
}

int UpdateHoleList(int f, unsigned start, unsigned finish)
{
	HOLE *h, *p, *n;

	for (p = NULL, h = of[f].firstHole; h != NULL; p = h, h = h->nextHole)
	{
		/* see if this is a candidate */
		if (start <= h->finish && finish >= h->start)
		{
			if (start == h->start && finish == h->finish)
			{
				/* exact match - will remove all of it */
				if (p == NULL)
					of[f].firstHole = h->nextHole;
				else
					p->nextHole = h->nextHole;

				of[f].holes--;
				free((char *)h);
				return(FILL_UNIQUE);
			}

			if (start <= h->start && finish >= h->finish)
			{
				/* larger - will remove all of it */
				if (p == NULL)
					of[f].firstHole = h->nextHole;
				else
					p->nextHole = h->nextHole;

				of[f].holes--;
				free((char *)h);
				return(FILL_OVERLAP);
			}

			if (start == h->start && finish < h->finish)
			{
				/* remove the front part */
				h->start = finish + 1;
				return(FILL_UNIQUE);
			}

			if (start < h->start && finish < h->finish)
			{
				/* remove the front part */
				h->start = finish + 1;
				return(FILL_OVERLAP);
			}

			if (start > h->start && finish == h->finish)
			{
				/* will remove the end */
				h->finish = start - 1;
				return(FILL_UNIQUE);
			}

			if (start > h->start && finish > h->finish)
			{
				/* will remove the end */
				h->finish = start - 1;
				return(FILL_OVERLAP);
			}

			if (start > h->start && finish < h->finish)
			{
				/* remove the middle */
				n = (HOLE *)malloc(sizeof(HOLE));
				n->start    = finish + 1;
				n->finish   = h->finish;
				n->nextHole = h->nextHole;

				/* change the current hole for the first part */
				h->finish   = start - 1;
				h->nextHole = n;

				/* one more hole */
				of[f].holes++;
				return(FILL_UNIQUE);
			}
		}
	}

	return(FILL_NONE);
}

/*
 * Message file maintenance
 */
void LoadFile(int fileId, int f)
{
	char buffer[80];
	char holeName[40];
	char fileName[40];
	char txt[20];

	/* save this file id */
	of[f].fileId = fileId;

	/* load the hole file and open the data file */
	sprintf(fileName, "%x.act", of[f].fileId);
	sprintf(holeName, "%x.hol", of[f].fileId);

	if (LoadHoleList(f, holeName, fileName))
	{
		if ((of[f].file = fopen(fileName, "r+")) == NULL)
		{
			sprintf(buffer, "Cannot open file %s", fileName);
			MessageBox(buffer);
		}
	}
	else
	{
		if ((of[f].file = fopen(fileName, "w+")) == NULL)
		{
			sprintf(buffer, "Cannot open file %s", fileName);
			MessageBox(buffer);
		}
	}
	/* now update the download window */
		sprintf (txt, "%4x  %4d  %6d  %s",
		              of[f].fileId, of[f].holes, 0,
		              (of[f].hdrSeen == 1 ? "Y" : "N"));
		writedownload (f + 1, txt);

}

void SaveFile(int f)
{
	HOLE *hole;
	HOLE *next;
	char fileName[40];

	if (of[f].file != NULL)
	{
		sprintf (writebuf, "saving file %x", of[f].fileId);
		writelog (FILES, writebuf);

		/* close the data file */
		fclose(of[f].file);

		/* write out the hole list */
		sprintf(fileName, "%x.hol", of[f].fileId);
		SaveHoleList(f, fileName);

		/* clean up the file table */
		of[f].fileId   = 0;
		of[f].file     = NULL;
		of[f].hdrSeen  = 0;
		of[f].fileSize = 0;
		of[f].holes    = 0;

		/* update the download window */
		writedownload (f+1, "") ;


		/* free the hole list */
		hole = of[f].firstHole;

		while (hole != NULL)
		{
			next = hole->nextHole;
			free((char *)hole);
			hole = next;
		}

		of[f].firstHole = NULL;
	}
}

void CheckDownloaded(int f)
{
	int headerSize;
	HEADER *hdr;
	HOLE *h;
	unsigned char *buf;
	char oldName[80];
	char newName[80];
	char holeName[80];

	/* see if we have the header */
	if (of[f].hdrSeen == 0)
	{
		h = of[f].firstHole;

		if (h->start != 0)
		{
			/* read the header into a buffer */
			if ((buf = malloc(h->start)) != NULL)
			{
				fseek(of[f].file, 0L, SEEK_SET);
				fread(buf, 1, h->start, of[f].file);

				if ((hdr = ExtractHeader(buf, h->start, &headerSize)) != NULL)
				{
					of[f].hdrSeen  = 1;
					of[f].fileSize = hdr->fileSize;
					UpdateHoleList(f, (unsigned)of[f].fileSize, (unsigned)0xFFFFFFFF);
					free((char *)hdr);
				}

				free(buf);
			}
		}
	}

	/* see if we now have the complete file */
	if (of[f].holes == 0 && of[f].hdrSeen == 1)
	{
		unsigned long fileId;

		/* close the current file */
		fileId = of[f].fileId;
		SaveFile(f);

		/* rename the file */
		sprintf(oldName, "%lx.act", fileId);
		sprintf(newName, "%lx.dl", fileId);
		rename(oldName, newName);

		/* remove the hole file */
		sprintf(holeName, "%lx.hol", fileId);
		unlink(holeName);

		/* Remove the file from the download_list */
		RemoveDownloadFile (fileId);

		/* let the user know */
		sprintf(writebuf, "%lx downloaded", fileId);
		writelog (FILES, writebuf);

		/* Check if this file completes the active request */
		/* send the satellite a new request */
		/* This is a optimizing hack. ???? */
/* This hack is disabled. ?????
		if (id == fileId) {
			in_queue = 0;		/ * Clear the queue flag * /
			UpdateRequest ();	/ * Create a new request * /
		}
*/
	}
}

void BroadcastFile(unsigned char *buffer, int length)
{
	static unsigned HeardId = 0;
	static unsigned DupId   = 0;
	FILEHEADER *fh;
	unsigned char *data;
	unsigned dataOffset;
	unsigned dataLength;
	char fileName[80];
	char txt[100];
	FILE *fp;
	int i;

	/* crc validation */
	if (!CheckCRC(buffer, length))
	{
		crcErrors++;
		UpdateStatus();
		return;
	}

	/* point to the header */
	fh = (FILEHEADER *)buffer;

	/* point to the data */
	data       = buffer + sizeof(FILEHEADER);
	dataOffset = fh->wOffset + (fh->nOffsetHigh << 16);
	dataLength = length - sizeof(FILEHEADER) - CRCLENGTH;
	
	/* inform the user */
	if (fh->fileId != HeardId)
	{
		sprintf  (writebuf, "heard message %x", fh->fileId);
		writelog (FILES, writebuf);
		HeardId = fh->fileId;
		DupId   = -1;
	}

	/* see if the file is already downloaded */
	sprintf(fileName, "%x.dl", fh->fileId);

	if ((fp = fopen(fileName, "r")) != NULL)
	{
		fclose(fp);

		if (fh->fileId != DupId)
		{
			sprintf  (writebuf, "%x is already downloaded", fh->fileId);
			writelog (FILES, writebuf);
			DupId = fh->fileId;
		}

		return;
	}

	/* see if the file is in the current list of files */
	for (i = 0; i < MAXFILES; i++)
		if (of[i].fileId == fh->fileId)
			break;

	/* if it is not there then we must load it */
	if (i == MAXFILES)
	{
		/* Locate a empty slot before closing an active slot ???? */

		/* save current file if used */
		SaveFile(nextof);

		/* now use this slot */
		LoadFile(fh->fileId, nextof);
		
		/* setup i for this entry */
		i = nextof;

		/* make sure we round robin the open files */
		nextof = (nextof + 1) % MAXFILES;
	}

	if (UpdateHoleList(i, (unsigned)dataOffset, (unsigned)(dataOffset + dataLength - 1)) != FILL_NONE)
	{
		/* write the data */
		fseek(of[i].file, dataOffset, SEEK_SET);
		fwrite(data, 1, dataLength, of[i].file);

		/* Update the download window */
		sprintf (txt, "%4x  %4d  %6d  %s",
		              of[i].fileId, of[i].holes, dataOffset,
		              (of[i].hdrSeen == 1 ? "Y" : "N"));
		writedownload (i + 1, txt);

		/* see if it has all been downloaded */
		CheckDownloaded(i);

		/* Check if the file is completed */
		if (of[i].file == NULL) {
			writedownload (i + 1, "");
		}
	}
}

/*
 *	Directory Maintenance
 */
void WriteDirectory(unsigned char *data, int dataLength)
{
	char buffer[80];
	char fileName[40];
	struct stat statbuf;
	FILE *fp;

	sprintf(fileName, "pb__%04d.pfh", of[MAXFILES].fileSize);

	/* write the data to the end of the current file */
	if ((fp = fopen(fileName, "a")) == NULL)
	{
		sprintf(buffer, "Cannot open file %s", fileName);
		MessageBox(buffer);
		return;
	}

	fwrite(data, 1, dataLength, fp);
	fclose(fp);

	stat(fileName, &statbuf);

	/* limit file size to 20000 bytes */
	if (statbuf.st_size > 20000) of[MAXFILES].fileSize++;
}

void BroadcastDirectory(unsigned char *buffer, int length)
{
	static unsigned int  fragmentId     = 0;
	static unsigned int  fragmentOffset = 0;
	static unsigned char fragmentBuffer[1024];
	DIRHEADER	*dh;
	int		headerSize;
	HEADER		*hdr;
	unsigned char 	*data;
	int		dataLength;
	int		i;

	/* crc validation */
	if (!CheckCRC(buffer, length))
	{
		crcErrors++;
		UpdateStatus();
		return;
	}

	/* point to the header */
	dh = (DIRHEADER *)buffer;

	/* check for fragmented directory header */
	if (dh->offset != 0)
	{
		/* see if this is the next fragment */
		if (fragmentId == dh->fileId && dh->offset == fragmentOffset)
		{
			/* append this fragment */
			data       = buffer + sizeof(DIRHEADER);
			dataLength = length - sizeof(DIRHEADER) - CRCLENGTH;

			for (i = 0; i < dataLength; i++)
				fragmentBuffer[fragmentOffset + i] = data[i];

			fragmentOffset += dataLength;

			if ((dh->flags & LASTBYTEFLAG) == LASTBYTEFLAG)
			{
				data       = fragmentBuffer;
				dataLength = fragmentOffset;
			}
			else
			{
				return;
			}
		}
		else
		{
			fragmentId     = 0;
			fragmentOffset = 0;
			return;
		}
	}
	else if ((dh->flags & LASTBYTEFLAG) != LASTBYTEFLAG)
	{
		/* copy the fragment */
		data       = buffer + sizeof(DIRHEADER);
		dataLength = length - sizeof(DIRHEADER) - CRCLENGTH;

		for (i = 0; i < dataLength; i++)
			fragmentBuffer[i] = data[i];

		/* start a new fragment */
		fragmentId     = dh->fileId;
		fragmentOffset = dataLength;

		return;
	}
	else
	{
		/* its all there -  point to the data */
		data       = buffer + sizeof(DIRHEADER);
		dataLength = length - sizeof(DIRHEADER) - CRCLENGTH;
	}

	if ((dh->flags & LASTBYTEFLAG) == LASTBYTEFLAG)
	{
		/* reset fragmentation */
		fragmentId     = 0;
		fragmentOffset = 0;

		/* try to extract the header */
		if ((hdr = ExtractHeader(data, dataLength, &headerSize)) != NULL)
		{
			/* inform the user */
			if (strlen(hdr->title) == 0)
				sprintf(writebuf, "dir: %lx: to:%s  %s",
					hdr->fileId,
					hdr->destination,
					hdr->fileName);
			else
				sprintf(writebuf, "dir: %lx: to:%s  %s",
					hdr->fileId,
					hdr->destination,
					hdr->title);

			notprintable (writebuf);
			writelog (FILES, writebuf);

			/* update the directory  if needed */
			if (UpdateHoleList(MAXFILES, dh->tOld, dh->tNew) == FILL_UNIQUE) {
				WriteDirectory(data, dataLength);

				/* Check if the directory is complete */
				/* If so end a DIR request */
/*  This is a optimizing hack.  Currently disabled ??????
				if (of [MAXFILES].holes == 1 && request_no == REQUEST_DIR)
					UpdateRequest (REQUEST_NONE);
*/

				/* Check if the just received file is to be downloaded */
				if (ThisFileShouldBeDownloaded(hdr)) {
					/* Add this file to download_list */
					AddDownloadFile (hdr->fileId);
					writelog (FILES, "file ^^^^^^ marked for downloading");
				}
			}
			free ((char *)hdr);

			/* Mark the directory as in KNOWN state now */
			directoryOld = 0;
		}
		else
		{
			sprintf  (writebuf, "** Bad directory header for %x", dh->fileId);
			writelog (LOG, writebuf);
		}
	}
}

/*
 *	decode a received frame.
 */
void ProcessFrame(void)
{
	int n=1, i;
	int via;
	unsigned char protocol;
	char toCall[10];
	char fromCall[10];
	char viaCall[10];
	char *s;

	/* Check for runts/pygmygs */
	if (bufSize < 15) {
		char	st[10];
		int	x;
		sprintf (writebuf, "Runt:");
		for (x = 0; x < bufSize; x++) {
			sprintf (st, " %02x", buf [x]);
			strcat (writebuf, st);
		}
		writelog (LOG, writebuf);
		return;
	}
	
	bytes += bufSize - 1 ;

	/* decode the to/from address */
	/* dont expect via address, but saves last if any */
	strcpy(toCall, ax25_ntoa((const ax25_address *)(buf+n)));

	//via = ConvertCall(buf + n, toCall);
	n += 7;

	via = ConvertCall(buf + n, fromCall);
	n += 7;

	while (via)
	{
		via = ConvertCall(buf + n, viaCall);
		n += 7;
	}

	if (fpkiss != NULL)
	{
		fputc(0300, fpkiss);		/* FEND */
		fputc(0x00, fpkiss);		/* KISS Data */

		for (i = 1; i < bufSize; i++)
		{
			switch (buf[i])
			{
				case 0300:	/* FEND */
					fputc(0333, fpkiss);	/* FESC */
					fputc(0334, fpkiss);	/* FESC_END */
					break;
				case 0333:	/* FESC */
					fputc(0333, fpkiss);	/* FESC */
					fputc(0335, fpkiss);	/* FESC_ESC */
					break;
				default:
					fputc(buf[i], fpkiss);
					break;
			}
		}

		fputc(0300, fpkiss);		/* FEND */
	}


	/* check for a UI frame */
	if ((buf[n] & 0xEF) != 0003)
	{
		/* Ignore non UI frames */
		UpdateStatus ();
		return;
	}

	n++;
	protocol = buf[n++];

	/* see if the frame is a broadcast frame */	
	if (strcmp(toCall, "QST-1") == 0)
	{
		switch (protocol)
		{
		case PID_FILE:
			fileBytes += bufSize - n;
			BroadcastFile(buf + n,  bufSize - n);
			break;
		case PID_DIRECTORY:
			dirBytes += bufSize - n;
			BroadcastDirectory(buf + n, bufSize - n);
			break;
		default:
			break;
		}
	}
	else if (strcmp(toCall, "PBLIST") == 0)
	{
		buf[bufSize] = '\0';
		sprintf  (writebuf, "%s", buf + n);
		notprintable (writebuf);
		writelog (LOG, writebuf);

		/* Check if a request has not been received by the satellite */
		s = strstr ((char *)buf + n, MyPBCall);
		if (s != NULL)  /* I might be in the queue */ {
			/* Check that the char after MyCall is a SP, a '\' or a CR */
			s += strlen (MyPBCall);
			if (*s == ' ' || *s == '\\' ||*s == '\r')   {
                           /* I'm in the queue */
                           if (*s == '\\') {
                              /* I have a DIR request at the satellite 
                                 see if a FILE request should go instead */
                              UpdateRequest (REQUEST_FILE) ; }
                           else /* it's a FILE request - do nothing */
                              ; }
                        else /* it wasn't my call after all */
                           UpdateRequest (REQUEST_ANY) ; }
                else /* not in the queue */
                   UpdateRequest (REQUEST_ANY) ;

	}
	else if (strcmp(toCall, "TLM") == 0)
	{
		tlmBytes += bufSize - n;

		/* Currently disabled as the 1200 Bd satellites apparently
		   do not CRC protect the TLM frames. Also, the TLM info is
		   not being used by PB. Note that it is written to the KISS
		   log file, so it may be processed by other programs.
		if (!CheckCRC(buf + n, bufSize - n))
		{
			crcErrors++;
			UpdateStatus();
			return;
		} */
	}
	else if ((strcmp (toCall, "STATUS") == 0) || (strcmp (toCall, "BSTAT") == 0))
	{
		char * ptr ;
		buf [bufSize - 1] = '\0';
		sprintf (writebuf, "%s", buf + n);
		notprintable (writebuf);

		if ((ptr = strstr (writebuf, "B: ")) || 
		    (ptr = strstr (writebuf, "b:"))) {
			/* ByteCounter message */
			curByteRead  = dirBytes + fileBytes ;
			curByteWrite = atol (ptr + 4) ; 
				/* 4 is cheating for the 9600 Bd satellites */
			if (lastByteRead == 0) {
				writelog (LOG, writebuf);
			} else {
				long l1, l2, l3 = 100L;
				l1 = (curByteRead  - lastByteRead);
				l2 = (curByteWrite - lastByteWrite);
				if (l2 != 0L) l3 = (100L * l1) / l2;
				sprintf (writebuf, "%s    (%ld / %ld = %ld%%)",
				         buf + n,
				         (curByteRead - lastByteRead),
				         (curByteWrite - lastByteWrite),
				         l3);
				notprintable (writebuf);
				writelog (LOG, writebuf);
			}
			lastByteRead  = curByteRead;
			lastByteWrite = curByteWrite; 
		} else {
			writelog (LOG, writebuf);
		}
	}
	else if (strcmp (toCall, MyCall) == 0)
	{
		/* This packet is sent directly to ME */
		buf[bufSize] = '\0';
		sprintf  (writebuf, "%s", buf + n);
		notprintable (writebuf);
		writelog (LOG, writebuf);

		/* Check for negative response for a FILE FILL request */
		if ((protocol == PID_FILE) && ( 
		    (strncmp((char *)buf + n, "NO -2 ", 6) == 0) ||
		    (strncmp((char *)buf + n, "NO -3 ", 6) == 0)))
		{
			sprintf  (writebuf, "** File %lx not accesible, request cancelled", id);
			writelog (FILES, writebuf);
			RemoveDownloadFile (id);

			/* Make an new request. This is a optimization of */
			/* the protocol. I might wait for the PB: xxx list ???? */
			UpdateRequest (REQUEST_FILE);
		}

		/* Check for confirmation of a DIR FILL request */
		if (protocol == PID_DIRECTORY &&
		    strncmp ((char *)buf + n, "OK ", 3) == 0)
		{
			/* This information is currently not used ???? */
		}
	}
	else
	{
		buf [bufSize] = '\0';
		sprintf (writebuf, "%s", buf + n);
		notprintable (writebuf);
		writelog (LOG, writebuf);
	}

	UpdateStatus();
}

/*
 *	Input function when data is received on the input stream
 */
void GetFrame(void)
{
	if ((bufSize = recv (s_input, buf, MAXBUFFER, 0)) == -1) {
		perror ("recv") ;
		return ; }

		/* Check for read errors */
		if (bufSize == -1) stopFlag = 1;	/* Stop program */

		/* Process the received frame */
		time(&end_time);
		if (start_time == 0) start_time = end_time;
		ProcessFrame();
}

/*
 * Timeout has occured, flush the directory hole list.
 */
void TimeoutCb(void)
{
#ifdef FLUSH_FILES
	char fileName[40];
	int i;

	for (i = 0; i < MAXFILES; i++)
	{
		if (of[i].file != NULL)
		{
			/* write out the hole list */
			sprintf(fileName, "%x.hol", of[i].fileId);
			SaveHoleList(i, fileName);
		}
	}
#endif

	/* save the directory hole list */
	if (of[MAXFILES].firstHole != NULL)
		SaveHoleList(MAXFILES, "pfhdir.hol");
}

/*
 *	callback function when a another app calls us
 */
void CommandRequest(void)
{
	struct request request;

	if (recv(s_request, (char *)&request, sizeof(struct request), 0) == sizeof(struct request))
	{
		switch (request.type)
		{
			case REQ_TYPE_CANCEL:
				CancelFill();
				break;
			case REQ_TYPE_DIR:
/*				FillDirectory();	*/
				break;
			case REQ_TYPE_FILE:
/*				sprintf(id, "%lx", request.fileId);   */
/*				id = request.fileId;	*/
/*				FillFile(0);	*/
				break;
			case REQ_TYPE_REFILE:
				writelog (FILES, "download.req updated");
				/* If requester is idle check it */
/* ???? This hack is disabled.The request will be sent on next PB frame
				if (request_no == REQUEST_NONE)
					UpdateRequest (REQUEST_NONE);
*/
				break;
			case REQ_TYPE_REINIT:
				InitializeDownload() ;
				break ;
			case REQ_TYPE_QUIT:
				writelog (FILES, "Stop request received");
				stopFlag = 1;
				break;
		}
	}
	else
	{
		perror("recv - FillRequest");
	}
}

/*
 *	the user wants to exit this program
 */
void QuitCb(void)
{
	FILE *fp;
	struct tm *tm;
	int ave_rate = 0, i;


	/* save any open files */
	for (i = 0; i < MAXFILES; i++)
		SaveFile(i);

	/* save the directory hole list */
	if (of[MAXFILES].firstHole != NULL)
		SaveHoleList(MAXFILES, "pfhdir.hol");

	/* close the sockets */
	close(s_input);
	close(s_file);
	close(s_directory);

	/* write the statistics out */
	if (start_time != 0)
	{
		if ((fp = fopen("pb.log", "a")) != NULL)
		{
			if (end_time - start_time > 0)
				ave_rate = bytes / (end_time - start_time);

			tm = gmtime(&start_time);
			fprintf(fp, "%02d/%02d/%02d %02d:%02d:%02d - ",
				tm->tm_mday, tm->tm_mon + 1, tm->tm_year,
				tm->tm_hour, tm->tm_min, tm->tm_sec);

			tm = gmtime(&end_time);
			fprintf(fp, "%02d:%02d:%02d - total %d files %d dir %d tlm %d ave %d crc %d\n",
				tm->tm_hour, tm->tm_min, tm->tm_sec,
				bytes, fileBytes, dirBytes, tlmBytes, ave_rate, crcErrors);
	
			fclose(fp);
		}
	}
	ToMonitor("LOG", "PB exiting");
	ToMonitor("FINAL", "");
}


/*
 *	Construct a fill request for the directory.
 */
void FillDirectory(void)
{
	DIRREQUESTHEADER *request;
	DIRHOLEPAIR *hole;
	HOLE *h;
	unsigned char *buffer;
	int i;

	/* construct a hole list request */
	buffer = (unsigned char *)malloc (256);
	request = (DIRREQUESTHEADER *)buffer;
	request->flags     = 0x00 | VERSION | 0x10;
	request->blockSize = 244;

	hole = (DIRHOLEPAIR *)&buffer[sizeof(DIRREQUESTHEADER)];
	h = of[MAXFILES].firstHole;
	for (i = 0; i < 10 && i < of[MAXFILES].holes; i++)
	{
		hole->startTime = h->start;
		hole->endTime   = h->finish;
		h = h->nextHole;
		hole++;
	}

	if (sendto(s_directory, buffer,
			sizeof(DIRREQUESTHEADER) + (i * sizeof(DIRHOLEPAIR)),
			0, (struct sockaddr *)&dest, sizeof(dest)) == -1)
		perror ("send - fill dir") ;
	sprintf  (writebuf, "Fill Directory: fill %d holes.", i);
	id = -2 ;
	writelog (FILES, writebuf);
	UpdateStatus () ;
	free (buffer);
}


/*
 *	construct a fill request for a file
 */
void FillFile(int *reqlen)
{
	unsigned int fileId;
	char fileName[80];
	unsigned char * buffer;
	REQUESTHEADER *request;
	HOLEPAIR *hole;
	HOLE *h;
	FILE *fp;
	int f, i;

	fileId = id;
	if (fileId == 0) return;

	/* see if the file already downloaded */
	sprintf(fileName, "%x.dl", fileId);
	if ((fp = fopen(fileName, "r")) != NULL)
	{
		fclose(fp);
		sprintf  (writebuf, "%x is already downloaded", fileId);
		writelog (FILES, writebuf);
		return;
	}
	/* see if the file is in the current list of files */
	for (f = 0; f < MAXFILES; f++)
		if (of[f].fileId == fileId)
			break;

	if (f == MAXFILES)
	{
		/* not there - must save if this is in use */
		SaveFile(nextof);

		/* now use this slot */
		LoadFile(fileId, nextof);

		/* make sure we round robin the files */
		f = nextof;
		nextof = (nextof + 1) % MAXFILES;
	}
	buffer = (unsigned char *)malloc(256);
	request = (REQUESTHEADER *)buffer ;
	if (of[f].holes == 1 && of[f].firstHole->start  == 0 &&
				of[f].firstHole->finish == 0xFFFFFFFF)
	{
		request->flags     = 0x00 | VERSION | 0x10;
		request->fileId    = fileId;
		request->blockSize = 244;
	
		if (sendto(s_file, buffer, sizeof(REQUESTHEADER), 0,
	                   (struct sockaddr *)&dest, sizeof(dest)) == -1)
                        perror("send - req. file");
                /* Update the *reqlen with the request header size */
                *reqlen += sizeof (REQUESTHEADER);


		sprintf  (writebuf, "Fill File: %x send file.", fileId);
		writelog (FILES, writebuf);
	}
	else
	{
		request->flags     = 0x02 | VERSION | 0x10;
		request->fileId    = fileId;
		request->blockSize = 244;

		hole = (HOLEPAIR *)&buffer[*reqlen + sizeof(REQUESTHEADER)];
		h = of[f].firstHole;

		for (i = 0; i < 10 && i < of[f].holes; i++)
		{
			hole->offset     = h->start;
			hole->offset_msb = h->start >> 16;
			if ((h->finish - h->start) > 0xfffe) hole->length = 0xffff ;
			else hole->length = 1 + (h->finish - h->start);
			h = h->nextHole;
			hole++;
		}
                if (sendto(s_file, buffer,
                            sizeof(REQUESTHEADER) + (i * sizeof(HOLEPAIR)), 0,
                            (struct sockaddr *)&dest, sizeof(dest)) == -1)
                        perror("send - fill file");
                /* Update the *reqlen with the request size */
                *reqlen +=  sizeof (REQUESTHEADER) +
                           (sizeof (HOLEPAIR) * i);


		sprintf  (writebuf, "Fill File: %x fill %d holes.", fileId, i);
		writelog (FILES, writebuf);
	}
	free (buffer);

	/* Update the status line */
	UpdateStatus () ;
}



/*
 *	Cancel a Fill request		????
 */
void CancelFill(void)
{
#ifdef undefined
	switch (request_state)
	{
		case REQUEST_DIR:
			writelog (FILES, "Cancel Fill: directory");
			break;
		case REQUEST_FILE:
			sprintf  (writebuf, "Cancel Fill: file %lx", id);
			writelog (FILES, writebuf);
			break;
		default:
			break;
	}

	request_state = REQUEST_NONE;
#endif
}

void output_init(void) {
	char txt[100] ;
	
	if (monitors) {
		sprintf(txt, "<SATNAME>%s</SATNAME><MYCALL>%s</MYCALL>",
				SatName, MyPBCall);
		ToMonitor("INIT", txt);
	}
}

/* Read a request from the command pipe.
 * Valid requests are:
 *	monitor file	- start a monitoring program using 'file' as FIFO.
 *	dfill			- request a directory fill
 *	initdl			- re-initialize download control
 *	quit			- shut down and exit
 */

void ctrl_read(PIPE *pipe) {
	int i, count = pipe->count ; /* save starting index */
	char tag[TAGSIZE], tagvalue[TAGSIZE];

	i = pipe_read(pipe);
	if ((debugmask & DEBUG_FROM_FIFO) == DEBUG_FROM_FIFO) {
		strncpy (tag, pipe->buf, i) ;
		tag[i] = '\0' ;
		sprintf (debugline, "From FIFO:%s (%d from pos %d)", tag, i, count) ;
		WriteToLog (debugline) ; }
	if (i <= 0) {
		FD_CLR(pipe->fd, &ctrl_fds);
		close(pipe->fd);
		return;
	}
	if ((count = getAnyTag (pipe, tag)) != 0) {
		if (strcasecmp (tag, "QUIT/") == 0) {
			stopFlag = 1 ; }
		else if (strcasecmp (tag, "DFILL/") == 0) {
			FillDirectory() ; }
		else if (strcasecmp (tag, "INITDL/") == 0) {
			InitializeDownload() ; }
		else if (strcasecmp (tag, "MONITOR") == 0) {
			struct stat sbuf;
			int fd;
			MONITORS *new;
			
			if ((count = getTagWithValue (pipe, "FIFO", tagvalue, count)) == 0) return ;
			/* Try to get the closing tag */
			if ((count = getTag (pipe, tag, count)) != 0) {
				if (strcmp (tag, "/MONITOR") != 0) {
					if ((debugmask & DEBUG_IN_TAG) == DEBUG_IN_TAG) {
					sprintf (debugline, "CtrlRead : got tag: %s, wanted: /MONITOR", tag) ;
					WriteToLog (debugline) ; }
				}
			} else return ;
			new = monitors;
			while (new) { /* See if fifo already exists */
				if (strcmp(new->name,tagvalue) == 0) break;
				new = new->next ;
			}
			if (!new) { /* must create new fifo */
				if (pipe == fifo_pipe
					&& (stat(FIFOname,&sbuf) < 0 || !sbuf.st_mode&S_IFIFO)) {
					fprintf(stderr, "%s: %s not a pipe", pipe->name, tagvalue);
				} else if ((pipe != fifo_pipe && (fd=dup(pipe->fd)) < 0)
							|| (pipe == fifo_pipe && (fd = open(tagvalue,O_WRONLY|O_NDELAY))<0)) {
					fprintf(stderr, "%s: could not open pipe: %s", pipe->name, tagvalue);
				} else  {
					new = (MONITORS *)malloc(sizeof(MONITORS));
					new->name = strdup(tagvalue);
					new->next = monitors;
					new->fd = fd;
					monitors = new;
					output_init();
				}
			}
		} else {
			fprintf(stderr, "%s: empty monitor request ignored", pipe->name);
		}
		pipe_flush (pipe, count) ; /* Discard used characters */
	}
}


int main(int argc, char **argv)
{
	int	within = 0; /* scan config. file: inside found satellite parameters */
	int	among  = 0; /* scan config. file: among specific satellite parameters */
	int n, fifo_fd;
	char tmpMyCall	[100] ;
	char tmpMaxDays	[100] ;
	char tmpUDPPort	[100] ;
	char tmpKissLog	[100] ;

	fd_set		readfds;
	struct timeval	timeout;
	time_t		timeoutflush;
	struct		stat sbuf ;

	FILE		*paramf;
	char		*s, s1[100], s2[100];

	/* Get command line options but do not check them yet */
	while ((n = getopt(argc, argv, "s:m:n:M:U:k:v")) != -1) {
		switch (n) {
			case 's':
				strcpy(Satellite, optarg);
				break;
			case 'm':
				strcpy(MyCall, optarg);
				break;
			case 'n':
				strcpy(SatName, optarg);
				break;
			case 'M':
		      	strcpy(MaxDays, optarg);
				break;
			case 'U':
				strcpy(UDPPort, optarg);
		        break;
			case 'k':
				strcpy(KissLog, optarg);
		        break;
			case 'v':
				fprintf(stderr, "pb: %s\n", VERSION_STRING);
				return 0;
			case ':':
			case '?':
				fprintf(stderr, "%s\n", USAGE_STRING);
				return 1;
		}
	}

	/* Determine satellite name */
	if (Satellite [0] == '\0') { /* not on command line */
		if ((s = getenv("SATELLITE")) == NULL) {
			fprintf (stderr, "SATELLITE parameter is not defined\n");
			exit (1);
		} else 
			strcpy( Satellite, s);
	}

	/* Open and read the parameter file pb.conf */
	if ((paramf = fopen ("/etc/pbpg/pb.conf", "r")) == NULL) {
		perror ("pb - open pb.conf");
		exit (1);
	}
	n = 0;
	while (! feof (paramf)) {
		fscanf (paramf, "%100s %100s\n", s1, s2);
		n++;
		if (! strcasecmp (s1, "SATELLITE")) {
			if (!among) { among = 1;};
			if (!strcmp (Satellite, s2)) within = 1; 
			else if (within) {
				break;}
			continue;
		}
		if (! strcmp (s1, "SATNAME")) {
			if (within && SatName[0] == '\0') strcpy (SatName, s2); continue;}
		if (! strcmp (s1, "MYCALL")) {
			if (!among || within) strcpy (tmpMyCall, s2); continue;}
		if (! strcmp (s1, "MAXDAYS")) {
			if (!among || within) strcpy (tmpMaxDays, s2); continue;}
		if (! strcmp (s1, "UDPPORT")) {
			if (!among || within) strcpy (tmpUDPPort, s2); continue;}
		if (! strcmp (s1, "KISSLOG")) {
			if (!among || within) strcpy (tmpKissLog, s2); continue;}
		if (! strcmp (s1, "MAXIDLE")) {continue; /* PG parameter - ignore */}
		if (! strcmp (s1, "MAXWAIT")) {continue; /* PG parameter - ignore */}
		fprintf (stderr, "Error in config file line <%d>\n", n);
	}
	if (!within) {
		fprintf (stderr, "PB: Satellite %s not found, exiting\n", Satellite); 
		exit (1);}
	fclose (paramf);

	/* Copy config. file values if not defined on the command line */
	if (MyCall[0]  == '\0') strcpy (MyCall,  tmpMyCall) ;
	if (UDPPort[0] == '\0') strcpy (UDPPort, tmpUDPPort) ;
	if (MaxDays[0] == '\0') strcpy (MaxDays, tmpMaxDays) ;
	if (KissLog[0] == '\0') strcpy (KissLog, tmpKissLog) ;
	
	/*  Check if the needed parameters exist */
	if (MyCall [0] == '\0') {
		fprintf (stderr, "MYCALL parameter is not defined\n");
		exit (1);
	}

	/* Convert string values to numbers */
	if (MaxDays [0] != '\0') {
		maxDays = atoi(MaxDays);
		if (maxDays <= 0) maxDays = 5;
	}
	if (UDPPort [0] != '\0') {
		UdpPort = atoi (UDPPort);
		if (UdpPort < 1024) UdpPort = 5100;
	}

	/* Create the packet addresses to network format */
	strcat(Satellite, "-11");
	ax25_aton(MyCall, (struct full_sockaddr_ax25 *)&src);
	ax25_aton(Satellite, (struct full_sockaddr_ax25 *)&dest);
	strcpy (writebuf, MyCall) ;
	if ((s = strtok(writebuf, "-")) != NULL)
		strcpy (MyPBCall, s) ;
	else
		strcpy (MyPBCall, MyCall) ;
	MyPBCallLength = strlen (MyPBCall) ;

	InitializeDownload() ;

	/* install signal handlers */
	signal (SIGHUP, signal_reinit) ;	/* for download control re-initialization */
	signal(SIGPIPE, SIG_IGN) ;			/* ignore pipe signals */
	signal(SIGTERM, signal_term) ;		/*  terminate processing */

	id = -1;   /* Id of file to download */
	
	/* initialize the open files structure */
	for (n = 0; n < MAXFILES; n++)
	{
		of[n].fileId    = 0;
		of[n].file      = NULL;
		of[n].hdrSeen   = 0;
		of[n].fileSize  = 0;
		of[n].holes     = 0;
		of[n].firstHole = NULL;
	}

        if (ax25_config_load_ports () == 0) {
                fprintf (stderr, "no AX.25 ports configured\n") ;
                return 1 ; }

        if ((s_input = socket (PF_PACKET, SOCK_PACKET, htons (ETH_P_AX25))) == -1) {
                perror ("socket") ;
                return 1 ; }

        /* open up the AX25 datagram socket to send file requests */
        if ((s_file = socket(AF_AX25, SOCK_RAW, PID_FILE)) == -1)
        {
                perror("socket");
                return(1);
        }

        if (bind(s_file, (struct sockaddr *)&src, sizeof(src)) == -1)
        {
                perror("bind");
                return(1);
        }

        /* open up the AX25 datagram socket to send directory requests */
        if ((s_directory = socket(AF_AX25, SOCK_RAW, PID_DIRECTORY)) == -1)
        {
                perror("socket");
                return(1);
        }

        if (bind(s_directory, (struct sockaddr *)&src, sizeof(src)) == -1)
        {
                perror("bind");
                return(1);
        }

	/* open up a udp socket to receive file fill requests from other apps */
	memset((char *)&request_addr, 0, sizeof(request_addr));
	request_addr.sin_family      = AF_INET;
	request_addr.sin_addr.s_addr = INADDR_ANY;
	request_addr.sin_port        = htons(UdpPort);

	if ((s_request = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	{
		perror("socket - s_request");
	}
		
	if (bind(s_request, (struct sockaddr *)&request_addr, sizeof(request_addr)) == -1)
	{
		perror("bind - s_request");
	}

	/* Open the KissLog file */
	if (KissLog [0] != 0) {
		time_t timenow ;
		struct tm *tm ;
		time (&timenow) ;
		tm = gmtime (&timenow) ;
		sprintf (KissLog, "%02d%02d%02d%02d.kss", tm->tm_year, tm->tm_mon+1, tm->tm_mday, tm->tm_hour) ;
		if ((fpkiss = fopen(KissLog, "ab")) == NULL)  {
			perror ("Error opening the KissLog file");
		}
	}

	/* Load the Directory Hole List into memory */
	LoadDirectoryHoleList ();

	/* Load the download_list from the download.req file */
	CheckDownloadFile ();

    /* Open control FIFO */
	if (FIFOname[0] != '\0') {
		if (stat(FIFOname,&sbuf) < 0 || !(sbuf.st_mode&S_IFIFO)) {
		    fprintf (stderr, "Creating FIFO\n");
	    	/* Create the fifo. */
		    mknod(FIFOname, S_IFIFO|0666, 0);
		    chmod(FIFOname, 0666);
		}
		/* We need to open this RDWR to make select() work the
    	 * way we want. In particular we don't want select() to return 1 
		 * whenever there are no writers on the remote side of the command fifo.
		 * This guarantees that there is always at least one writer...
    	 */
		if ((fifo_fd = open(FIFOname, O_RDWR)) >= 0) {
    		fifo_pipe = (PIPE *)malloc(sizeof(PIPE));
			if (fifo_pipe) {
				fprintf(stderr, "Using fifo %s\n", FIFOname);
				pipe_init(pipes, "FIFO", fifo_fd, fifo_pipe, 1);
				FD_SET(fifo_fd, &ctrl_fds);
			} else {
				fprintf(stderr, "Could not open fifo file %s\n", FIFOname);
				fifo_fd = -1;
			}
		} else {
			fprintf(stderr, "Could not open fifo file %s\n", FIFOname);
	    	fifo_fd = -1;
		}
	} else {
		/* make sure to invalidate the fifo_fd if we don't open one. */
		fifo_fd = -1;
	}
	/* Close stdin, stdout, and stderr */
	//for (n = 0 ; n < 3 ; n++) close (n) ;
		
	/* Main loop. Start select on DataInput, UDPInput, FIFO and timeout */
	timeout.tv_sec  = TIMEOUT;
	timeout.tv_usec = 0;
	timeoutflush    = TIMEOUTFLUSH + time (0);
	while (! stopFlag) { 
		/* Prepare select */
		FD_ZERO (&readfds);
		if (fifo_fd != -1) FD_SET(fifo_fd, &readfds) ;
		FD_SET (s_input,        &readfds);
		FD_SET (s_request,      &readfds);
		n = select (32, &readfds, NULL, NULL, &timeout);
		if (n < 0) {
			if (errno == EINTR) continue ; /* restart the operation */
			else {
				perror ("pb, select");
				exit (1); }
		}

		/* Check if the TimeoutCb has to be run */
		if (time (0) > timeoutflush) {
			TimeoutCb ();	/* Call timeout process */
			/* Setup next time */
			timeoutflush = TIMEOUTFLUSH + time (0);
		}
		
		/* Reset timeout values */
		timeout.tv_sec  = TIMEOUT;
		timeout.tv_usec = 0;
		
		/* Chech which descriptors came out with some data */
		if (FD_ISSET (s_input, &readfds)) GetFrame ();
		if ((fifo_fd != -1) && (FD_ISSET (fifo_fd, &readfds))) {
			/* Input from FIFO */
			ctrl_read (fifo_pipe) ; }
		if (FD_ISSET (s_request, &readfds)) CommandRequest ();
	}

	/* Terminate the program */
	writelog (LOG, "***Program terminated normally***");
	writelog (FILES, "***Program terminated normally***");

	QuitCb ();		/* Flush buffers and close all files */
	return 0;
}

