Index: Evidence/Bridge.cc
===================================================================
--- Evidence/Bridge.cc	(revision 221)
+++ Evidence/Bridge.cc	(revision 221)
@@ -0,0 +1,179 @@
+/********************************************************************\
+
+  Bridge between two networks
+
+  Subscription to top-level server list DIS_DNS/SERVER_LIST not via
+  AddService() ensures AddService() only called from infoHandler(),
+  thus serialized by DIM and no Mutex is necessary.
+
+  Oliver Grimm, May 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "Bridge"
+#include "Evidence.h"
+
+#include <string>
+#include <vector>
+
+const int DEFAULT_PORT = 2505;
+
+using namespace std;
+
+// Class declaration
+class Bridge: public DimClient, public EvidenceServer {
+
+	struct Item {
+	  DimStampedInfo *DataItem;
+	  DimService *Service;
+	  char *Data;
+	};
+	vector<struct Item> List;
+
+	DimInfo *ServerList;
+	
+    void infoHandler();
+	void AddService(string, const char *);
+	void RemoveService(string);
+   
+  public:
+    Bridge(char *, int);
+    ~Bridge();
+}; 
+
+//
+// Constructor
+//
+Bridge::Bridge(char *Name, int Port): EvidenceServer(SERVER_NAME) {
+
+  // Set primary DIM network to subscribe from
+  DimClient::setDnsNode(Name, Port);
+
+  // Subsribe to top-level server list
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
+}
+
+//
+// Destructor: Delete all subscriptions and services
+//
+Bridge::~Bridge() {
+
+  while (List.size() != 0) RemoveService(List[0].DataItem->getName());  
+  delete ServerList;
+}
+
+//
+// Service subscription and repeating
+//
+void Bridge::infoHandler() {
+
+  DimInfo *I = getInfo();
+
+  // Check if service available
+  if (!ServiceOK(I)) return;
+
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(I->getString(), "+-!@");	
+	while (Token != NULL) {
+	  if (*I->getString() == '-' || *I->getString() == '!') {
+		RemoveService(string(Token)+"/SERVICE_LIST");
+	  }
+	  else AddService(string(Token)+"/SERVICE_LIST", "C");
+
+	  // Skip server IP address and process ID
+	  Token = strtok(NULL, "|");
+	  Token = strtok(NULL, "@");
+	}	
+	return;
+  }
+
+  // If service is SERVICE_LIST, scan and subscribe/unsubscribe to services
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+	char *Type, *Name = strtok(I->getString(), "+-!|");
+	while (Name != NULL) {
+	  // Only consider DIM services (not commands and RPCs)
+      if (((Type = strtok(NULL, "\n")) != NULL) &&
+		  (strstr(Type, "|CMD") == NULL) && (strstr(Type, "|RPC") == NULL)) {
+	    if (*I->getString() == '-' || *I->getString() == '!') RemoveService(Name);
+		else {
+		  Type[strlen(Type)-1] = '\0'; // Isolate service format
+		  AddService(Name, Type);
+		}
+	  }
+	  Name = strtok(NULL, "|");
+	}
+	return;
+  }
+
+  // Identify service and repeat to secondary DNS
+  for (int Service=0; Service<List.size(); Service++) if (I == List[Service].DataItem) {
+
+	// Ignores repeating DIS_DNS services
+	if (List[Service].Service == NULL) break;
+
+    // Copy service data
+    delete[] List[Service].Data;
+    List[Service].Data = new char [I->getSize()];
+    memcpy(List[Service].Data, I->getData(), I->getSize());
+
+	// Set new service properties and update service
+    List[Service].Service->setQuality(I->getQuality());
+    List[Service].Service->setTimestamp(I->getTimestamp(), I->getTimestampMillisecs());	
+	List[Service].Service->updateService(List[Service].Data, I->getSize());
+  }
+}
+
+//
+// Add service subscription
+//
+void Bridge::AddService(string Name, const char *Format) {
+
+  // Check if already subscribed to this service
+  for (int i=0; i<List.size(); i++) {
+	if (Name == List[i].DataItem->getName()) return;
+  }
+
+  // Create subscription and new service to secondary DNS (do not forward DIS_DNS services)
+  struct Item New;
+  
+  New.Data = NULL;
+  if (Name.find("DIS_DNS/") != string::npos) New.Service = NULL;
+  else New.Service = new DimService(Name.c_str(), (char *) Format, New.Data, 0);
+  New.DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
+  List.push_back(New);
+}
+
+
+//
+// Remove service from watch list
+//
+void Bridge::RemoveService(string Name) {
+
+  // Find service index
+  vector<struct Item>::iterator E;
+  for (E=List.begin(); E<List.end(); ++E) if (Name == (*E).DataItem->getName()) {
+	delete (*E).DataItem;
+	delete (*E).Service;
+	delete[] (*E).Data;
+	List.erase(E);
+  }
+}
+
+
+//	    
+// Main program
+//
+int main(int argc, char *argv[]) {
+
+  if (argc == 1) {
+	printf("Usage: %s <address of primary DNS> [port] (default port %d)\n", argv[0], DEFAULT_PORT);
+	exit(EXIT_FAILURE);
+  }
+          
+  // Static ensures calling of destructor by exit()
+  static Bridge Class(argv[1], argc>2 ? atoi(argv[2]) : DEFAULT_PORT);
+  
+  // Sleep until signal caught
+  pause();
+}
Index: Evidence/Config.cc
===================================================================
--- Evidence/Config.cc	(revision 216)
+++ Evidence/Config.cc	(revision 221)
@@ -48,5 +48,7 @@
 
     void rpcHandler();
-  
+	void AddItem(string, string, string);
+	string RemoveSpaces(string &);
+
   public:
     EvidenceConfig(const char *);
@@ -168,7 +170,5 @@
 
   stringstream In(FileContent), Out;
-  string Line;
-  struct Item New;
-  size_t Pos;
+  string Section, Item, Line, Data;
   
   // First clean up and concatenate lines
@@ -178,12 +178,8 @@
     // Replace all tabs by spaces
     while (Line.find("\t") != string::npos) Line[Line.find("\t")] = ' ';
-	// Remove leading spaces
-	while (!Line.empty() && isspace(Line[0])) Line.erase(0, 1);
-	// Remove trailing spaces
-	while (!Line.empty() && isspace(Line[Line.size()-1])) Line.erase(Line.size()-1);
 	// Remove empty lines
-	if (Line.empty()) continue;
-	// Concatenate if line ends with '+'
-    if (Line[Line.size()-1] != '+') Out << Line << endl;
+	if (RemoveSpaces(Line).empty()) continue;
+	// Concatenate if line ends with '\'
+    if (Line[Line.size()-1] != '\\') Out << Line << endl;
 	else Out << Line.erase(Line.size()-1);
   };
@@ -194,22 +190,61 @@
   // Interpret data
   while (getline(In, Line).good()) {
-    // Remove multiple spaces
-    while (Line.find("  ") != string::npos) Line.erase(Line.find("  "), 1);
-
-	// Find second space character
-    Pos = Line.find(" ", Line.find(" ") + 1);
-	if(Pos == string::npos) continue;
-
-	// Extract configuration name and data
-	New.Name = string(Line, 0, Pos);
-	New.Data = string(Line, Pos+1);
-
-	// Add to configuration list
-	if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()");
-	List.push_back(New);
-	if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()");
-  };
-}
-
+
+	// Check if current line is section heading (contains only [xxx])
+	if (Line.find('[')==0 && Line.find(']')==Line.size()-1) {
+	  // Add previous item to list (if any)
+	  AddItem(Section, Item, Data);
+	  Item.clear();
+	  Data.clear();
+	  // Extract new section name
+	  Section = Line.substr(1, Line.size()-2);
+	  continue;
+	}
+
+	// Check if current line contains equal sign (defines new item name)
+	if((Line.find('=')) != string::npos) {
+	  // Add previous item to list
+	  AddItem(Section, Item, Data);
+	  // Extract parameter name and data
+	  Item = string(Line, 0, Line.find('=')-1);
+	  Data = string(Line, Line.find('=')+1, string::npos);
+	}
+	else Data += ' ' + Line; // Concatenate lines
+  }
+  // Add last item
+  AddItem(Section, Item, Data);
+}
+
+// Add item to configuration list
+void EvidenceConfig::AddItem(string Section, string Parameter, string Data) {
+
+  // Clean up strings
+  RemoveSpaces(Parameter);
+  RemoveSpaces(Data);
+  if (Section.empty() || Parameter.empty() || Data.empty()) return;
+
+  // Prepare new item of configuration list
+  struct Item New;
+  New.Name = Section + ' ' + Parameter;
+  New.Data = Data;
+
+  // Add to configuration list
+  if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()");
+  List.push_back(New);
+  if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()");
+}
+
+// Removes whitespace
+string EvidenceConfig::RemoveSpaces(string &Text) {
+
+  // Remove leading spaces
+  while (!Text.empty() && isspace(Text[0])) Text.erase(0, 1);
+  // Remove trailing spaces
+  while (!Text.empty() && isspace(Text[Text.size()-1])) Text.erase(Text.size()-1);
+  // Remove multiple spaces
+  while (Text.find("  ") != string::npos) Text.erase(Text.find("  "), 1);
+  
+  return Text;
+}
 
 //	    
Index: Evidence/DColl.cc
===================================================================
--- Evidence/DColl.cc	(revision 216)
+++ Evidence/DColl.cc	(revision 221)
@@ -6,20 +6,11 @@
     server and writes these to the data file at every update.
   - One data file per day is generated.
-  - A history of events is kept within a ring buffer for each service. Each
-	entry will be the result of a conversion to double of the text 
-	written to the data file. Only if the new value has changed by a
-	minimum amout it will be added to the ring buffer. The history is
-	available via an rpc call.
-  - The history buffers are written to disk at program termination and
-    are tired to be read when adding a service.
   - The command 'DColl/Log' writes the associated string to the log file
   
-  Oliver Grimm, May 2010
+  Oliver Grimm, June 2010
 
 \********************************************************************/
 
 #define SERVER_NAME "DColl"
-
-#define MIN_HIST_SIZE 1024	// Minimum history buffer in bytes (> 3*sizeof(int) !)
 #define LOG_FILENAME "Evidence.log"
 
@@ -29,11 +20,5 @@
 #include <sstream>
 #include <vector>
-#include <iomanip>
-
-#include <math.h>
-#include <float.h>
 #include <sys/stat.h>
-#include <ctype.h>
-#include <sys/types.h>
 #include <regex.h>
 
@@ -43,18 +28,14 @@
 // Class declaration
 //
-class DataHandler:	public DimRpc, public DimClient, public DimBrowser,
-					public EvidenceServer {
+class DataHandler:	public DimClient, public EvidenceServer {
 
 	struct Item {
 	  DimStampedInfo *DataItem;
-	  char *Buffer;
-	  unsigned int HistSize;
-	  int Next;
-	  double LastValue;
-	  double MinAbsChange;
+	  bool Exclude;
 	};
 	vector<struct Item> List;
 	
 	DimCommand *LogCommand;
+	DimInfo *ServerList;
 		
     FILE *DataFile;
@@ -69,16 +50,11 @@
 	int RollOver;
 
-	int RegExCount;
-	regex_t *RegEx;
-	double *MinChange;
-    unsigned int *HistSize;
+	vector<regex_t> RegEx;
 	
     void infoHandler();
-    void rpcHandler();
     void commandHandler();
 	void AddService(string);
 	void RemoveService(string);
 	off_t FileSize(FILE *);
-	FILE *OpenHistFile(string, const char *);
    
   public:
@@ -90,5 +66,5 @@
 // Constructor
 //
-DataHandler::DataHandler(): DimRpc("ServiceHistory", "C", "C"), EvidenceServer(SERVER_NAME) {
+DataHandler::DataHandler(): EvidenceServer(SERVER_NAME) {
 
   // Initialization to prevent freeing unallocated memory 
@@ -113,7 +89,4 @@
     Message(FATAL, "Could not open log file (%s)", strerror(errno));
   }
-
-  // Provide logging command   
-  LogCommand = new DimCommand("DColl/Log", (char *) "C", this);
               
   // Create services for file sizes and data file name
@@ -126,44 +99,26 @@
   DataFilename = new DimService(SERVER_NAME "/CurrentFile", (char *) "");
 
-  // Count how many minimum change regular expressions are present
-  char *Change = GetConfig("items");
-  RegExCount = 0;
-  char *Token = strtok(Change, "\t ");
+  // Compile regular expressions
+  char *Exclude = GetConfig("exclude");
+  char *Token = strtok(Exclude, "\t ");
+  regex_t R;
+
   while (Token != NULL) {
-	RegExCount++;
+	int Ret = regcomp(&R, Token, REG_EXTENDED|REG_NOSUB);
+	if (Ret != 0) {
+	  char Err[200];
+	  regerror(Ret, &R, Err, sizeof(Err));
+	  Message(ERROR, "Error compiling regular expression '%s' (%s)", Token, Err);
+	}
+	else RegEx.push_back(R);
+
 	Token = strtok(NULL, "\t ");
   }
   
-  // Allocate memory for regular expressions, minimum change and history size
-  RegEx = new regex_t [RegExCount];
-  MinChange = new double [RegExCount];
-  HistSize = new unsigned int [RegExCount];
-
-  // Compile regular expressions and extract minimum change and history size
-  int Pos = 0;
-  for (int i=0; i<RegExCount; i++) {
-    int Len = strlen(Change+Pos) + 1;
-    Token = strtok(Change + Pos, ": \t");
-
-    int Ret = regcomp(&RegEx[i], Token, REG_EXTENDED|REG_NOSUB);
-	if (Ret != 0) {
-	  char ErrMsg[200];
-	  regerror(Ret, &RegEx[i], ErrMsg, sizeof(ErrMsg));
-	  RegExCount--;
-	  i--;
-	  Message(ERROR, "Error compiling regular expression '%s' (%s)", Token, ErrMsg);
-	}
-	else {
-	  if ((Token=strtok(NULL, ": \t")) != NULL) MinChange[i] = atof(Token);
-	  else MinChange[i] = 0;
-
-  	  if ((Token=strtok(NULL, "")) != NULL) HistSize[i] = atoi(Token)*1024;
-	  else HistSize[i] = 0;
-	}
-	Pos += Len;
-  }
-
-  // Subscribe to list of servers at DIS_DNS 
-  AddService("DIS_DNS/SERVER_LIST");
+  // Provide logging command   
+  LogCommand = new DimCommand("DColl/Log", (char *) "C", this);
+
+  // Subsribe to top-level server list (not via AddService() due to thread issue)
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
 }
 
@@ -174,4 +129,5 @@
 
   // Delete all DIM subscriptions
+  delete ServerList;
   while (List.size() != 0) RemoveService(List[0].DataItem->getName());
   
@@ -192,9 +148,5 @@
 
   // Free memory for regular expressions handling
-  for (int i=0; i<RegExCount; i++) {
-    regfree(&RegEx[i]);
-  }
-  delete[] MinChange;
-  delete[] RegEx;
+  for (int i=0; i<RegEx.size(); i++) regfree(&RegEx[i]);
 }
 
@@ -206,8 +158,8 @@
 void DataHandler::infoHandler() {
 
-  DimInfo *Info = getInfo();
+  DimInfo *I = getInfo();
 
   // Check if service available
-  if (!ServiceOK(Info)) return;
+  if (!ServiceOK(I)) return;
 
   //
@@ -217,6 +169,6 @@
   
   // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
-  if (strcmp(Info->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
-	char *Token = strtok(Info->getString(), "+-!@");	
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(I->getString(), "+-!@");	
 	while (Token != NULL) {
 	  AddService(string(Token)+"/SERVICE_LIST"); // 'add' also for '-' and '!'
@@ -229,6 +181,6 @@
   // If service is SERVICE_LIST of any server, scan all services.
   // Subscribe to all services (but not to commands and RPCs)
-  if (strstr(Info->getName(), "/SERVICE_LIST") != NULL) {
-	char *Name = strtok(Info->getString(), "+-!|");
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+	char *Name = strtok(I->getString(), "+-!|");
 	while (Name != NULL) {
 	  // Check if item is a service
@@ -242,5 +194,5 @@
 
   //
-  // ====== Part B: Handle opening data files ===
+  // ====== Part B: Handle opening of data files ===
   //
 
@@ -297,18 +249,17 @@
 
   // Identify index of service
-  int Service;
-  for (Service=0; Service<List.size(); Service++) if (Info == List[Service].DataItem) break;
-  if (Service == List.size()) return;  // Service not found
-
-  // If negative value for absolute change, do not write to file
-  if (List[Service].MinAbsChange >= 0) {
+  for (int Service=0; Service<List.size(); Service++) if (I == List[Service].DataItem) {
+
+	// Service excluded from writing?
+	if (List[Service].Exclude) return;
+	
 	// Write data header
-	time_t RawTime = Info->getTimestamp();
+	time_t RawTime = I->getTimestamp();
 	struct tm *TM = localtime(&RawTime);
 
-	fprintf(DataFile, "%s %d %d %d %d %d %d %d %lu ", Info->getName(), TM->tm_year+1900, TM->tm_mon+1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec, Info->getTimestampMillisecs(), Info->getTimestamp());
+	fprintf(DataFile, "%s %d %d %d %d %d %d %d %d %lu ", I->getName(), I->getQuality(), TM->tm_year+1900, TM->tm_mon+1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec, I->getTimestampMillisecs(), I->getTimestamp());
 
 	// Translate data into ASCII
-	char *Text = EvidenceServer::ToString(Info);
+	char *Text = EvidenceServer::ToString(I);
 
 	if (Text != NULL) {
@@ -343,73 +294,4 @@
 	}  
   } // Check for disk writing
-
-  //
-  // ====== Part D: Handle history service ===
-  //
-
-  if (Info->getSize()==0 || Info->getTimestamp()==0) return;
-
-  // Check if data should be added to history buffer
-  if (strcmp(Info->getFormat(),"C") != 0 && strlen(Info->getFormat())==1) {
-	// Calculate sum of all number in array
-	char *Text = EvidenceServer::ToString(Info);
-	char *Token = strtok(Text, " ");
-	double Sum = 0;
-	while (Token != NULL) {
-	  Sum += atof(Token);
-      Token = strtok(NULL, " ");
-	}
-	free(Text);
-	// Minimum change?
-	if (fabs(Sum-List[Service].LastValue) < fabs(List[Service].MinAbsChange)) return;
-	List[Service].LastValue = Sum;
-  }
-  
-  // Check if data fits into buffer
-  if (List[Service].HistSize < Info->getSize() + sizeof(int)+ 2*sizeof(EvidenceHistory::Item)) return;
-
-  int Size = Info->getSize() + 2*sizeof(EvidenceHistory::Item), Next = List[Service].Next;
-  void *WrapPos = NULL;
-  char *Buffer = List[Service].Buffer;
-  int Oldest = *(int *) Buffer;
-
-  // Check if buffer wrap-around (write wrap mark after Oldest is adjusted)
-  if (Next + Size >= List[Service].HistSize) {
-    WrapPos = Buffer + Next;
-    Next = 4;
-  }
-
-  // Adapt pointer to oldest entry
-  while ((Oldest < Next + Size) && 
-  		 (Oldest + *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int) > Next)) {
-	// Check for wrap-around
-	if (memcmp(Buffer + Oldest, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark)) == 0) {
-	  Oldest = 4;
-	  continue;
-	}
-	// Check if end marker reached, then only one event fits buffer
-  	if (memcmp(Buffer + Oldest, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark)) == 0) {
-	  Oldest = Next;
-	  break;
-	}
-	// Move to next entry
-    Oldest += *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int);
-  }
-  // Update pointer in buffer
-  *(int *) Buffer = Oldest;
-
-  // Write wrap mark if necessary
-  if (WrapPos != NULL) memcpy(WrapPos, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark));
-
-  // Copy data into ring buffer
-  *((int *) (Buffer + Next)) = Info->getTimestamp();
-  *((int *) (Buffer + Next + sizeof(int))) = Info->getSize();
-  memcpy(Buffer + Next + 2*sizeof(int), Info->getData(), Info->getSize());
-
-  // Adjust pointer for next entry and write end marker to buffer
-  Next += Info->getSize() + sizeof(EvidenceHistory::Item);
-  memcpy(Buffer + Next, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark));
-  
-  List[Service].Next = Next;
 }
 
@@ -454,45 +336,9 @@
 
 //
-// Implementation of history buffer distribution
-//
-void DataHandler::rpcHandler() {
-
-  // Search for history buffer
-  for (int i=0; i<List.size(); i++) {
-    if (strcmp(List[i].DataItem->getName(), getString()) == 0) {
-	  setData((void *) List[i].Buffer, List[i].HistSize);
-	  return;
-	}
-  }
-
-  // Try to open history file if not found in memory
-  FILE *File = OpenHistFile(getString(), "rb");
-  if (File == NULL) {
-    setData(NULL, 0);
-	return;
-  }
-
-  // Read history file
-  off_t Size = FileSize(File);
-  if (Size != -1) {
-	char *Buffer = new char [Size-sizeof(int)];
-	fseek(File, sizeof(int), SEEK_SET);
-	fread(Buffer, sizeof(char), Size-sizeof(int), File);
-	if (ferror(File) != 0) {
-	  Message(WARN, "Error reading history file '%s' in rpcHandler()", getString());
-	  setData(NULL, 0);		// Default response
-	}
-	else setData((void *) Buffer, Size);
-	delete[] Buffer;
-  }
-  
-  if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in rpcHandler()", getString());
-}
-
-
-//
 // Add service to watch list
 //
 void DataHandler::AddService(string Name) {
+
+  struct Item New;
 
   // Check if already subscribed to this service
@@ -500,36 +346,9 @@
 	if (Name == List[i].DataItem->getName()) return;
   }
-
-  // Set minimum required change by comparing to regular expressions
-  struct Item New;
-  New.MinAbsChange = 0;
-  New.HistSize = 0;
-  for (int i=0; i<RegExCount; i++) {
-    if (regexec(&RegEx[i], Name.c_str(), (size_t) 0, NULL, 0) == 0) {
-	  New.MinAbsChange = MinChange[i];
-	  if (HistSize[i] != 0) New.HistSize = HistSize[i];
-	}
-  }
-
-  // At least 3*sizeof(int)
-  if (New.HistSize < MIN_HIST_SIZE) New.HistSize = MIN_HIST_SIZE;
-
-  // Create history service
-  New.Buffer = new char [New.HistSize];
-  memset(New.Buffer, 0, New.HistSize);
-  *(int *) New.Buffer = 4;
-  New.Next = 4;
-  New.LastValue = DBL_MAX;
-
-  // Load history buffer from file if existing
-  FILE *File = OpenHistFile(Name, "rb");
-  if (File != NULL) {
-    // Only load if current buffer size is equal or larger
-    if (FileSize(File) <= New.HistSize*sizeof(char)+sizeof(New.Next) && FileSize(File) != -1) {
-      fread(&New.Next, sizeof(New.Next), 1, File);
-      fread(New.Buffer, sizeof(char), New.HistSize, File);
-	  if (ferror(File) != 0) Message(WARN, "Error reading history file '%s' in AddService()", Name.c_str());
-      if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in AddService()", Name.c_str());;
-	}
+  
+  // Should service be ignored?
+  New.Exclude = false;
+  for (int i=0; i<RegEx.size(); i++) {
+    if (regexec(&RegEx[i], Name.c_str(), (size_t) 0, NULL, 0) == 0) New.Exclude = true;
   }
 
@@ -537,5 +356,4 @@
   New.DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
 
-  // Add item to list
   List.push_back(New);
 }
@@ -550,18 +368,5 @@
   vector<struct Item>::iterator E;
   for (E=List.begin(); E<List.end(); ++E) if (Name == (*E).DataItem->getName()) {
-	// Delete subscription first so handler and not called anymore
-	delete (*E).DataItem;
-
-	// Save history buffer
-	FILE *File = OpenHistFile(Name, "wb");
-	if (File != NULL) {
-      fwrite(&(*E).Next, sizeof((*E).Next), 1, File);
-      fwrite((*E).Buffer, sizeof(char), (*E).HistSize, File);
-	  if (ferror(File) != 0) Message(WARN, "Error writing history file '%s' in RemoveService()", Name.c_str());
-      if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in RemoveService()", Name.c_str());;
-	}
-	
-	// Delete history service and free memory
-    delete[] (*E).Buffer;
+	delete (*E).DataItem;	
 	List.erase(E);
   }
@@ -579,22 +384,7 @@
 	 return -1;
   }
-
   return FileStatus.st_size;
 }
 
-//
-// Open file for service history
-//
-FILE *DataHandler::OpenHistFile(string Service, const char *Mode) {
-
-  string Dir = string(BaseDir) + "/Histories/";
-
-  // Create directory if not yet existing
-  if(mkdir(Dir.c_str(), S_IRWXU|S_IRWXG)==-1 && errno!=EEXIST) return NULL;
-
-  // Replace all '/' by '_' in string and open file
-  for (int i=0; i<Service.size(); i++) if (Service[i] == '/') Service[i] = '_';
-  return fopen((Dir + Service).c_str(), Mode);
-}
 
 //	    
@@ -604,5 +394,5 @@
         
   // Static ensures calling of destructor by exit()
-  static DataHandler Data;
+  static DataHandler DataInstance;
   
   // Sleep until signal caught
Index: Evidence/Edd/Edd.cc
===================================================================
--- Evidence/Edd/Edd.cc	(revision 216)
+++ Evidence/Edd/Edd.cc	(revision 221)
@@ -73,4 +73,5 @@
   ShowAsTime = false;
   setFrame(false);
+  setAttribute(Qt::WA_DeleteOnClose);
   
   // Connect to DIM handler
@@ -1145,4 +1146,5 @@
 
   QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
 
   // Status display
@@ -1171,4 +1173,5 @@
 
   QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
   EddLineDisplay *Line;
   
@@ -1203,4 +1206,5 @@
 TP_Feedback::TP_Feedback() {
 
+  setAttribute(Qt::WA_DeleteOnClose);
   QGridLayout *Layout = new QGridLayout(this);
   EddLineDisplay *Line;
@@ -1237,4 +1241,5 @@
 void TP_Feedback::FeedbackDetails() {
 
+  setAttribute(Qt::WA_DeleteOnClose);
   EddLineDisplay *Line;
   QWidget *Widget = new QWidget();
@@ -1257,4 +1262,5 @@
 TP_DAQ::TP_DAQ() {
 
+  setAttribute(Qt::WA_DeleteOnClose);
   QGridLayout *Layout = new QGridLayout(this);
 
@@ -1403,4 +1409,5 @@
 TP_Evidence::TP_Evidence() {
 
+  setAttribute(Qt::WA_DeleteOnClose);
   QGridLayout *Layout = new QGridLayout(this);
   EddLineDisplay *Line;
@@ -1473,13 +1480,14 @@
   setCentralWidget(Central);
   setStatusBar(new QStatusBar(this));
-  setGeometry(100, 100, 800, 650);
   setWindowTitle("Edd - Evidence Data Display");
 
   // Arrangement in tabs
   TabWidget = new QTabWidget(Central);
+  TabWidget->setTabsClosable(true);
+  connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int)));
   TabWidget->addTab(new TP_DAQ, "Event scope");
-  TabWidget->addTab(new TP_Bias, "&Bias");
-  TabWidget->addTab(new TP_Feedback, "&Feedback");
-  TabWidget->addTab(new TP_Environment, "&Environment");
+  TabWidget->addTab(new TP_Bias, "Bias");
+  TabWidget->addTab(new TP_Feedback, "Feedback");
+  TabWidget->addTab(new TP_Environment, "Environment");
   TabWidget->addTab(new TP_Evidence, "Evidence");
 
@@ -1494,4 +1502,5 @@
 
   // Show main window
+  resize(TabWidget->sizeHint()*1.1);
   show();
 }  
@@ -1540,4 +1549,34 @@
 }
 
+// Open tab as separate window
+void GUI::DetachTab(int Tab) {
+
+  QWidget *W = NULL;
+  QMainWindow *M = new QMainWindow;
+
+  M->setCentralWidget(new QWidget(M));
+  M->setStatusBar(new QStatusBar(M));
+
+  switch(Tab) {
+	case 0:	W = new TP_DAQ; break;
+	case 1: W = new TP_Bias; break;
+	case 2: W = new TP_Feedback; break;
+	case 3: W = new TP_Environment; break;
+	case 4: W = new TP_Evidence; break;
+	default: break;
+  }
+
+  if (W == NULL) {
+    delete M->centralWidget();
+	delete M;
+	return;
+  }
+
+  W->setParent(M);
+  M->resize(size());
+  M->setWindowTitle("Edd - " + TabWidget->tabText(Tab));
+  M->show();
+}
+
 // Quit application when clicking close button on window
 void GUI::closeEvent(QCloseEvent *) {
Index: Evidence/Edd/Edd.h
===================================================================
--- Evidence/Edd/Edd.h	(revision 216)
+++ Evidence/Edd/Edd.h	(revision 221)
@@ -374,4 +374,5 @@
     void MenuAbout();
     void MenuNewHistory();
+	void DetachTab(int);
 };
 
Index: Evidence/Evidence.cc
===================================================================
--- Evidence/Evidence.cc	(revision 216)
+++ Evidence/Evidence.cc	(revision 221)
@@ -174,5 +174,6 @@
   // Terminate if not successful
   if (!EvidenceServer::ServiceOK(&Config)) {
-    Message(FATAL, "Configuration server unreachable, can't get '%s'", Item.c_str());
+    if (Default == NULL) Message(FATAL, "Configuration server unreachable, can't get '%s'", Item.c_str());
+	Result = (char *) Default;
   }
 
Index: Evidence/History.cc
===================================================================
--- Evidence/History.cc	(revision 221)
+++ Evidence/History.cc	(revision 221)
@@ -0,0 +1,354 @@
+/********************************************************************\
+
+  History server of the Evidence Control System
+  
+  - Subscribes to all services and keeps a ring buffer for each service. 
+  - Data added to the buffer only if changed by minimum amount given by
+    configuration information. 
+  - The history is available via an rpc call.
+  - The history buffers are written to disk at program termination and
+    are tried to be read when adding a service.
+  - The buffer should hold at least REQUEST_NUM entries (of the size currently
+    handled in infoHandler()), but should not be larger than MAX_SIZE_KB. 
+  Oliver Grimm, June 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "History"
+#include "Evidence.h"
+
+const int MIN_SIZE_KB = 50;		// Minimum and maximum history buffer in kByte (> 3*sizeof(int) !)
+const int MAX_SIZE_KB = 2000;
+const int REQUEST_NUM = 1000;	// Requested number of entries in each history buffer
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <math.h>
+#include <float.h>
+#include <sys/stat.h>
+
+using namespace std;
+
+//
+// Class declaration
+//
+class History:	public DimRpc, public DimClient, public EvidenceServer {
+
+	struct Item {
+	  DimStampedInfo *DataItem;
+	  vector <char> Buffer;
+	  int Next;
+	  double LastValue;
+	  double MinAbsChange;
+	};
+	vector<struct Item> List;
+	
+	DimInfo *ServerList;
+	char *Directory;
+	char *Change;
+
+    void infoHandler();
+    void rpcHandler();
+	void AddService(string);
+	void RemoveService(string);
+	off_t FileSize(FILE *);
+	FILE *OpenFile(string, const char *);
+   
+  public:
+    History(char *);
+    ~History();
+}; 
+
+
+// Constructor
+History::History(char *Dir): DimRpc("ServiceHistory", "C", "C"),
+							 EvidenceServer(SERVER_NAME),
+							 Directory(Dir) {
+
+  // List of minimum required change for addition to history buffer 
+  Change = GetConfig("minchange", " ");
+
+  // Subscribe to top-level server list
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
+}
+
+
+// Destructor deletes all DIM subscriptions
+History::~History() {
+
+  delete ServerList;
+  while (List.size() != 0) RemoveService(List[0].DataItem->getName());
+}
+
+
+// Implementation of data handling
+void History::infoHandler() {
+
+  DimInfo *I = getInfo();
+
+  // Check if service available
+  if (!ServiceOK(I)) return;
+
+  // ====== Part A: Handle service subscriptions ===
+  
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(I->getString(), "+-!@");	
+	while (Token != NULL) {
+	  AddService(string(Token)+"/SERVICE_LIST"); // 'add' also for '-' and '!'
+	  Token = strtok(NULL, "|"); // Skip server IP address
+	  Token = strtok(NULL, "@");
+	}	
+	return;
+  }
+
+  // If service is SERVICE_LIST of any server, scan all services.
+  // Subscribe to all services (but not to commands and RPCs)
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+	char *Name = strtok(I->getString(), "+-!|");
+	while (Name != NULL) {
+	  // Check if item is a service
+	  char *Type = strtok(NULL, "\n");
+	  if (Type == NULL) return; // for safety, should not happen
+      if (strstr(Type, "|CMD")==NULL && strstr(Type, "|RPC")==NULL) AddService(Name); // 'add' also for '-' and '!'
+	  Name = strtok(NULL, "|");
+	}
+	return;
+  }
+
+  // ====== Part B: Handle history service ===
+
+  // Identify index of service
+  int Service;
+  for (Service=0; Service<List.size(); Service++) if (I == List[Service].DataItem) break;
+  if (Service == List.size()) return;  // Service not found
+
+  // Ignore empty services
+  if (I->getSize()==0 || I->getTimestamp()==0) return;
+
+  // Resize buffer if necessary
+  if (List[Service].Buffer.size() < REQUEST_NUM*I->getSize()) {
+	if (REQUEST_NUM*I->getSize() < MAX_SIZE_KB*1024) List[Service].Buffer.resize(REQUEST_NUM*I->getSize());
+  }
+
+  // If data is number of single type, a minumum change might be requested before addind to history
+  if (strcmp(I->getFormat(),"C") != 0 && strlen(I->getFormat())==1) {
+	// Calculate sum of all number in array
+	char *Text = EvidenceServer::ToString(I);
+	char *Token = strtok(Text, " ");
+	double Sum = 0;
+	while (Token != NULL) {
+	  Sum += atof(Token);
+      Token = strtok(NULL, " ");
+	}
+	free(Text);
+
+	// Minimum change?
+	if (fabs(Sum-List[Service].LastValue) < fabs(List[Service].MinAbsChange)) return;
+	List[Service].LastValue = Sum;
+  }
+  
+  // Check if data fits into buffer
+  if (List[Service].Buffer.size() < I->getSize() + sizeof(int)+ 2*sizeof(EvidenceHistory::Item)) return;
+
+  int Size = I->getSize() + 2*sizeof(EvidenceHistory::Item), Next = List[Service].Next;
+  void *WrapPos = NULL;
+  char *Buffer = &List[Service].Buffer[0];
+  int Oldest = *(int *) Buffer;
+
+  // Check if buffer wrap-around (write wrap mark after Oldest is adjusted)
+  if (Next + Size >= List[Service].Buffer.size()) {
+    WrapPos = Buffer + Next;
+    Next = 4;
+  }
+
+  // Adapt pointer to oldest entry
+  while ((Oldest < Next + Size) && 
+  		 (Oldest + *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int) > Next)) {
+	// Check for wrap-around
+	if (memcmp(Buffer + Oldest, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark)) == 0) {
+	  Oldest = 4;
+	  continue;
+	}
+	// Check if end marker reached, then only one event fits buffer
+  	if (memcmp(Buffer + Oldest, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark)) == 0) {
+	  Oldest = Next;
+	  break;
+	}
+	// Move to next entry
+    Oldest += *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int);
+  }
+  // Update pointer in buffer
+  *(int *) Buffer = Oldest;
+
+  // Write wrap mark if necessary
+  if (WrapPos != NULL) memcpy(WrapPos, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark));
+
+  // Copy data into ring buffer
+  *((int *) (Buffer + Next)) = I->getTimestamp();
+  *((int *) (Buffer + Next + sizeof(int))) = I->getSize();
+  memcpy(Buffer + Next + 2*sizeof(int), I->getData(), I->getSize());
+
+  // Adjust pointer for next entry and write end marker to buffer
+  Next += I->getSize() + sizeof(EvidenceHistory::Item);
+  memcpy(Buffer + Next, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark));
+  
+  List[Service].Next = Next;
+}
+
+
+// Implementation of history buffer distribution
+void History::rpcHandler() {
+
+  // Search for history buffer
+  for (int i=0; i<List.size(); i++) {
+    if (strcmp(List[i].DataItem->getName(), getString()) == 0) {
+	  setData((void *) &List[i].Buffer[0], List[i].Buffer.size());
+	  return;
+	}
+  }
+
+  // Try to open history file if not found in memory
+  FILE *File = OpenFile(getString(), "rb");
+  if (File == NULL) {
+    setData(NULL, 0);
+	return;
+  }
+
+  // Read history file
+  off_t Size = FileSize(File);
+  if (Size != -1) {
+	char *Buffer = new char [Size-sizeof(int)];
+	fseek(File, sizeof(int), SEEK_SET);
+	fread(Buffer, sizeof(char), Size-sizeof(int), File);
+	if (ferror(File) != 0) {
+	  Message(WARN, "Error reading history file '%s' in rpcHandler()", getString());
+	  setData(NULL, 0);		// Default response
+	}
+	else setData((void *) Buffer, Size);
+	delete[] Buffer;
+  }
+  
+  if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in rpcHandler()", getString());
+}
+
+
+//
+// Add service to watch list
+//
+void History::AddService(string Name) {
+
+  // Check if already subscribed to this service
+  for (int i=0; i<List.size(); i++) {
+	if (Name == List[i].DataItem->getName()) return;
+  }
+
+  struct Item New;
+  New.LastValue = DBL_MAX;
+
+  // Set minimum required change if given in configuratrion
+  char *Pnt = strstr(Change, Name.c_str());
+  if (Pnt != NULL && *(Pnt+Name.size()) == ':') New.MinAbsChange = atof(Pnt+Name.size()+1);
+  else New.MinAbsChange = 0;
+
+  // Load history buffer from file if existing
+  FILE *File = OpenFile(Name, "rb");
+  off_t Size;
+
+  if (File != NULL && (Size = FileSize(File)) != -1) {
+	if (Size > New.Buffer.size()) New.Buffer.resize(Size);
+    fread(&New.Next, sizeof(New.Next), 1, File);
+    fread(&New.Buffer[0], sizeof(char), Size, File);
+	if (ferror(File) != 0) {
+	  Message(WARN, "Error reading history file '%s' in AddService()", Name.c_str());
+	  New.Buffer.clear();
+	}
+    if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in AddService()", Name.c_str());;
+  }
+
+  // If no buffer loaded, allocate empty buffer
+  if (New.Buffer.empty()) {
+	New.Buffer.resize(MIN_SIZE_KB*1024);
+	memset(&New.Buffer[0], 0, New.Buffer.size());
+	*(int *) &New.Buffer[0] = 4;
+	New.Next = 4;
+  }
+  
+  // Subscribe to service
+  New.DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
+
+  List.push_back(New);
+}
+
+
+//
+// Remove service from watch list
+//
+void History::RemoveService(string Name) {
+
+  // Find service index
+  vector<struct Item>::iterator E;
+  for (E=List.begin(); E<List.end(); ++E) if (Name == (*E).DataItem->getName()) {
+	// Delete subscription first so handler and not called anymore
+	delete (*E).DataItem;
+
+	// Save history buffer
+	FILE *File = OpenFile(Name, "wb");
+	if (File != NULL) {
+      fwrite(&(*E).Next, sizeof((*E).Next), 1, File);
+      fwrite(&(*E).Buffer[0], sizeof(char), (*E).Buffer.size(), File);
+	  if (ferror(File) != 0) Message(WARN, "Error writing history file '%s' in RemoveService()", Name.c_str());
+      if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in RemoveService()", Name.c_str());;
+	}
+	
+	// Delete history service and free memory
+    //delete[] (*E).Buffer;
+	List.erase(E);
+  }
+}
+
+//
+// Determine size of file in kB
+//
+off_t History::FileSize(FILE *File) {
+
+  struct stat FileStatus;
+
+  if (fstat(fileno(File), &FileStatus) == -1) {
+     Message(WARN, "Could not determine size of file (%s)", strerror(errno));
+	 return -1;
+  }
+
+  return FileStatus.st_size;
+}
+
+//
+// Open file for service history
+//
+FILE *History::OpenFile(string Service, const char *Mode) {
+
+  // Create directory if not yet existing
+  if(mkdir(Directory, S_IRWXU|S_IRWXG)==-1 && errno!=EEXIST) return NULL;
+
+  // Replace all '/' by '_' in string and open file
+  for (int i=0; i<Service.size(); i++) if (Service[i] == '/') Service[i] = '_';
+  return fopen((Directory + Service).c_str(), Mode);
+}
+
+//	    
+// Main program
+//
+int main(int argc, char *argv[]) {
+
+  if (argc != 2) {
+	printf("Usage: %s <History-Directory>\n", argv[0]);
+	exit(EXIT_FAILURE);
+  }
+
+  // Static ensures calling of destructor by exit()
+  static History Hist(argv[1]);
+  
+  // Sleep until signal caught
+  pause();
+}
Index: Evidence/Makefile
===================================================================
--- Evidence/Makefile	(revision 216)
+++ Evidence/Makefile	(revision 221)
@@ -4,9 +4,11 @@
 PROG2=DColl
 PROG3=Alarm
+PROG4=Bridge
+PROG5=History
 
 CPPFLAGS += -I$(DIMDIR)/dim/
 LDLIBS += -lpthread $(DIMDIR)/linux/libdim.a
 
-all: $(PROG1) $(PROG2) $(PROG3)
+all: $(PROG1) $(PROG2) $(PROG3) $(PROG4) $(PROG5)
 	 
 $(PROG1): $(PROG1).o Evidence.o
@@ -16,7 +18,13 @@
 $(PROG3): $(PROG3).o Evidence.o
 
+$(PROG4): $(PROG4).o Evidence.o
+
+$(PROG5): $(PROG5).o Evidence.o
+
 clean:
 	@rm -f $(PROG1) $(PROG1).o
 	@rm -f $(PROG2) $(PROG2).o
 	@rm -f $(PROG3) $(PROG3).o
+	@rm -f $(PROG4) $(PROG4).o
+	@rm -f $(PROG5) $(PROG5).o
 	@rm -f Evidence.o	
Index: Evidence/readme.txt
===================================================================
--- Evidence/readme.txt	(revision 216)
+++ Evidence/readme.txt	(revision 221)
@@ -31,16 +31,21 @@
 			yet in memory, reading from history file is tried. Improved error handling of
 			history files.
-28/5/2010	Changed name of 'State' service to 'Message' to better reflect its functionality. Added client information to log file entries.			
+28/5/2010	Changed name of 'State' service to 'Message' to better reflect its functionality.
+			Added client information to log file entries.			
+30/5/2010	Created Bridge server that repeats services from one DNS to another.
+			Service quality now also written to slow data file.
+31/5/2010	Configuration file format now follows semi-standard INI format.
+7/6/2010	Separated History service from DColl.
 
 
-Preliminary firewall settings (rule 9 for DIM, rule 10 for X11 over ssh)
+Preliminary firewall settings on eth-vme02 (rule 5 for DIM, rule 6 for X11 over ssh)
 
 Chain INPUT (policy ACCEPT)
 num  target     prot opt source               destination
-1    RH-Firewall-1-INPUT  all  --  anywhere             anywhere
+1    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
 
 Chain FORWARD (policy ACCEPT)
 num  target     prot opt source               destination
-1    RH-Firewall-1-INPUT  all  --  anywhere             anywhere
+1    RH-Firewall-1-INPUT  all  --  0.0.0.0/0            0.0.0.0/0
 
 Chain OUTPUT (policy ACCEPT)
@@ -49,13 +54,9 @@
 Chain RH-Firewall-1-INPUT (2 references)
 num  target     prot opt source               destination
-1    ACCEPT     icmp --  anywhere             anywhere            icmp any
-2    ACCEPT     ipv6-crypt--  anywhere             anywhere
-3    ACCEPT     ipv6-auth--  anywhere             anywhere
-4    ACCEPT     udp  --  anywhere             224.0.0.251         udp dpt:5353
-5    ACCEPT     udp  --  anywhere             anywhere            udp dpt:ipp
-6    ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED
-7    ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:ssh
-8    ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpt:sieve
-9    ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpts:5100:x11
-10   ACCEPT     tcp  --  anywhere             anywhere            state NEW tcp dpts:x11:6063
-11   REJECT     all  --  anywhere             anywhere            reject-with icmp-host-prohibited
+1    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 255
+2    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
+3    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
+4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:2000
+5    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpts:5100:6000 state NEW
+6    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpts:6000:6063 state NEW
+7    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
