Sophie

Sophie

distrib > * > 2008.0 > x86_64 > by-pkgid > 04b3b7c00918e0d1dbbdcc6d00c18398 > files > 1

hobbit-4.2.0-9mdv2008.0.src.rpm

diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/bbgen.h ./bbdisplay/bbgen.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/bbgen.h	2006-08-09 22:09:54.000000000 +0200
+++ ./bbdisplay/bbgen.h	2006-10-03 13:52:28.000000000 +0200
@@ -51,9 +51,9 @@
                                            dialup               column -------> bbgen_col_t
                                            reportwarnlevel      color             name
                                            comment              age               next
-                                           banks                oldage
-                                           banksize             acked
-                                           next                 alert
+                                           next                 oldage
+                                                                acked
+                                                                alert
                                                                 onwap
                                                                 propagate
                                                                 reportinfo
@@ -140,6 +140,9 @@
 	struct state_t	*next;
 } state_t;
 
+/* OSX has a built-in "host_t" type. */
+#define host_t bbgen_host_t
+
 typedef struct host_t {
 	char	*hostname;
 	char	*displayname;
@@ -166,8 +169,6 @@
 	struct bbgen_page_t *parent;
 	double  reportwarnlevel;
 	char	*reporttime;
-	int     *banks;
-	int     banksize;
 	int     nobb2;
 	struct host_t	*next;
 } host_t;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loadbbhosts.c ./bbdisplay/loadbbhosts.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loadbbhosts.c	2006-08-09 22:09:54.000000000 +0200
+++ ./bbdisplay/loadbbhosts.c	2007-02-09 11:01:19.389403275 +0100
@@ -109,7 +109,7 @@
 	}
 
 	xfree(set);
-	return ((strlen(result) > 0) ? result : NULL);
+	return result;	/* This may be an empty string */
 }
 
 bbgen_page_t *init_page(char *name, char *title)
@@ -174,8 +174,7 @@
 		  int ip1, int ip2, int ip3, int ip4, 
 		  int dialup, double warnpct, char *reporttime,
 		  char *alerts, int nktime, char *waps,
-		  char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests,
-		  int modembanksize)
+		  char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests)
 {
 	host_t 		*newhost = (host_t *) calloc(1, sizeof(host_t));
 	hostlist_t	*oldlist;
@@ -256,21 +255,6 @@
 	}
 
 	newhost->parent = NULL;
-	newhost->banks = NULL;
-	newhost->banksize = modembanksize;
-	if (modembanksize) {
-		int i;
-		newhost->banks = (int *) calloc(modembanksize, sizeof(int));
-		for (i=0; i<modembanksize; i++) newhost->banks[i] = -1;
-
-		if (comment) {
-			newhost->comment = (char *) realloc(newhost->comment, strlen(newhost->comment) + 22);
-			sprintf(newhost->comment+strlen(newhost->comment), " - [%s]", newhost->ip);
-		}
-		else {
-			newhost->comment = newhost->ip;
-		}
-	}
 	newhost->nobb2 = 0;
 	newhost->next = NULL;
 
@@ -440,7 +424,6 @@
 	host_t	*curhost;
 	char	*curtitle;
 	int	ip1, ip2, ip3, ip4;
-	int	modembanksize;
 	char	*p;
 	int	fqdn = get_fqdn();
 
@@ -482,8 +465,6 @@
 
 		dbgprintf("load_bbhosts: -- got line '%s'\n", STRBUF(inbuf));
 
-		modembanksize = 0;
-
 		if (strncmp(STRBUF(inbuf), pagetag, strlen(pagetag)) == 0) {
 			getnamelink(STRBUF(inbuf), &name, &link);
 			if (curpage == NULL) {
@@ -581,9 +562,7 @@
 			if (curtitle) { curgroup->pretitle = curtitle; curtitle = NULL; }
 			curhost = NULL;
 		}
-		else if ( (sscanf(STRBUF(inbuf), "%3d.%3d.%3d.%3d %s", &ip1, &ip2, &ip3, &ip4, hostname) == 5) ||
-		          (!reportstart && !snapshot && (sscanf(STRBUF(inbuf), "dialup %s %d.%d.%d.%d %d", hostname, &ip1, &ip2, &ip3, &ip4, &modembanksize) == 6) && (modembanksize > 0)) ) {
-
+		else if (sscanf(STRBUF(inbuf), "%3d.%3d.%3d.%3d %s", &ip1, &ip2, &ip3, &ip4, hostname) == 5) {
 			namelist_t *bbhost = NULL;
 			int dialup, nobb2, nktime = 1;
 			double warnpct = reportwarnlevel;
@@ -685,8 +664,7 @@
 							    ip1, ip2, ip3, ip4, dialup, 
 							    warnpct, reporttime,
 							    alertlist, nktime, onwaplist,
-							    nopropyellowlist, nopropredlist, noproppurplelist, nopropacklist,
-							    modembanksize);
+							    nopropyellowlist, nopropredlist, noproppurplelist, nopropacklist);
 					if (curgroup != NULL) {
 						curgroup->hosts = curhost;
 					}
@@ -710,8 +688,7 @@
 									    warnpct, reporttime,
 									    alertlist, nktime, onwaplist,
 									    nopropyellowlist,nopropredlist, 
-									    noproppurplelist, nopropacklist,
-									    modembanksize);
+									    noproppurplelist, nopropacklist);
 				}
 				curhost->parent = (cursubparent ? cursubparent : (cursubpage ? cursubpage : curpage));
 				if (curtitle) { curhost->pretitle = curtitle; curtitle = NULL; }
@@ -758,8 +735,7 @@
 									    warnpct, reporttime,
 									    alertlist, nktime, onwaplist,
 									    nopropyellowlist,nopropredlist, 
-									    noproppurplelist, nopropacklist,
-									    modembanksize);
+									    noproppurplelist, nopropacklist);
 
 						if (wantedgroup > 0) {
 							group_t *gwalk;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loadbbhosts.h ./bbdisplay/loadbbhosts.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loadbbhosts.h	2006-08-09 22:09:54.000000000 +0200
+++ ./bbdisplay/loadbbhosts.h	2006-10-03 13:52:28.000000000 +0200
@@ -24,8 +24,7 @@
 			 int dialup,
 			 double warnpct, char *reporttime,
 			 char *alerts, int nktime, char *waps,
-			 char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests,
-			 int modembanksize);
+			 char *nopropyellowtests, char *nopropredtests, char *noproppurpletests, char *nopropacktests);
 
 extern char	*nopropyellowdefault;
 extern char	*nopropreddefault;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loaddata.c ./bbdisplay/loaddata.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/loaddata.c	2006-08-09 22:09:54.000000000 +0200
+++ ./bbdisplay/loaddata.c	2006-10-03 13:52:28.000000000 +0200
@@ -182,12 +182,6 @@
 
 	host = find_host(hostname);
 
-	/* If the host is a modem-bank host, dont mix in normal status messages */
-	if (host && (host->banksize > 0)) {
-		errprintf("Modembank %s has additional status-logs - ignored\n", hostname);
-		return NULL;
-	}
-
 	newstate = (state_t *) calloc(1, sizeof(state_t));
 	newstate->entry = (entry_t *) calloc(1, sizeof(entry_t));
 	newstate->next = NULL;
@@ -347,72 +341,6 @@
 	return newsum;
 }
 
-void init_modembank_status(char *fn, logdata_t *log)
-{
-	char *msgcopy;
-	host_t *targethost;
-	time_t now = time(NULL);
-
-	dbgprintf("init_modembank_status(%s)\n", textornull(fn));
-
-	if (log->validtime < now) return;
-
-	targethost = find_host(fn+strlen("dialup."));
-	if (targethost == NULL) {
-		dbgprintf("Modembank status from unknown host %s - ignored\n", fn+strlen("dialup."));
-		return;
-	}
-
-	msgcopy = strdup(log->msg);
-	if (strlen(msgcopy)) {
-		char *startip, *endip, *tag;
-		int idx = -1;
-
-		startip = endip = NULL;
-		tag = strtok(msgcopy, " \n");
-		while (tag) {
-			if (idx >= 0) {
-				/* Next result */
-				if (idx < targethost->banksize) targethost->banks[idx] = parse_color(tag);
-				idx++;
-			}
-			else if (strcmp(tag, "DATA") == 0) {
-				if (startip && endip) idx = 0;
-				else errprintf("Invalid modembank status logfile %s (missing FROM and/or TO)\n", fn);
-			}
-			else if (strcmp(tag, "FROM") == 0) {
-				tag = strtok(NULL, " \n");
-
-				if (tag) {
-					startip = tag;
-					if (strcmp(startip, targethost->ip) != 0) {
-						errprintf("Modembank in bb-hosts begins with %s, but logfile begins with %s\n",
-						  	targethost->ip, startip);
-					}
-				} else errprintf("Invalid modembank status logfile %s (truncated)\n", fn);
-			}
-			else if (strcmp(tag, "TO") == 0) {
-				tag = strtok(NULL, " \n");
-
-				if (tag) {
-					if (startip) endip = tag;
-					else errprintf("Invalid modembank status logfile %s (no FROM)\n", fn);
-				} else errprintf("Invalid modembank status logfile %s (truncated)\n", fn);
-			}
-
-			if (tag) tag = strtok(NULL, " \n");
-		}
-
-		if ((idx >= 0) && (idx != targethost->banksize)) {
-			errprintf("Modembank status log %s has more entries (%d) than expected (%d)\n", 
-				  fn, (idx-1), targethost->banksize);
-		}
-	}
-
-	xfree(msgcopy);
-}
-
-
 state_t *load_state(dispsummary_t **sumhead)
 {
 	int hobbitdresult;
@@ -506,9 +434,6 @@
 				}
 			}
 		}
-		else if (strncmp(fn, "dialup.", 7) == 0) {
-			init_modembank_status(fn, &log);
-		}
 		else {
 			if (acklist && *acklist) {
 				/*
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/pagegen.c ./bbdisplay/pagegen.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbdisplay/pagegen.c	2006-08-09 22:09:54.000000000 +0200
+++ ./bbdisplay/pagegen.c	2006-10-03 13:52:28.000000000 +0200
@@ -322,8 +322,6 @@
 	int	genstatic;
 	int	columncount;
 	char	*bbskin;
-	int	maxbanksize = 0;
-	int	anyplainhosts = 0;
 	int	rowcount = 0;
 
 	if (head == NULL)
@@ -338,27 +336,10 @@
 	else fprintf(output, "<A NAME=hosts-blk-%d>&nbsp;</A>\n\n", hostblkidx);
 	hostblkidx++;
 
-	for (h = head; (h); h = h->next) {
-		if (h->banksize > maxbanksize) maxbanksize = h->banksize;
-		if (h->banksize == 0) anyplainhosts = 1;
-	}
-
-	if (maxbanksize == 0) {
-		/* No modembanks - normal hostlist with columns and stuff */
-		groupcols = gen_column_list(head, pagetype, onlycols, exceptcols);
-		for (columncount=0, gc=groupcols; (gc); gc = gc->next, columncount++) ;
-	}
-	else {
-		/* There are modembanks here! */
-		if (anyplainhosts) {
-			errprintf("WARNING: Modembank displays should be in their own group or page.\nMixing normal hosts with modembanks yield strange output.\n");
-		}
-		groupcols = NULL;
-		columncount = maxbanksize;
-	}
-
-	if (groupcols || (maxbanksize > 0)) {
+	groupcols = gen_column_list(head, pagetype, onlycols, exceptcols);
+	for (columncount=0, gc=groupcols; (gc); gc = gc->next, columncount++) ;
 
+	if (groupcols) {
 		int width;
 
 		width = atoi(xgetenv("DOTWIDTH"));
@@ -386,29 +367,11 @@
 				fprintf(output, "<TD VALIGN=MIDDLE ROWSPAN=2><CENTER><FONT %s>%s</FONT></CENTER></TD>\n", 
 					xgetenv("MKBBTITLE"), (strlen(grouptitle) ? grouptitle : "&nbsp;"));
 
-				if ((groupcols == NULL) && (maxbanksize > 0)) {
-					int i,j;
-
-					fprintf(output, "<TD><TABLE BORDER=0>\n");
-					for (i=0; (i < maxbanksize); i+=16) {
-						fprintf(output, "<TR>\n");
-						for (j=i; (((j-i) < 16) && (j < maxbanksize)); j++) {
-							fprintf(output, " <TD ALIGN=CENTER VALIGN=BOTTOM WIDTH=%d>", width);
-							fprintf(output, " <FONT %s><B>%d</B></FONT>", 
-								xgetenv("MKBBCOLFONT"), j);
-							fprintf(output, " </TD>\n");
-						}
-						fprintf(output, "</TR>\n");
-					}
-					fprintf(output, "</TABLE></TD>\n");
-				}
-				else {
-					for (gc=groupcols; (gc); gc = gc->next) {
-						fprintf(output, " <TD ALIGN=CENTER VALIGN=BOTTOM WIDTH=45>\n");
-						fprintf(output, " <A HREF=\"%s\"><FONT %s><B>%s</B></FONT></A> </TD>\n", 
-							columnlink(gc->column->name), 
-							xgetenv("MKBBCOLFONT"), gc->column->name);
-					}
+				for (gc=groupcols; (gc); gc = gc->next) {
+					fprintf(output, " <TD ALIGN=CENTER VALIGN=BOTTOM WIDTH=45>\n");
+					fprintf(output, " <A HREF=\"%s\"><FONT %s><B>%s</B></FONT></A> </TD>\n", 
+						columnlink(gc->column->name), 
+						xgetenv("MKBBCOLFONT"), gc->column->name);
 				}
 				fprintf(output, "</TR> \n<TR><TD COLSPAN=%d><HR WIDTH=\"100%%\"></TD></TR>\n\n", columncount);
 			}
@@ -421,44 +384,6 @@
 				hostnamehtml(h->hostname, ((pagetype != PAGE_BB) ? hostpage_link(h) : NULL)) );
 
 			/* Then the columns. */
-			if ((groupcols == NULL) && (h->banksize > 0)) {
-				int i, j;
-				char alttag[30];
-				unsigned int baseip, ip1, ip2, ip3, ip4;
-
-				sscanf(h->ip, "%u.%u.%u.%u", &ip1, &ip2, &ip3, &ip4);
-				baseip = IPtou32(ip1, ip2, ip3, ip4);
-
-				fprintf(output, "<TD ALIGN=CENTER><TABLE BORDER=0>");
-				for (i=0; (i < h->banksize); i+=16) {
-					fprintf(output, "<TR>\n");
-					for (j=i; (((j-i) < 16) && (j < h->banksize)); j++) {
-						fprintf(output, "<TD ALIGN=CENTER VALIGN=BOTTOM WIDTH=%d>", width);
-
-						if (genstatic) {
-							/*
-							 * Dont use htmlextension here - it's for the
-							 * pages generated by bbd.
-							 */
-							fprintf(output, "<A HREF=\"%s/html/dialup.%s.html\">",
-								xgetenv("BBWEB"), h->hostname);
-						}
-						else {
-							fprintf(output, "<A HREF=\"%s\">", 
-								hostsvcurl("dialup", commafy(h->hostname), 1));
-						}
-	
-						sprintf(alttag, "%s:%s", u32toIP(baseip+j), colorname(h->banks[j]));
-						fprintf(output, "<IMG SRC=\"%s/%s\" ALT=\"%s\" TITLE=\"%s\" HEIGHT=\"%s\" WIDTH=\"%s\" BORDER=0></A>",
-							bbskin, dotgiffilename(h->banks[j], 0, 0),
-							alttag, alttag, xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH"));
-
-						fprintf(output, "</TD>\n");
-					}
-					fprintf(output, "</TR>\n");
-				}
-				fprintf(output, "</TABLE></TD>\n");
-			}
 			for (gc = groupcols; (gc); gc = gc->next) {
 				char *htmlalttag;
 
@@ -657,7 +582,7 @@
 
 		if (newhost == NULL) {
 			/* New summary "host" */
-			newhost = init_host(s->row, 1, NULL, NULL, NULL, NULL, 0,0,0,0, 0, 0.0, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0);
+			newhost = init_host(s->row, 1, NULL, NULL, NULL, NULL, 0,0,0,0, 0, 0.0, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL);
 
 			/* Insert into sorted host list */
 			if ((!sumhosts) || (strcmp(newhost->hostname, sumhosts->hostname) < 0)) {
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbnet/bbtest-net.c ./bbnet/bbtest-net.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbnet/bbtest-net.c	2006-08-09 22:09:55.000000000 +0200
+++ ./bbnet/bbtest-net.c	2006-10-03 13:52:28.000000000 +0200
@@ -64,27 +64,13 @@
 	NULL
 };
 
-/* toolid values */
-#define TOOL_CONTEST	0
-#define TOOL_NSLOOKUP	1
-#define TOOL_DIG	2
-#define TOOL_NTP        3
-#define TOOL_FPING      4
-#define TOOL_HTTP       5
-#define TOOL_MODEMBANK  6
-#define TOOL_LDAP	7
-#define TOOL_RPCINFO	8
-
-
 RbtHandle	svctree;			/* All known services, has service_t records */
 service_t	*pingtest = NULL;		/* Identifies the pingtest within svctree list */
 int		pingcount = 0;
 service_t	*dnstest = NULL;		/* Identifies the dnstest within svctree list */
-service_t	*digtest = NULL;		/* Identifies the digtest within svctree list */
 service_t	*httptest = NULL;		/* Identifies the httptest within svctree list */
 service_t	*ldaptest = NULL;		/* Identifies the ldaptest within svctree list */
 service_t	*rpctest = NULL;		/* Identifies the rpctest within svctree list */
-service_t	*modembanktest = NULL;		/* Identifies the modembank test within svctree list */
 RbtHandle       testhosttree;			/* All tested hosts, has testedhost_t records */
 char		*nonetpage = NULL;		/* The "NONETPAGE" env. variable */
 int		dnsmethod = DNS_THEN_IP;	/* How to do DNS lookups */
@@ -157,35 +143,21 @@
 		printf("Service %s, port %d, toolid %d\n", swalk->testname, swalk->portnum, swalk->toolid);
 
 		for (iwalk = swalk->items; (iwalk); iwalk = iwalk->next) {
-			if (swalk == modembanktest) {
-				modembank_t *mentry;
-				int i;
-
-				mentry = iwalk->privdata;
-				printf("\tModembank   : %s\n", textornull(mentry->hostname));
-				printf("\tStart-IP    : %s\n", u32toIP(mentry->startip));
-				printf("\tBanksize    : %d\n", mentry->banksize);
-				printf("\tOpen        :");
-				for (i=0; i<mentry->banksize; i++) printf(" %d", mentry->responses[i]);
-				printf("\n");
-			}
-			else {
-				printf("\tHost        : %s\n", textornull(iwalk->host->hostname));
-				printf("\ttestspec    : %s\n", textornull(iwalk->testspec));
-				printf("\tFlags       :");
-				if (iwalk->dialup) printf(" dialup");
-				if (iwalk->reverse) printf(" reverse");
-				if (iwalk->silenttest) printf(" silenttest");
-				if (iwalk->alwaystrue) printf(" alwaystrue");
-				printf("\n");
-				printf("\tOpen        : %d\n", iwalk->open);
-				printf("\tBanner      : %s\n", textornull(STRBUF(iwalk->banner)));
-				printf("\tcertinfo    : %s\n", textornull(iwalk->certinfo));
-				printf("\tDuration    : %ld.%06ld\n", (long int)iwalk->duration.tv_sec, (long int)iwalk->duration.tv_usec);
-				printf("\tbadtest     : %d:%d:%d\n", iwalk->badtest[0], iwalk->badtest[1], iwalk->badtest[2]);
-				printf("\tdowncount    : %d started %s", iwalk->downcount, ctime(&iwalk->downstart));
-				printf("\n");
-			}
+			printf("\tHost        : %s\n", textornull(iwalk->host->hostname));
+			printf("\ttestspec    : %s\n", textornull(iwalk->testspec));
+			printf("\tFlags       :");
+			if (iwalk->dialup) printf(" dialup");
+			if (iwalk->reverse) printf(" reverse");
+			if (iwalk->silenttest) printf(" silenttest");
+			if (iwalk->alwaystrue) printf(" alwaystrue");
+			printf("\n");
+			printf("\tOpen        : %d\n", iwalk->open);
+			printf("\tBanner      : %s\n", textornull(STRBUF(iwalk->banner)));
+			printf("\tcertinfo    : %s\n", textornull(iwalk->certinfo));
+			printf("\tDuration    : %ld.%06ld\n", (long int)iwalk->duration.tv_sec, (long int)iwalk->duration.tv_usec);
+			printf("\tbadtest     : %d:%d:%d\n", iwalk->badtest[0], iwalk->badtest[1], iwalk->badtest[2]);
+			printf("\tdowncount    : %d started %s", iwalk->downcount, ctime(&iwalk->downstart));
+			printf("\n");
 		}
 
 		printf("\n");
@@ -423,35 +395,6 @@
 
 		if (!wanted_host(hwalk, location)) continue;
 
-		if (argnmatch(hwalk->bbhostname, "@dialup.")) {
-			/* Modembank entry: "dialup displayname startIP count" */
-
-			char *realname;
-			testitem_t *newtest;
-			modembank_t *newentry;
-			int i, ip1, ip2, ip3, ip4, banksize;
-
-			realname = hwalk->bbhostname + strlen("@dialup.");
-			banksize = atoi(bbh_item(hwalk, BBH_BANKSIZE));
-			sscanf(bbh_item(hwalk, BBH_IP), "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4);
-
-			newtest = init_testitem(NULL, modembanktest, NULL, 0, 0, 0, 0, 0);
-			newtest->next = modembanktest->items;
-			modembanktest->items = newtest;
-
-			newtest->privdata = (void *)malloc(sizeof(modembank_t));
-			newentry = (modembank_t *)newtest->privdata;
-			newentry->hostname = realname;
-			newentry->startip = IPtou32(ip1, ip2, ip3, ip4);
-			newentry->banksize = banksize;
-			newentry->responses = (int *) malloc(banksize * sizeof(int));
-			for (i=0; i<banksize; i++) newentry->responses[i] = 0;
-
-			/* No more to do for modembanks */
-			continue;
-		}
-
-
 		h = init_testedhost(hwalk->bbhostname);
 
 		p = bbh_custom_item(hwalk, "badconn:");
@@ -645,7 +588,7 @@
 					s = dnstest;
 				}
 				else if (argnmatch(testspec, "dig=")) {
-					s = digtest;
+					s = dnstest;
 				}
 				else {
 					/* 
@@ -1311,60 +1254,6 @@
 	return 0;
 }
 
-void run_modembank_service(service_t *service)
-{
-	testitem_t	*t;
-	char		cmd[1024];
-	char		startip[IP_ADDR_STRLEN], endip[IP_ADDR_STRLEN];
-	char		*p;
-	char		cmdpath[PATH_MAX];
-	FILE		*cmdpipe;
-	char		l[MAX_LINE_LEN];
-	int		ip1, ip2, ip3, ip4;
-
-	for (t=service->items; (t); t = t->next) {
-		modembank_t *req = (modembank_t *)t->privdata;
-
-		p = xgetenv("FPING");
-		strcpy(cmdpath, (p ? p : "hobbitping"));
-		strcpy(startip, u32toIP(req->startip));
-		strcpy(endip, u32toIP(req->startip + req->banksize - 1));
-		sprintf(cmd, "%s -g -Ae %s %s 2>/dev/null", cmdpath, startip, endip);
-
-		dbgprintf("Running command: '%s'\n", cmd);
-		cmdpipe = popen(cmd, "r");
-		if (cmdpipe == NULL) {
-			errprintf("Could not run the hobbitping command %s\n", cmd);
-			return;
-		}
-
-		while (fgets(l, sizeof(l), cmdpipe)) {
-			dbgprintf("modembank response: %s", l);
-
-			if (sscanf(l, "%d.%d.%d.%d ", &ip1, &ip2, &ip3, &ip4) == 4) {
-				unsigned int idx = IPtou32(ip1, ip2, ip3, ip4) - req->startip;
-
-				if (idx >= req->banksize) {
-					errprintf("Unexpected response for IP not in bank - %d.%d.%d.%d", 
-						  ip1, ip2, ip3, ip4);
-				}
-				else {
-					req->responses[idx] = (strstr(l, "is alive") != NULL);
-				}
-			}
-		}
-		pclose(cmdpipe);
-
-		if (debug) {
-			int i;
-
-			dbgprintf("Results for modembank start=%s, length %d\n", u32toIP(req->startip), req->banksize);
-			for (i=0; (i<req->banksize); i++)
-				dbgprintf("\t%s is %d\n", u32toIP(req->startip+i), req->responses[i]);
-		}
-	}
-}
-
 
 int decide_color(service_t *service, char *svcname, testitem_t *test, int failgoesclear, char *cause)
 {
@@ -1756,44 +1645,6 @@
 }
 
 
-void send_modembank_results(service_t *service)
-{
-	testitem_t	*t;
-	char		msgline[1024];
-	int		i, color, inuse;
-	char		startip[IP_ADDR_STRLEN], endip[IP_ADDR_STRLEN];
-
-	for (t=service->items; (t); t = t->next) {
-		modembank_t *req = (modembank_t *)t->privdata;
-
-		inuse = 0;
-		strcpy(startip, u32toIP(req->startip));
-		strcpy(endip, u32toIP(req->startip + req->banksize - 1));
-
-		init_status(COL_GREEN);		/* Modembanks are always green */
-		sprintf(msgline, "status dialup.%s %s %s FROM %s TO %s DATA ", 
-			req->hostname, colorname(COL_GREEN), timestamp, startip, endip);
-		addtostatus(msgline);
-		for (i=0; i<req->banksize; i++) {
-			if (req->responses[i]) {
-				color = COL_GREEN;
-				inuse++;
-			}
-			else {
-				color = COL_CLEAR;
-			}
-
-			sprintf(msgline, "%s ", colorname(color));
-			addtostatus(msgline);
-		}
-
-		sprintf(msgline, "\n\nUsage: %d of %d (%d%%)\n", inuse, req->banksize, ((inuse * 100) / req->banksize));
-		addtostatus(msgline);
-		finish_status();
-	}
-}
-
-
 void send_rpcinfo_results(service_t *service, int failgoesclear)
 {
 	testitem_t	*t;
@@ -2234,14 +2085,12 @@
 		return 0;
 	}
 
-	dnstest = add_service("dns", getportnumber("domain"), 0, TOOL_NSLOOKUP);
-	digtest = add_service("dig", getportnumber("domain"), 0, TOOL_DIG);
+	dnstest = add_service("dns", getportnumber("domain"), 0, TOOL_DNS);
 	add_service("ntp", getportnumber("ntp"),    0, TOOL_NTP);
 	rpctest  = add_service("rpc", getportnumber("sunrpc"), 0, TOOL_RPCINFO);
 	httptest = add_service("http", getportnumber("http"),  0, TOOL_HTTP);
 	ldaptest = add_service("ldapurl", getportnumber("ldap"), strlen("ldap"), TOOL_LDAP);
 	if (pingcolumn) pingtest = add_service(pingcolumn, 0, 0, TOOL_FPING);
-	modembanktest = add_service("dialup", 0, 0, TOOL_MODEMBANK);
 	add_timestamp("Service definitions loaded");
 
 	load_tests();
@@ -2360,18 +2209,14 @@
 	add_timestamp("LDAP tests result collection completed");
 
 
-	/* dns, dig, ntp tests */
+	/* dns, ntp tests */
 	for (handle = rbtBegin(svctree); handle != rbtEnd(svctree); handle = rbtNext(svctree, handle)) {
 		s = (service_t *)gettreeitem(svctree, handle);
 		if (s->items) {
 			switch(s->toolid) {
-				case TOOL_NSLOOKUP:
+				case TOOL_DNS:
 					run_nslookup_service(s); 
-					add_timestamp("NSLOOKUP tests executed");
-					break;
-				case TOOL_DIG:
-					run_nslookup_service(s); 
-					add_timestamp("DIG tests executed");
+					add_timestamp("DNS tests executed");
 					break;
 				case TOOL_NTP:
 					run_ntp_service(s); 
@@ -2381,9 +2226,7 @@
 					run_rpcinfo_service(s); 
 					add_timestamp("RPC tests executed");
 					break;
-				case TOOL_MODEMBANK:
-					run_modembank_service(s); 
-					add_timestamp("Modembank tests executed");
+				default:
 					break;
 			}
 		}
@@ -2394,8 +2237,7 @@
 		s = (service_t *)gettreeitem(svctree, handle);
 		switch (s->toolid) {
 			case TOOL_CONTEST:
-			case TOOL_NSLOOKUP:
-			case TOOL_DIG:
+			case TOOL_DNS:
 			case TOOL_NTP:
 				send_results(s, failgoesclear);
 				break;
@@ -2406,10 +2248,6 @@
 				/* These handle result-transmission internally */
 				break;
 
-			case TOOL_MODEMBANK:
-				send_modembank_results(s);
-				break;
-
 			case TOOL_RPCINFO:
 				send_rpcinfo_results(s, failgoesclear);
 				break;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/bbnet/bbtest-net.h ./bbnet/bbtest-net.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/bbnet/bbtest-net.h	2006-08-09 22:09:55.000000000 +0200
+++ ./bbnet/bbtest-net.h	2006-10-03 13:52:28.000000000 +0200
@@ -19,6 +19,9 @@
 
 #define MAX_CONTENT_DATA (1024*1024)	/* 1 MB should be enough for most */
 
+/* toolids */
+enum toolid_t { TOOL_CONTEST, TOOL_DNS, TOOL_NTP, TOOL_FPING, TOOL_HTTP, TOOL_LDAP, TOOL_RPCINFO };
+
 /*
  * Structure of the bbtest-net in-memory records
  *
@@ -62,12 +65,11 @@
 	char *testname;		/* Name of the test = column name in Hobbit report */
 	int namelen;		/* Length of name - "testname:port" has this as strlen(testname), others 0 */
 	int portnum;		/* Port number this service runs on */
-	int toolid;		/* Which tool to use */
+	enum toolid_t toolid;	/* Which tool to use */
 	struct testitem_t *items; /* testitem_t linked list of tests for this service */
 } service_t;
 
-#define MULTIPING_BEST 0
-#define MULTIPING_WORST 1
+enum multiping_t { MULTIPING_BEST, MULTIPING_WORST };
 typedef struct ipping_t {
 	char *ip;
 	int  open;
@@ -76,7 +78,7 @@
 } ipping_t;
 
 typedef struct extraping_t {
-	int   matchtype;
+	enum multiping_t matchtype;
 	ipping_t *iplist;
 } extraping_t;
 
@@ -150,13 +152,6 @@
 	struct testitem_t *next;
 } testitem_t;
 
-typedef struct modembank_t {
-	char		*hostname;
-	unsigned int 	startip;	/* Saved as 32-bit binary */
-	int		banksize;
-	int		*responses;
-} modembank_t;
-
 typedef struct dnstest_t {
 	int	testcount;
 	int	okcount;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/build/bb-commands.sh ./build/bb-commands.sh
--- /home/henrik/hobbit/release/hobbit-4.2.0/build/bb-commands.sh	2006-08-09 22:09:57.000000000 +0200
+++ ./build/bb-commands.sh	2006-10-03 12:55:27.000000000 +0200
@@ -23,23 +23,13 @@
 echo "# Hobbit does not use them, but they are provided here so if you use BB extension"
 echo "# scripts, then they will hopefully run without having to do a lot of tweaking."
 echo ""
-for CMD in uptime awk cat cp cut date egrep expr find grep head id ln ls mv rm sed sort tail touch tr uniq who
+for CMD in uptime awk cat cp cut date egrep expr find grep head id ln ls mv rm sed sort tail top touch tr uniq who
 do
 	ENVNAME=`echo $CMD | tr "[a-z]" "[A-Z]"`
 	PGM=`findbin $CMD | head -n 1`
 	echo "${ENVNAME}=\"${PGM}\""
 done
 
-# TOP can either be "top", or on Solaris the "prstat" command.
-PRSTAT=`findbin prstat | head -n 1`
-if test "$PRSTAT" != ""
-then
-	PGM="$PRSTAT -can 20 1 1"
-else
-	PGM=`findbin top | head -n 1`
-fi
-echo "TOP=\"${PGM}\""
-
 # WC is special
 PGM=`findbin wc | head -n 1`
 echo "WC=\"${PGM} -l\""
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-aix.sh ./client/hobbitclient-aix.sh
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-aix.sh	2006-08-09 22:09:58.000000000 +0200
+++ ./client/hobbitclient-aix.sh	2006-10-03 13:52:28.000000000 +0200
@@ -36,7 +36,7 @@
 echo "[ifconfig]"
 ifconfig -a
 echo "[route]"
-netstat -r
+netstat -rn
 echo "[netstat]"
 netstat -s
 echo "[ports]"
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-darwin.sh ./client/hobbitclient-darwin.sh
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-darwin.sh	2006-08-09 22:09:58.000000000 +0200
+++ ./client/hobbitclient-darwin.sh	2006-10-03 13:52:28.000000000 +0200
@@ -33,7 +33,7 @@
 echo "[ifconfig]"
 ifconfig -a
 echo "[route]"
-netstat -r
+netstat -rn
 echo "[netstat]"
 netstat -s
 echo "[ifstat]"
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-sunos.sh ./client/hobbitclient-sunos.sh
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/hobbitclient-sunos.sh	2006-08-09 22:09:58.000000000 +0200
+++ ./client/hobbitclient-sunos.sh	2006-10-03 12:55:27.000000000 +0200
@@ -22,7 +22,7 @@
 
 echo "[df]"
 # All of this because Solaris df cannot show multiple fs-types, or exclude certain fs types.
-FSTYPES=`/bin/df -n -l|awk '{print $3}'|egrep -v "^proc|^fd|^mntfs|^ctfs|^devfs|^objfs|^nfs"|sort|uniq`
+FSTYPES=`/bin/df -n -l|awk '{print $3}'|egrep -v "^proc|^fd|^mntfs|^ctfs|^devfs|^objfs|^nfs|^lofs"|sort|uniq`
 if test "$FSTYPES" = ""; then FSTYPES="ufs"; fi
 set $FSTYPES
 /bin/df -F $1 -k | grep -v " /var/run"
@@ -54,14 +54,13 @@
 echo "[ps]"
 ps -A -o pid,ppid,user,stime,s,pri,pcpu,time,pmem,rss,vsz,args
 
-# $TOP must be set, the install utility should do that for us if it exists.
-if test "$TOP" != ""
+# If TOP is defined, then use it. If not, fall back to the Solaris prstat command.
+echo "[top]"
+if test "$TOP" != "" -a -x "$TOP"
 then
-    if test -x "$TOP"
-    then
-        echo "[top]"
-        $TOP -b 20
-    fi
+	"$TOP" -b 20
+else
+	prstat -can 20 1 1
 fi
 
 # vmstat and iostat (iostat -d provides a cpu utilisation with I/O wait number)
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/logfetch.c ./client/logfetch.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/logfetch.c	2006-08-09 22:09:58.000000000 +0200
+++ ./client/logfetch.c	2006-10-03 13:52:28.000000000 +0200
@@ -52,8 +52,10 @@
 	long lastpos[POSCOUNT];
 	long maxbytes;
 #endif
-	char *trigger;
-	char *ignore;
+	char **trigger;
+	int triggercount;
+	char **ignore;
+	int ignorecount;
 } logdef_t;
 
 typedef struct filedef_t {
@@ -111,7 +113,7 @@
 	int openerr, i, status, triggerlinecount, done;
 	char *linepos[2*LINES_AROUND_TRIGGER+1];
 	int lpidx;
-	regex_t ignexpr, trigexpr;
+	regex_t *ignexpr = NULL, *trigexpr = NULL;
 #ifdef _LARGEFILE_SOURCE
 	off_t bufsz;
 #else
@@ -188,13 +190,21 @@
 	}
 
 	/* Compile the regex patterns */
-	if (logdef->ignore) {
-		status = regcomp(&ignexpr, logdef->ignore, REG_EXTENDED|REG_ICASE|REG_NOSUB);
-		if (status != 0) logdef->ignore = NULL;
-	}
-	if (logdef->trigger) {
-		status = regcomp(&trigexpr, logdef->trigger, REG_EXTENDED|REG_ICASE|REG_NOSUB);
-		if (status != 0) logdef->trigger = NULL;
+	if (logdef->ignorecount) {
+		int i;
+		ignexpr = (regex_t *) malloc(logdef->ignorecount * sizeof(regex_t));
+		for (i=0; (i < logdef->ignorecount); i++) {
+			status = regcomp(&ignexpr[i], logdef->ignore[i], REG_EXTENDED|REG_ICASE|REG_NOSUB);
+			if (status != 0) logdef->ignore[i] = NULL;
+		}
+	}
+	if (logdef->triggercount) {
+		int i;
+		trigexpr = (regex_t *) malloc(logdef->triggercount * sizeof(regex_t));
+		for (i=0; (i < logdef->triggercount); i++) {
+			status = regcomp(&trigexpr[i], logdef->trigger[i], REG_EXTENDED|REG_ICASE|REG_NOSUB);
+			if (status != 0) logdef->trigger[i] = NULL;
+		}
 	}
 	triggerstartpos = triggerendpos = NULL;
 	triggerlinecount = 0;
@@ -222,17 +232,27 @@
 		}
 
 		/* Check ignore pattern */
-		if (logdef->ignore) {
-			status = regexec(&ignexpr, fillpos, 0, NULL, 0);
-			if (status == 0) continue;
+		if (logdef->ignorecount) {
+			int i, match = 0;
+
+			for (i=0; ((i < logdef->ignorecount) && !match); i++) {
+				match = (regexec(&ignexpr[i], fillpos, 0, NULL, 0) == 0);
+			}
+
+			if (match) continue;
 		}
 
 		linepos[lpidx] = fillpos;
 
 		/* See if this is a trigger line */
-		if (logdef->trigger) {
-			status = regexec(&trigexpr, fillpos, 0, NULL, 0);
-			if (status == 0) {
+		if (logdef->triggercount) {
+			int i, match = 0;
+
+			for (i=0; ((i < logdef->ignorecount) && !match); i++) {
+				match = (regexec(&trigexpr[i], fillpos, 0, NULL, 0) == 0);
+			}
+
+			if (match) {
 				int sidx;
 				
 				sidx = lpidx - LINES_AROUND_TRIGGER; 
@@ -270,6 +290,7 @@
 	if (bytesread > logdef->maxbytes) {
 		char *skiptxt = "<...SKIPPED...>\n";
 
+		/* FIXME: Must make sure to only pass complete lines back to the server */
 		if (triggerstartpos) {
 			/* Skip the beginning of the data up until the trigger was found */
 			startpos = triggerstartpos;
@@ -322,8 +343,24 @@
 
 cleanup:
 	if (fd) fclose(fd);
-	if (logdef->ignore) regfree(&ignexpr);
-	if (logdef->trigger) regfree(&trigexpr);
+
+	{
+		int i;
+
+		if (logdef->ignorecount) {
+			for (i=0; (i < logdef->ignorecount); i++) {
+				if (logdef->ignore[i]) regfree(&ignexpr[i]);
+			}
+			xfree(ignexpr);
+		}
+
+		if (logdef->triggercount) {
+			for (i=0; (i < logdef->triggercount); i++) {
+				if (logdef->trigger[i]) regfree(&trigexpr[i]);
+			}
+			xfree(trigexpr);
+		}
+	}
 
 	return startpos;
 }
@@ -707,12 +744,33 @@
 					checkdef_t *walk = currcfg;
 
 					do {
-						walk->check.logcheck.ignore = strdup(p);
+						walk->check.logcheck.ignorecount++;
+						if (walk->check.logcheck.ignore == NULL) {
+							walk->check.logcheck.ignore = (char **)malloc(sizeof(char *));
+						}
+						else {
+							walk->check.logcheck.ignore = 
+								(char **)realloc(walk->check.logcheck.ignore, 
+										 walk->check.logcheck.ignorecount * sizeof(char **));
+						}
+
+						walk->check.logcheck.ignore[walk->check.logcheck.ignorecount-1] = strdup(p);
+
 						walk = walk->next;
 					} while (walk && (walk != firstpipeitem->next));
 				}
 				else {
-					currcfg->check.logcheck.ignore = strdup(p);
+					currcfg->check.logcheck.ignorecount++;
+					if (currcfg->check.logcheck.ignore == NULL) {
+						currcfg->check.logcheck.ignore = (char **)malloc(sizeof(char *));
+					}
+					else {
+						currcfg->check.logcheck.ignore = 
+							(char **)realloc(currcfg->check.logcheck.ignore, 
+									 currcfg->check.logcheck.ignorecount * sizeof(char **));
+					}
+
+					currcfg->check.logcheck.ignore[currcfg->check.logcheck.ignorecount-1] = strdup(p);
 				}
 			}
 			else if (strncmp(bol, "trigger ", 8) == 0) {
@@ -725,12 +783,33 @@
 					checkdef_t *walk = currcfg;
 
 					do {
-						walk->check.logcheck.trigger = strdup(p);
+						walk->check.logcheck.triggercount++;
+						if (walk->check.logcheck.trigger == NULL) {
+							walk->check.logcheck.trigger = (char **)malloc(sizeof(char *));
+						}
+						else {
+							walk->check.logcheck.trigger = 
+								(char **)realloc(walk->check.logcheck.trigger, 
+										 walk->check.logcheck.triggercount * sizeof(char **));
+						}
+
+						walk->check.logcheck.trigger[walk->check.logcheck.triggercount-1] = strdup(p);
+
 						walk = walk->next;
 					} while (walk && (walk != firstpipeitem->next));
 				}
 				else {
-					currcfg->check.logcheck.trigger = strdup(p);
+					currcfg->check.logcheck.triggercount++;
+					if (currcfg->check.logcheck.trigger == NULL) {
+						currcfg->check.logcheck.trigger = (char **)malloc(sizeof(char *));
+					}
+					else {
+						currcfg->check.logcheck.trigger = 
+							(char **)realloc(currcfg->check.logcheck.trigger, 
+									 currcfg->check.logcheck.triggercount * sizeof(char **));
+					}
+
+					currcfg->check.logcheck.trigger[currcfg->check.logcheck.triggercount-1] = strdup(p);
 				}
 			}
 		}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/msgcache.c ./client/msgcache.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/msgcache.c	2006-08-09 22:09:58.000000000 +0200
+++ ./client/msgcache.c	2006-10-03 12:55:27.000000000 +0200
@@ -440,7 +440,9 @@
 			freestrbuffer(zombie->msgbuf);
 			xfree(zombie);
 		}
-		if (!chead) ctail = NULL;
+		ctail = chead;
+		if (ctail) { while (ctail->next) ctail = ctail->next; }
+
 
 		/* Remove expired messages */
 		qwalk = qhead; qprev = NULL;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/client/runclient.sh ./client/runclient.sh
--- /home/henrik/hobbit/release/hobbit-4.2.0/client/runclient.sh	2006-08-09 22:09:58.000000000 +0200
+++ ./client/runclient.sh	2006-10-03 12:55:27.000000000 +0200
@@ -76,7 +76,7 @@
 
   	if test -s $HOBBITCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid; then
 		echo "Hobbit client already running, re-starting it"
-		$0 stop
+		$0 --hostname="$MACHINEDOTS" stop
 		rm -f $HOBBITCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid
 	fi
 
@@ -99,12 +99,12 @@
 
   "restart")
   	if test -s $HOBBITCLIENTHOME/logs/clientlaunch.$MACHINEDOTS.pid; then
-		$0 stop
+		$0 --hostname="$MACHINEDOTS" stop
 	else
 		echo "Hobbit client not running, continuing to start it"
 	fi
 
-	$0 start
+	$0 --hostname="$MACHINEDOTS" --os="$BBOSTYPE" start
 	;;
 
   "status")
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/common/bb-hosts.5 ./common/bb-hosts.5
--- /home/henrik/hobbit/release/hobbit-4.2.0/common/bb-hosts.5	2006-08-09 22:09:59.000000000 +0200
+++ ./common/bb-hosts.5	2006-10-03 12:55:27.000000000 +0200
@@ -494,7 +494,7 @@
 tool; the values specified in the "ssldays" tag overrides
 the default.
 
-.IP DOWNTIME=day:starttime:endtime:cause[,day:starttime:endtime:cause]
+.IP DOWNTIME=[columns:]day:starttime:endtime:cause[,day:starttime:endtime:cause]
 This tag can be used to ignore failed checks during
 specific times of the day - e.g. if you run services that
 are only monitored e.g. Mon-Fri 8am-5pm, or you always 
@@ -506,6 +506,10 @@
 will not be triggered and the downtime is not counted in the 
 availability calculations generated by the Hobbit reports.
 
+The "columns" setting is optional - it may be a comma-separated
+list of status columns in which case the DOWNTIME setting only applies
+to these columns.
+
 The "cause" string (optional) is a text that will be displayed on 
 the status web page to explain thy the system is down.
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/configure.server ./configure.server
--- /home/henrik/hobbit/release/hobbit-4.2.0/configure.server	2006-08-09 22:10:23.000000000 +0200
+++ ./configure.server	2006-10-03 13:52:28.000000000 +0200
@@ -316,13 +316,16 @@
 echo "top-level directory. I can set this up if you tell me"
 echo "what group-ID your webserver runs with. This is typically"
 echo "'nobody' or 'apache' or 'www-data'"
-echo "If you dont know, just hit ENTER and we will handle it later."
 echo ""
-echo "What group-ID does your webserver use ?"
+echo "What group-ID does your webserver use [nobody] ?"
 if test -z "$HTTPDGID"
 then
 	read HTTPDGID
 fi
+if test -z "$HTTPDGID"
+then
+	HTTPDGID="nobody"
+fi
 echo ""; echo ""
 
 echo "Where to put the Hobbit logfiles [/var/log/hobbit] ? "
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/Makefile ./hobbitd/Makefile
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/Makefile	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/Makefile	2006-10-03 13:52:28.000000000 +0200
@@ -1,4 +1,4 @@
-PROGRAMS = hobbit.sh hobbitd hobbitd_channel hobbitd_filestore hobbitd_history hobbitd_alert hobbitd_rrd hobbitd_sample hobbitd_client hobbitd_hostdata hobbitd_capture hobbitfetch hobbit-mailack trimhistory bbcombotest hobbitreports.sh moverrd.sh
+PROGRAMS = hobbit.sh hobbitd hobbitd_channel hobbitd_filestore hobbitd_history hobbitd_alert hobbitd_rrd hobbitd_sample hobbitd_client hobbitd_hostdata hobbitd_capture hobbitfetch hobbit-mailack trimhistory bbcombotest hobbitreports.sh moverrd.sh convertnk
 CLIENTPROGRAMS = ../client/hobbitd_client
 
 LIBOBJS = ../lib/libbbgen.a
@@ -17,7 +17,7 @@
 MAILACKOBJS   = hobbit-mailack.o
 TRIMHISTOBJS  = trimhistory.o
 FETCHOBJS     = hobbitfetch.o
-
+CONVERTNKOBJS = convertnk.o
 
 IDTOOL := $(shell if test `uname -s` = "SunOS"; then echo /usr/xpg4/bin/id; else echo id; fi)
 
@@ -91,6 +91,9 @@
 hobbitfetch: $(FETCHOBJS) $(LIBOBJS)
 	$(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(FETCHOBJS) $(LIBOBJS) $(NETLIBS)
 
+convertnk: $(CONVERTNKOBJS) $(LIBOBJS)
+	$(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(CONVERTNKOBJS) $(LIBOBJS) $(NETLIBS)
+
 hobbit.sh: hobbit.sh.DIST
 	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@BBLOGDIR@!$(BBLOGDIR)!g' | sed -e 's!@BBUSER@!$(BBUSER)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
 	chmod 755 $@
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/client_config.c ./hobbitd/client_config.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/client_config.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/client_config.c	2006-10-03 12:55:27.000000000 +0200
@@ -1249,6 +1249,7 @@
 	*abswarn = 0;
 	*abspanic = 0;
 	*ignored = 0;
+	*group = NULL;
 
 	rule = getrule(hostname, pagename, classname, C_DISK);
 	while (rule && !namematch(fsname, rule->rule.disk.fsexp->pattern, rule->rule.disk.fsexp->exp)) {
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/convertnk.c ./hobbitd/convertnk.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/convertnk.c	1970-01-01 01:00:00.000000000 +0100
+++ ./hobbitd/convertnk.c	2006-10-03 13:52:28.000000000 +0200
@@ -0,0 +1,54 @@
+/*----------------------------------------------------------------------------*/
+/* Hobbit utility to convert the deprecated NK tags to a hobbit-nkview.cfg    */
+/*                                                                            */
+/* Copyright (C) 2006 Henrik Storner <henrik@hswn.dk>                         */
+/*                                                                            */
+/* This program is released under the GNU General Public License (GPL),       */
+/* version 2. See the file "COPYING" for details.                             */
+/*                                                                            */
+/*----------------------------------------------------------------------------*/
+
+static char rcsid[] = "$Id: convertnk.c,v 1.1 2006/09/12 21:27:11 henrik Exp $";
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "libbbgen.h"
+
+int main(int argc, char *argv[])
+{
+	namelist_t *walk;
+
+	load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
+
+	for (walk = first_host(); (walk); walk=walk->next) {
+		char *nk, *nktime, *tok;
+
+		nk = bbh_item(walk, BBH_NK); if (!nk) continue;
+		nktime = bbh_item(walk, BBH_NKTIME);
+
+		nk = strdup(nk);
+
+		tok = strtok(nk, ",");
+		while (tok) {
+			char *hostname = bbh_item(walk, BBH_HOSTNAME);
+			char *startstr = "", *endstr = "", *ttgroup = "", *ttextra = "", *updinfo = "Migrated";
+			int priority = 2;
+
+			fprintf(stdout, "%s|%s|%s|%s|%s|%d|%s|%s|%s\n",
+				hostname, tok,
+				startstr, endstr,
+				(nktime ? nktime : ""),
+				priority, ttgroup, ttextra, updinfo);
+
+			tok = strtok(NULL, ",");
+		}
+
+		xfree(nk);
+	}
+
+	return 0;
+}
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/do_alert.c ./hobbitd/do_alert.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/do_alert.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/do_alert.c	2006-10-03 12:55:27.000000000 +0200
@@ -168,7 +168,7 @@
 
 static char *message_text(activealerts_t *alert, recip_t *recip)
 {
-	strbuffer_t *buf = NULL;
+	static strbuffer_t *buf = NULL;
 	char *eoln, *bom, *p;
 	char info[4096];
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd.c ./hobbitd/hobbitd.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd.c	2007-02-09 11:28:42.039878822 +0100
@@ -124,7 +124,7 @@
 typedef struct hobbitd_hostlist_t {
 	char *hostname;
 	char ip[IP_ADDR_STRLEN];
-	enum { H_NORMAL, H_SUMMARY, H_DIALUP } hosttype;
+	enum { H_NORMAL, H_SUMMARY } hosttype;
 	hobbitd_log_t *logs;
 	hobbitd_log_t *pinglog; /* Points to entry in logs list, but we need it often */
 	time_t clientmsgtstamp;
@@ -187,6 +187,7 @@
 hobbitd_channel_t *enadischn = NULL;	/* Receives "enable" and "disable" messages */
 hobbitd_channel_t *clientchn = NULL;	/* Receives "client" messages */
 hobbitd_channel_t *clichgchn = NULL;	/* Receives "clichg" messages */
+hobbitd_channel_t *userchn   = NULL;	/* Receives "usermsg" messages */
 
 #define NO_COLOR (COL_COUNT)
 static char *colnames[COL_COUNT+1];
@@ -431,7 +432,6 @@
 	hitem->hostname = strdup(hostname);
 	strcpy(hitem->ip, ip);
 	if (strcmp(hostname, "summary") == 0) hitem->hosttype = H_SUMMARY;
-	else if (strcmp(hostname, "dialup") == 0) hitem->hosttype = H_DIALUP;
 	else hitem->hosttype = H_NORMAL;
 	rbtInsert(rbhosts, hitem->hostname, hitem);
 
@@ -635,13 +635,14 @@
 			break;
 
 		  case C_NOTES:
+		  case C_USER:
 			n = snprintf(channel->channelbuf,  (bufsz-5),
 				"@@%s#%u|%d.%06d|%s|%s\n%s", 
 				channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
 				sender, hostname, msg);
 			if (n > (bufsz-5)) {
-				errprintf("Oversize notes msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
-					sender, hostname, log->test->name, n, bufsz);
+				errprintf("Oversize notes/user msg from %s for %s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, n, bufsz);
 			}
 			*(channel->channelbuf + bufsz - 5) = '\0';
 			break;
@@ -1292,6 +1293,13 @@
 	dbgprintf("<-handle_notes\n");
 }
 
+void handle_usermsg(char *msg, char *sender, char *hostname)
+{
+	dbgprintf("->handle_usermsg\n");
+	posttochannel(userchn, channelnames[C_USER], msg, sender, hostname, NULL, NULL);
+	dbgprintf("<-handle_usermsg\n");
+}
+
 void handle_enadis(int enabled, conn_t *msg, char *sender)
 {
 	char *firstline = NULL, *hosttest = NULL, *durstr = NULL, *txtstart = NULL;
@@ -2513,18 +2521,18 @@
 			handle_status(msg->buf, sender, h->hostname, t->name, NULL, log, color, NULL);
 		}
 	}
-	else if (strncmp(msg->buf, "notes", 5) == 0) {
+	else if ((strncmp(msg->buf, "notes", 5) == 0) || (strncmp(msg->buf, "usermsg", 7) == 0)) {
 		char *hostname, *bhost, *ehost, *p;
 		char savechar;
 
-		bhost = msg->buf + strlen("notes"); bhost += strspn(bhost, " \t");
+		bhost = msg->buf + strcspn(msg->buf, " \t\r\n"); bhost += strspn(bhost, " \t");
 		ehost = bhost + strcspn(bhost, " \t\r\n");
 		savechar = *ehost; *ehost = '\0';
 		hostname = strdup(bhost);
 		*ehost = savechar;
 
 		p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.';
-		if (*hostname == '\0') { errprintf("Invalid notes message from %s - blank hostname\n", sender); xfree(hostname); hostname = NULL; }
+		if (*hostname == '\0') { errprintf("Invalid notes/user message from %s - blank hostname\n", sender); xfree(hostname); hostname = NULL; }
 
 		if (hostname) {
 			char *hname, hostip[IP_ADDR_STRLEN];
@@ -2535,12 +2543,27 @@
 			if (hname == NULL) {
 				log_ghost(hostname, sender, msg->buf);
 			}
-			else if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) {
-				/* Invalid sender */
-				errprintf("Invalid notes message - sender %s not allowed for host %s\n", sender, hostname);
-			}
 			else {
-				handle_notes(msg->buf, sender, hostname);
+				if (*msg->buf == 'n') {
+					/* "notes" message */
+					if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) {
+						/* Invalid sender */
+						errprintf("Invalid notes message - sender %s not allowed for host %s\n", sender, hostname);
+					}
+					else {
+						handle_notes(msg->buf, sender, hostname);
+					}
+				}
+				else if (*msg->buf == 'u') {
+					/* "usermsg" message */
+					if (!oksender(statussenders, NULL, msg->addr.sin_addr, msg->buf)) {
+						/* Invalid sender */
+						errprintf("Invalid user message - sender %s not allowed for host %s\n", sender, hostname);
+					}
+					else {
+						handle_usermsg(msg->buf, sender, hostname);
+					}
+				}
 			}
 
 			xfree(hostname);
@@ -2787,7 +2810,7 @@
 				continue;
 			}
 
-			/* If there is a hostname filter, drop the "summary" and "dialup 'hosts' */
+			/* If there is a hostname filter, drop the "summary" 'hosts' */
 			if (shost && (hwalk->hosttype != H_NORMAL)) continue;
 
 			firstlog = hwalk->logs;
@@ -2953,7 +2976,7 @@
 		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
 
 		setup_filter(msg->buf, 
-			     "BBH_HOSTNAME,BBH_IP,BBH_BANKSIZE,BBH_RAW",
+			     "BBH_HOSTNAME,BBH_IP,BBH_RAW",
 			     &spage, &shost, &snet, &stest, &scolor, &acklevel, &fields,
 			     &chspage, &chshost, &chsnet, &chstest);
 
@@ -4159,6 +4182,8 @@
 	if (clientchn == NULL) { errprintf("Cannot setup client channel\n"); return 1; }
 	clichgchn  = setup_channel(C_CLICHG, CHAN_MASTER);
 	if (clichgchn == NULL) { errprintf("Cannot setup clichg channel\n"); return 1; }
+	userchn  = setup_channel(C_USER, CHAN_MASTER);
+	if (userchn == NULL) { errprintf("Cannot setup user channel\n"); return 1; }
 
 	errprintf("Setting up logfiles\n");
 	setvbuf(stdout, NULL, _IONBF, 0);
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd.c~ ./hobbitd/hobbitd.c~
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd.c~	1970-01-01 01:00:00.000000000 +0100
+++ ./hobbitd/hobbitd.c~	2007-02-09 11:27:54.415539157 +0100
@@ -0,0 +1,4580 @@
+/*----------------------------------------------------------------------------*/
+/* Hobbit message daemon.                                                     */
+/*                                                                            */
+/* This is the master daemon, hobbitd.                                        */
+/*                                                                            */
+/* This is a daemon that implements the Big Brother network protocol, with    */
+/* additional protocol items implemented for Hobbit.                          */
+/*                                                                            */
+/* This daemon maintains the full state of the Hobbit system in memory,       */
+/* eliminating the need for file-based storage of e.g. status logs. The web   */
+/* frontend programs (bbgen, bbcombotest, bb-hostsvc.cgi etc) can retrieve    */
+/* current statuslogs from this daemon to build the Hobbit webpages. However, */
+/* a "plugin" mechanism is also implemented to allow "worker modules" to      */
+/* pickup various types of events that occur in the system. This allows       */
+/* such modules to e.g. maintain the standard Hobbit file-based storage, or   */
+/* implement history logging or RRD database updates. This plugin mechanism   */
+/* uses System V IPC mechanisms for a high-performance/low-latency communi-   */
+/* cation between hobbitd and the worker modules - under no circumstances     */
+/* should the daemon be tasked with storing data to a low-bandwidth channel.  */
+/*                                                                            */
+/* Copyright (C) 2004-2006 Henrik Storner <henrik@hswn.dk>                    */
+/*                                                                            */
+/* This program is released under the GNU General Public License (GPL),       */
+/* version 2. See the file "COPYING" for details.                             */
+/*                                                                            */
+/*----------------------------------------------------------------------------*/
+
+static char rcsid[] = "$Id: hobbitd.c,v 1.253 2006/08/03 18:59:02 henrik Rel $";
+
+#include <limits.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>         /* Someday I'll move to GNU Autoconf for this ... */
+#endif
+#include <errno.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <signal.h>
+#include <time.h>
+
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/wait.h>
+
+#include "libbbgen.h"
+
+#include "hobbitd_buffer.h"
+#include "hobbitd_ipc.h"
+
+#define DISABLED_UNTIL_OK -1
+
+/*
+ * The absolute maximum size we'll grow our buffers to accomodate an incoming message.
+ * This is really just an upper bound to squash the bad guys trying to data-flood us. 
+ */
+#define MAX_HOBBIT_INBUFSZ (10*1024*1024)	/* 10 MB */
+
+/* The initial size of an input buffer. Make this large enough for most traffic. */
+#define HOBBIT_INBUF_INITIAL   (128*1024)
+
+/* How much the input buffer grows per re-allocation */
+#define HOBBIT_INBUF_INCREMENT (32*1024)
+
+/* How long to keep an ack after the status has recovered */
+#define ACKCLEARDELAY 720 /* 12 minutes */
+
+htnames_t *metanames = NULL;
+typedef struct hobbitd_meta_t {
+	htnames_t *metaname;
+	char *value;
+	struct hobbitd_meta_t *next;
+} hobbitd_meta_t;
+
+typedef struct ackinfo_t {
+	int level;
+	time_t received, validuntil, cleartime;
+	char *ackedby;
+	char *msg;
+	struct ackinfo_t *next;
+} ackinfo_t;
+
+typedef struct testinfo_t {
+	char *name;
+	int clientsave;
+} testinfo_t;
+
+/* This holds all information about a single status */
+typedef struct hobbitd_log_t {
+	struct hobbitd_hostlist_t *host;
+	testinfo_t *test;
+	char *origin;
+	int color, oldcolor, activealert, histsynced, downtimeactive;
+	char *testflags;
+	char *grouplist;        /* For extended status reports (e.g. from hobbitd_client) */
+	char sender[IP_ADDR_STRLEN];
+	time_t lastchange;	/* time when the currently logged status began */
+	time_t logtime;		/* time when last update was received */
+	time_t validtime;	/* time when status is no longer valid */
+	time_t enabletime;	/* time when test auto-enables after a disable */
+	time_t acktime;		/* time when test acknowledgement expires */
+	unsigned char *message;
+	int msgsz;
+	unsigned char *dismsg, *ackmsg;
+	int cookie;
+	time_t cookieexpires;
+	struct hobbitd_meta_t *metas;
+	ackinfo_t *acklist;	/* Holds list of acks */
+	struct hobbitd_log_t *next;
+} hobbitd_log_t;
+
+/* This is a list of the hosts we have seen reports for, and links to their status logs */
+typedef struct hobbitd_hostlist_t {
+	char *hostname;
+	char ip[IP_ADDR_STRLEN];
+	enum { H_NORMAL, H_SUMMARY } hosttype;
+	hobbitd_log_t *logs;
+	hobbitd_log_t *pinglog; /* Points to entry in logs list, but we need it often */
+	time_t clientmsgtstamp;
+	char *clientmsg;
+	int clientmsgposted;
+} hobbitd_hostlist_t;
+
+typedef struct filecache_t {
+	char *fn;
+	long len;
+	unsigned char *fdata;
+} filecache_t;
+
+RbtHandle rbhosts;				/* The hosts we have reports from */
+RbtHandle rbtests;				/* The tests (columns) we have seen */
+RbtHandle rborigins;				/* The origins we have seen */
+RbtHandle rbcookies;				/* The cookies we use */
+RbtHandle rbfilecache;
+
+sender_t *maintsenders = NULL;
+sender_t *statussenders = NULL;
+sender_t *adminsenders = NULL;
+sender_t *wwwsenders = NULL;
+sender_t *tracelist = NULL;
+int      traceall = 0;
+int      ignoretraced = 0;
+int      clientsavemem = 1;	/* In memory */
+int      clientsavedisk = 0;	/* On disk via the CLICHG channel */
+int      allow_downloads = 1;
+
+#define NOTALK 0
+#define RECEIVING 1
+#define RESPONDING 2
+
+/* This struct describes an active connection with a Hobbit client */
+typedef struct conn_t {
+	int sock;			/* Communications socket */
+	struct sockaddr_in addr;	/* Client source address */
+	unsigned char *buf, *bufp;	/* Message buffer and pointer */
+	size_t buflen, bufsz;		/* Active and maximum length of buffer */
+	int doingwhat;			/* Communications state (NOTALK, READING, RESPONDING) */
+	time_t timeout;			/* When the timeout for this connection happens */
+	struct conn_t *next;
+} conn_t;
+
+enum droprencmd_t { CMD_DROPHOST, CMD_DROPTEST, CMD_RENAMEHOST, CMD_RENAMETEST, CMD_DROPSTATE };
+
+static volatile int running = 1;
+static volatile int reloadconfig = 0;
+static volatile time_t nextcheckpoint = 0;
+static volatile int dologswitch = 0;
+static volatile int gotalarm = 0;
+
+/* Our channels to worker modules */
+hobbitd_channel_t *statuschn = NULL;	/* Receives full "status" messages */
+hobbitd_channel_t *stachgchn = NULL;	/* Receives brief message about a status change */
+hobbitd_channel_t *pagechn   = NULL;	/* Receives alert messages (triggered from status changes) */
+hobbitd_channel_t *datachn   = NULL;	/* Receives raw "data" messages */
+hobbitd_channel_t *noteschn  = NULL;	/* Receives raw "notes" messages */
+hobbitd_channel_t *enadischn = NULL;	/* Receives "enable" and "disable" messages */
+hobbitd_channel_t *clientchn = NULL;	/* Receives "client" messages */
+hobbitd_channel_t *clichgchn = NULL;	/* Receives "clichg" messages */
+hobbitd_channel_t *userchn   = NULL;	/* Receives "usermsg" messages */
+
+#define NO_COLOR (COL_COUNT)
+static char *colnames[COL_COUNT+1];
+int alertcolors, okcolors;
+enum alertstate_t { A_OK, A_ALERT, A_UNDECIDED };
+
+typedef struct ghostlist_t {
+	char *name;
+	char *sender;
+	time_t tstamp;
+} ghostlist_t;
+RbtHandle rbghosts;
+
+int ghosthandling = -1;
+
+char *checkpointfn = NULL;
+FILE *dbgfd = NULL;
+char *dbghost = NULL;
+time_t boottime;
+int  hostcount = 0;
+char *ackinfologfn = NULL;
+FILE *ackinfologfd = NULL;
+
+typedef struct hobbitd_statistics_t {
+	char *cmd;
+	unsigned long count;
+} hobbitd_statistics_t;
+
+hobbitd_statistics_t hobbitd_stats[] = {
+	{ "status", 0 },
+	{ "combo", 0 },
+	{ "page", 0 },
+	{ "summary", 0 },
+	{ "data", 0 },
+	{ "client", 0 },
+	{ "notes", 0 },
+	{ "enable", 0 },
+	{ "disable", 0 },
+	{ "ack", 0 },
+	{ "config", 0 },
+	{ "query", 0 },
+	{ "hobbitdboard", 0 },
+	{ "hobbitdlog", 0 },
+	{ "drop", 0 },
+	{ "rename", 0 },
+	{ "dummy", 0 },
+	{ "ping", 0 },
+	{ "notify", 0 },
+	{ "schedule", 0 },
+	{ "download", 0 },
+	{ NULL, 0 }
+};
+
+enum boardfield_t { F_NONE, F_HOSTNAME, F_TESTNAME, F_COLOR, F_FLAGS, 
+		    F_LASTCHANGE, F_LOGTIME, F_VALIDTIME, F_ACKTIME, F_DISABLETIME,
+		    F_SENDER, F_COOKIE, F_LINE1,
+		    F_ACKMSG, F_DISMSG, F_MSG, F_CLIENT, F_CLIENTTSTAMP,
+		    F_ACKLIST,
+		    F_HOSTINFO,
+		    F_LAST };
+
+typedef struct boardfieldnames_t {
+	char *name;
+	enum boardfield_t id;
+} boardfieldnames_t;
+boardfieldnames_t boardfieldnames[] = {
+	{ "hostname", F_HOSTNAME },
+	{ "testname", F_TESTNAME },
+	{ "color", F_COLOR },
+	{ "flags", F_FLAGS },
+	{ "lastchange", F_LASTCHANGE },
+	{ "logtime", F_LOGTIME },
+	{ "validtime", F_VALIDTIME },
+	{ "acktime", F_ACKTIME },
+	{ "disabletime", F_DISABLETIME },
+	{ "sender", F_SENDER },
+	{ "cookie", F_COOKIE },
+	{ "line1", F_LINE1 },
+	{ "ackmsg", F_ACKMSG },
+	{ "dismsg", F_DISMSG },
+	{ "msg", F_MSG },
+	{ "client", F_CLIENT },
+	{ "clntstamp", F_CLIENTTSTAMP },
+	{ "acklist", F_ACKLIST },
+	{ "BBH_", F_HOSTINFO },
+	{ NULL, F_LAST },
+};
+typedef struct boardfields_t {
+	enum boardfield_t field;
+	enum bbh_item_t bbhfield;
+} boardfield_t;
+#define BOARDFIELDS_MAX 50
+boardfield_t boardfields[BOARDFIELDS_MAX+1];
+
+unsigned long msgs_total = 0;
+unsigned long msgs_total_last = 0;
+time_t last_stats_time = 0;
+
+/* List of scheduled (future) tasks */
+typedef struct scheduletask_t {
+	int id;
+	time_t executiontime;
+	char *command;
+	char *sender;
+	struct scheduletask_t *next;
+} scheduletask_t;
+scheduletask_t *schedulehead = NULL;
+int nextschedid = 1;
+
+void update_statistics(char *cmd)
+{
+	int i;
+
+	dbgprintf("-> update_statistics\n");
+
+	if (!cmd) {
+		dbgprintf("No command for update_statistics\n");
+		return;
+	}
+
+	msgs_total++;
+
+	i = 0;
+	while (hobbitd_stats[i].cmd && strncmp(hobbitd_stats[i].cmd, cmd, strlen(hobbitd_stats[i].cmd))) { i++; }
+	hobbitd_stats[i].count++;
+
+	dbgprintf("<- update_statistics\n");
+}
+
+char *generate_stats(void)
+{
+	static char *statsbuf = NULL;
+	static int statsbuflen = 0;
+	time_t now = time(NULL);
+	char *bufp;
+	int i, clients;
+	char bootuptxt[40];
+	char uptimetxt[40];
+	RbtHandle ghandle;
+	time_t uptime = (now - boottime);
+
+	dbgprintf("-> generate_stats\n");
+
+	MEMDEFINE(bootuptxt);
+	MEMDEFINE(uptimetxt);
+
+	if (statsbuf == NULL) {
+		statsbuflen = 8192;
+		statsbuf = (char *)malloc(statsbuflen);
+	}
+	bufp = statsbuf;
+
+	strftime(bootuptxt, sizeof(bootuptxt), "%d-%b-%Y %T", localtime(&boottime));
+	sprintf(uptimetxt, "%d days, %02d:%02d:%02d", 
+		(int)(uptime / 86400), (int)(uptime % 86400)/3600, (int)(uptime % 3600)/60, (int)(uptime % 60));
+
+	bufp += sprintf(bufp, "status %s.hobbitd %s\nStatistics for Hobbit daemon\nUp since %s (%s)\n\n",
+			xgetenv("MACHINE"), colorname(errbuf ? COL_YELLOW : COL_GREEN), bootuptxt, uptimetxt);
+	bufp += sprintf(bufp, "Incoming messages      : %10ld\n", msgs_total);
+	i = 0;
+	while (hobbitd_stats[i].cmd) {
+		bufp += sprintf(bufp, "- %-20s : %10ld\n", hobbitd_stats[i].cmd, hobbitd_stats[i].count);
+		i++;
+	}
+	bufp += sprintf(bufp, "- %-20s : %10ld\n", "Bogus/Timeouts ", hobbitd_stats[i].count);
+
+	if ((now > last_stats_time) && (last_stats_time > 0)) {
+		bufp += sprintf(bufp, "Incoming messages/sec  : %10ld (average last %d seconds)\n", 
+			((msgs_total - msgs_total_last) / (now - last_stats_time)), 
+			(int)(now - last_stats_time));
+	}
+	msgs_total_last = msgs_total;
+
+	bufp += sprintf(bufp, "\n");
+	clients = semctl(statuschn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "status channel messages: %10ld (%d readers)\n", statuschn->msgcount, clients);
+	clients = semctl(stachgchn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "stachg channel messages: %10ld (%d readers)\n", stachgchn->msgcount, clients);
+	clients = semctl(pagechn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "page   channel messages: %10ld (%d readers)\n", pagechn->msgcount, clients);
+	clients = semctl(datachn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "data   channel messages: %10ld (%d readers)\n", datachn->msgcount, clients);
+	clients = semctl(noteschn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "notes  channel messages: %10ld (%d readers)\n", noteschn->msgcount, clients);
+	clients = semctl(enadischn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "enadis channel messages: %10ld (%d readers)\n", enadischn->msgcount, clients);
+	clients = semctl(clientchn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "client channel messages: %10ld (%d readers)\n", clientchn->msgcount, clients);
+	clients = semctl(clichgchn->semid, CLIENTCOUNT, GETVAL);
+	bufp += sprintf(bufp, "clichg channel messages: %10ld (%d readers)\n", clichgchn->msgcount, clients);
+
+	ghandle = rbtBegin(rbghosts);
+	if (ghandle != rbtEnd(rbghosts)) bufp += sprintf(bufp, "\n\nGhost reports:\n");
+	for (; (ghandle != rbtEnd(rbghosts)); ghandle = rbtNext(rbghosts, ghandle)) {
+		ghostlist_t *gwalk = (ghostlist_t *)gettreeitem(rbghosts, ghandle);
+
+		/* Skip records older than 10 minutes */
+		if (gwalk->tstamp < (now - 600)) continue;
+
+		if ((statsbuflen - (bufp - statsbuf)) < 512) {
+			/* Less than 512 bytes left in buffer - expand it */
+			statsbuflen += 4096;
+			statsbuf = (char *)realloc(statsbuf, statsbuflen);
+			bufp = statsbuf + strlen(statsbuf);
+		}
+
+		bufp += sprintf(bufp, "  %-15s reported host %s\n", gwalk->sender, gwalk->name);
+	}
+
+	if (errbuf) {
+		if ((strlen(statsbuf) + strlen(errbuf) + 1024) > statsbuflen) {
+			statsbuflen = strlen(statsbuf) + strlen(errbuf) + 1024;
+			statsbuf = (char *)realloc(statsbuf, statsbuflen);
+			bufp = statsbuf + strlen(statsbuf);
+		}
+
+		bufp += sprintf(bufp, "\n\nLatest errormessages:\n%s\n", errbuf);
+	}
+
+	MEMUNDEFINE(bootuptxt);
+	MEMUNDEFINE(uptimetxt);
+
+	dbgprintf("<- generate_stats\n");
+
+	return statsbuf;
+}
+
+
+enum alertstate_t decide_alertstate(int color)
+{
+	if ((okcolors & (1 << color)) != 0) return A_OK;
+	else if ((alertcolors & (1 << color)) != 0) return A_ALERT;
+	else return A_UNDECIDED;
+}
+
+
+hobbitd_hostlist_t *create_hostlist_t(char *hostname, char *ip)
+{
+	hobbitd_hostlist_t *hitem;
+
+	hitem = (hobbitd_hostlist_t *) calloc(1, sizeof(hobbitd_hostlist_t));
+	hitem->hostname = strdup(hostname);
+	strcpy(hitem->ip, ip);
+	if (strcmp(hostname, "summary") == 0) hitem->hosttype = H_SUMMARY;
+	else hitem->hosttype = H_NORMAL;
+	rbtInsert(rbhosts, hitem->hostname, hitem);
+
+	return hitem;
+}
+
+testinfo_t *create_testinfo(char *name)
+{
+	testinfo_t *newrec;
+
+	newrec = (testinfo_t *)calloc(1, sizeof(testinfo_t));
+	newrec->name = strdup(name);
+	newrec->clientsave = clientsavedisk;
+	rbtInsert(rbtests, newrec->name, newrec);
+
+	return newrec;
+}
+
+void posttochannel(hobbitd_channel_t *channel, char *channelmarker, 
+		   char *msg, char *sender, char *hostname, hobbitd_log_t *log, char *readymsg)
+{
+	struct sembuf s;
+	int clients;
+	int n;
+	struct timeval tstamp;
+	struct timezone tz;
+	int semerr = 0;
+	unsigned int bufsz = 1024*shbufsz(channel->channelid);
+
+	dbgprintf("-> posttochannel\n");
+
+	/* First see how many users are on this channel */
+	clients = semctl(channel->semid, CLIENTCOUNT, GETVAL);
+	if (clients == 0) {
+		dbgprintf("Dropping message - no readers\n");
+		return;
+	}
+
+	/* 
+	 * Wait for BOARDBUSY to go low.
+	 * We need a loop here, because if we catch a signal
+	 * while waiting on the semaphore, then we need to
+	 * re-start the semaphore wait. Otherwise we may
+	 * end up with semaphores that are out of sync
+	 * (GOCLIENT goes up while a worker waits for it 
+	 *  to go to 0).
+	 */
+	gotalarm = 0; alarm(5);
+	do {
+		s.sem_num = BOARDBUSY; s.sem_op = 0; s.sem_flg = 0;
+		n = semop(channel->semid, &s, 1);
+		if (n == -1) {
+			semerr = errno;
+			if (semerr != EINTR) errprintf("semop failed, %s\n", strerror(errno));
+		}
+	} while ((n == -1) && (semerr == EINTR) && running && !gotalarm);
+	alarm(0);
+	if (!running) return;
+
+	/* Check if the alarm fired */
+	if (gotalarm) {
+		errprintf("BOARDBUSY locked at %d, GETNCNT is %d, GETPID is %d, %d clients\n",
+			  semctl(channel->semid, BOARDBUSY, GETVAL),
+			  semctl(channel->semid, BOARDBUSY, GETNCNT),
+			  semctl(channel->semid, BOARDBUSY, GETPID),
+			  semctl(channel->semid, CLIENTCOUNT, GETVAL));
+		return;
+	}
+
+	/* All clear, post the message */
+	if (channel->seq == 999999) channel->seq = 0;
+	channel->seq++;
+	channel->msgcount++;
+	gettimeofday(&tstamp, &tz);
+	if (readymsg) {
+		n = snprintf(channel->channelbuf, (bufsz-5),
+			    "@@%s#%u|%d.%06d|%s|%s", 
+			    channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, sender,
+			    readymsg);
+		if (n > (bufsz-5)) {
+			char *p, *overmsg = readymsg;
+			*(overmsg+100) = '\0';
+			p = strchr(overmsg, '\n'); if (p) *p = '\0';
+			errprintf("Oversize data/client msg from %s truncated (n=%d, limit %d)\nFirst line: %s\n", 
+				   sender, n, bufsz, overmsg);
+		}
+		*(channel->channelbuf + bufsz - 5) = '\0';
+	}
+	else {
+		switch(channel->channelid) {
+		  case C_STATUS:
+			n = snprintf(channel->channelbuf, (bufsz-5),
+				"@@%s#%u|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%s|%d", 
+				channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
+				sender, log->origin, hostname, log->test->name, 
+				(int) log->validtime, colnames[log->color], (log->testflags ? log->testflags : ""),
+				colnames[log->oldcolor], (int) log->lastchange); 
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s",
+					(int)log->acktime, nlencode(log->ackmsg));
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s",
+					(int)log->enabletime, nlencode(log->dismsg));
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d",
+					(int)log->host->clientmsgtstamp);
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "\n%s", msg);
+			}
+			if (n > (bufsz-5)) {
+				errprintf("Oversize status msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, log->test->name, n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			break;
+
+		  case C_STACHG:
+			n = snprintf(channel->channelbuf, (bufsz-5),
+				"@@%s#%u|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%d", 
+				channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
+				sender, log->origin, hostname, log->test->name, 
+				(int) log->validtime, colnames[log->color], 
+				colnames[log->oldcolor], (int) log->lastchange);
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d|%s",
+					(int)log->enabletime, nlencode(log->dismsg));
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", log->downtimeactive);
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "|%d", 
+						(int) log->host->clientmsgtstamp);
+			}
+			if (n < (bufsz-5)) {
+				n += snprintf(channel->channelbuf+n, (bufsz-n-5), "\n%s", msg);
+			}
+			if (n > (bufsz-5)) {
+				errprintf("Oversize stachg msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, log->test->name, n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			break;
+
+		  case C_CLICHG:
+			n = snprintf(channel->channelbuf, (bufsz-5),
+				"@@%s#%u|%d.%06d|%s|%s|%d\n%s",
+				channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec,
+				sender, hostname, (int) log->host->clientmsgtstamp, 
+				log->host->clientmsg);
+			if (n > (bufsz-5)) {
+				errprintf("Oversize clichg msg from %s for %s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			log->host->clientmsgposted = 1;
+			break;
+
+		  case C_PAGE:
+			if (strcmp(channelmarker, "ack") == 0) {
+				n = snprintf(channel->channelbuf, (bufsz-5),
+					"@@%s#%u|%d.%06d|%s|%s|%s|%s|%d\n%s", 
+					channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
+					sender, hostname, 
+					log->test->name, log->host->ip,
+					(int) log->acktime, msg);
+			}
+			else {
+				namelist_t *hi = hostinfo(hostname);
+				char *pagepath, *classname, *osname;
+
+				pagepath = (hi ? bbh_item(hi, BBH_PAGEPATH) : "");
+				classname = (hi ? bbh_item(hi, BBH_CLASS) : "");
+				osname = (hi ? bbh_item(hi, BBH_OS) : "");
+				if (!classname) classname = "";
+				if (!osname) osname = "";
+
+				n = snprintf(channel->channelbuf, (bufsz-5),
+					"@@%s#%u|%d.%06d|%s|%s|%s|%s|%d|%s|%s|%d|%s|%d|%s|%s|%s\n%s", 
+					channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
+					sender, hostname, 
+					log->test->name, log->host->ip, (int) log->validtime, 
+					colnames[log->color], colnames[log->oldcolor], (int) log->lastchange,
+					pagepath, log->cookie, osname, classname, 
+					(log->grouplist ? log->grouplist : ""),
+					msg);
+			}
+			if (n > (bufsz-5)) {
+				errprintf("Oversize page/ack/notify msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, (log->test->name ? log->test->name : "<none>"), n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			break;
+
+		  case C_DATA:
+		  case C_CLIENT:
+			/* Data channel messages are pre-formatted so we never go here */
+			break;
+
+		  case C_NOTES:
+			n = snprintf(channel->channelbuf,  (bufsz-5),
+				"@@%s#%u|%d.%06d|%s|%s\n%s", 
+				channelmarker, channel->seq, (int) tstamp.tv_sec, (int) tstamp.tv_usec, 
+				sender, hostname, msg);
+			if (n > (bufsz-5)) {
+				errprintf("Oversize notes msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, log->test->name, n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			break;
+
+		  case C_ENADIS:
+			n = snprintf(channel->channelbuf, (bufsz-5),
+				"@@%s#%u|%d.%06d|%s|%s|%s|%d",
+				channelmarker, channel->seq, (int) tstamp.tv_sec, (int)tstamp.tv_usec,
+				sender, hostname, log->test->name, (int) log->enabletime);
+			if (n > (bufsz-5)) {
+				errprintf("Oversize enadis msg from %s for %s:%s truncated (n=%d, limit=%d)\n", 
+					sender, hostname, log->test->name, n, bufsz);
+			}
+			*(channel->channelbuf + bufsz - 5) = '\0';
+			break;
+
+		  case C_LAST:
+			break;
+		}
+	}
+	/* Terminate the message */
+	strncat(channel->channelbuf, "\n@@\n", (bufsz-1));
+
+	/* Let the readers know it is there.  */
+	clients = semctl(channel->semid, CLIENTCOUNT, GETVAL); /* Get it again, maybe changed since last check */
+	dbgprintf("Posting message %u to %d readers\n", channel->seq, clients);
+	/* Up BOARDBUSY */
+	s.sem_num = BOARDBUSY; 
+	s.sem_op = (clients - semctl(channel->semid, BOARDBUSY, GETVAL)); 
+	if (s.sem_op <= 0) {
+		errprintf("How did this happen? clients=%d, s.sem_op=%d\n", clients, s.sem_op);
+		s.sem_op = clients;
+	}
+	s.sem_flg = 0;
+	n = semop(channel->semid, &s, 1);
+
+	/* Make sure GOCLIENT is 0 */
+	n = semctl(channel->semid, GOCLIENT, GETVAL);
+	if (n > 0) {
+		errprintf("Oops ... GOCLIENT is high (%d)\n", n);
+	}
+
+	s.sem_num = GOCLIENT; s.sem_op = clients; s.sem_flg = 0; 		/* Up GOCLIENT */
+	n = semop(channel->semid, &s, 1);
+
+	dbgprintf("<- posttochannel\n");
+
+	return;
+}
+
+
+void log_ghost(char *hostname, char *sender, char *msg)
+{
+	RbtHandle ghandle;
+	ghostlist_t *gwalk;
+
+	dbgprintf("-> log_ghost\n");
+
+	/* If debugging, log the full request */
+	if (dbgfd) {
+		fprintf(dbgfd, "\n---- combo message from %s ----\n%s---- end message ----\n", sender, msg);
+		fflush(dbgfd);
+	}
+
+	if ((hostname == NULL) || (sender == NULL)) return;
+
+	ghandle = rbtFind(rbghosts, hostname);
+	if (ghandle == rbtEnd(rbghosts)) {
+		gwalk = (ghostlist_t *)malloc(sizeof(ghostlist_t));
+		gwalk->name = strdup(hostname);
+		gwalk->sender = strdup(sender);
+		gwalk->tstamp = time(NULL);
+		rbtInsert(rbghosts, gwalk->name, gwalk);
+	}
+	else {
+		gwalk = (ghostlist_t *)gettreeitem(rbghosts, ghandle);
+		if (gwalk->sender) xfree(gwalk->sender);
+		gwalk->sender = strdup(sender);
+		gwalk->tstamp = time(NULL);
+	}
+
+	dbgprintf("<- log_ghost\n");
+}
+
+hobbitd_log_t *find_log(char *hostname, char *testname, char *origin, hobbitd_hostlist_t **host)
+{
+	RbtIterator hosthandle, testhandle, originhandle;
+	hobbitd_hostlist_t *hwalk;
+	char *owalk = NULL;
+	testinfo_t *twalk;
+	hobbitd_log_t *lwalk;
+
+	*host = NULL;
+	if ((hostname == NULL) || (testname == NULL)) return NULL;
+
+	hosthandle = rbtFind(rbhosts, hostname);
+	if (hosthandle != rbtEnd(rbhosts)) *host = hwalk = gettreeitem(rbhosts, hosthandle); else return NULL;
+
+	testhandle = rbtFind(rbtests, testname);
+	if (testhandle != rbtEnd(rbtests)) twalk = gettreeitem(rbtests, testhandle); else return NULL;
+
+	if (origin) {
+		originhandle = rbtFind(rborigins, origin);
+		if (originhandle != rbtEnd(rborigins)) owalk = gettreeitem(rborigins, originhandle);
+	}
+
+	for (lwalk = hwalk->logs; (lwalk && ((lwalk->test != twalk) || (lwalk->origin != owalk))); lwalk = lwalk->next);
+	return lwalk;
+}
+
+void get_hts(char *msg, char *sender, char *origin,
+	     hobbitd_hostlist_t **host, testinfo_t **test, char **grouplist, hobbitd_log_t **log, 
+	     int *color, char **downcause, int *alltests, int createhost, int createlog)
+{
+	/*
+	 * This routine takes care of finding existing status log records, or
+	 * (if they dont exist) creating new ones for an incoming status.
+	 *
+	 * "msg" contains an incoming message. First list is of the form "KEYWORD host,domain.test COLOR"
+	 */
+
+	char *firstline, *p;
+	char *hosttest, *hostname, *testname, *colstr, *grp;
+	char hostip[IP_ADDR_STRLEN];
+	RbtIterator hosthandle, testhandle, originhandle;
+	hobbitd_hostlist_t *hwalk = NULL;
+	testinfo_t *twalk = NULL;
+	char *owalk = NULL;
+	hobbitd_log_t *lwalk = NULL;
+
+	dbgprintf("-> get_hts\n");
+
+	MEMDEFINE(hostip);
+	*hostip = '\0';
+
+	*host = NULL;
+	*test = NULL;
+	*log = NULL;
+	*color = -1;
+	if (grouplist) *grouplist = NULL;
+	if (downcause) *downcause = NULL;
+	if (alltests) *alltests = 0;
+
+	hosttest = hostname = testname = colstr = grp = NULL;
+	p = strchr(msg, '\n');
+	if (p == NULL) {
+		firstline = strdup(msg);
+	}
+	else {
+		*p = '\0';
+		firstline = strdup(msg); 
+		*p = '\n';
+	}
+
+	p = strtok(firstline, " \t"); /* Keyword ... */
+	if (p) {
+		/* There might be a group-list */
+		grp = strstr(p, "/group:");
+		if (grp) grp += 7;
+	}
+	if (p) hosttest = strtok(NULL, " \t"); /* ... HOST.TEST combo ... */
+	if (hosttest == NULL) goto done;
+	colstr = strtok(NULL, " \t"); /* ... and the color (if any) */
+	if (colstr) {
+		*color = parse_color(colstr);
+		/* Dont create log-entries if we get a bad color spec. */
+		if (*color == -1) createlog = 0;
+	}
+	else createlog = 0;
+
+	if (strncmp(msg, "summary", 7) == 0) {
+		/* Summary messages are handled specially */
+		hostname = hosttest;	/* This will always be "summary" */
+		testname = strchr(hosttest, '.');
+		if (testname) { *testname = '\0'; testname++; }
+	}
+	else {
+		char *knownname;
+
+		hostname = hosttest;
+		testname = strrchr(hosttest, '.');
+		if (testname) { *testname = '\0'; testname++; }
+		p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.';
+
+		knownname = knownhost(hostname, hostip, ghosthandling);
+		if (knownname == NULL) {
+			log_ghost(hostname, sender, msg);
+			goto done;
+		}
+		hostname = knownname;
+	}
+
+	hosthandle = rbtFind(rbhosts, hostname);
+	if (hosthandle == rbtEnd(rbhosts)) hwalk = NULL;
+	else hwalk = gettreeitem(rbhosts, hosthandle);
+
+	if (createhost && (hosthandle == rbtEnd(rbhosts))) {
+		hwalk = create_hostlist_t(hostname, hostip);
+		hostcount++;
+	}
+
+	if (testname && *testname) {
+		if (alltests && (*testname == '*')) {
+			*alltests = 1;
+			return;
+		}
+
+		testhandle = rbtFind(rbtests, testname);
+		if (testhandle != rbtEnd(rbtests)) twalk = gettreeitem(rbtests, testhandle);
+		if (createlog && (twalk == NULL)) twalk = create_testinfo(testname);
+	}
+	else {
+		if (createlog) errprintf("Bogus message from %s: No testname '%s'\n", sender, msg);
+	}
+
+	if (origin) {
+		originhandle = rbtFind(rborigins, origin);
+		if (originhandle != rbtEnd(rborigins)) owalk = gettreeitem(rborigins, originhandle);
+		if (createlog && (owalk == NULL)) {
+			owalk = strdup(origin);
+			rbtInsert(rborigins, owalk, owalk);
+		}
+	}
+
+	if (hwalk && twalk && owalk) {
+		for (lwalk = hwalk->logs; (lwalk && ((lwalk->test != twalk) || (lwalk->origin != owalk))); lwalk = lwalk->next);
+		if (createlog && (lwalk == NULL)) {
+			lwalk = (hobbitd_log_t *)calloc(1, sizeof(hobbitd_log_t));
+			lwalk->color = lwalk->oldcolor = NO_COLOR;
+			lwalk->host = hwalk;
+			lwalk->test = twalk;
+			lwalk->origin = owalk;
+			lwalk->cookie = -1;
+			lwalk->next = hwalk->logs;
+			hwalk->logs = lwalk;
+			if (strcmp(testname, xgetenv("PINGCOLUMN")) == 0) hwalk->pinglog = lwalk;
+		}
+	}
+
+done:
+	if (colstr) {
+		if ((*color == COL_RED) || (*color == COL_YELLOW)) {
+			char *cause;
+
+			cause = check_downtime(hostname, testname);
+			if (lwalk) lwalk->downtimeactive = (cause != NULL);
+			if (cause) *color = COL_BLUE;
+			if (downcause) *downcause = cause;
+		}
+		else {
+			if (lwalk) lwalk->downtimeactive = 0;
+		}
+	}
+
+	if (grouplist && grp) *grouplist = strdup(grp);
+
+	xfree(firstline);
+
+	*host = hwalk;
+	*test = twalk;
+	*log = lwalk;
+
+	MEMUNDEFINE(hostip);
+
+	dbgprintf("<- get_hts\n");
+}
+
+
+hobbitd_log_t *find_cookie(int cookie)
+{
+	/*
+	 * Find a cookie we have issued.
+	 */
+	hobbitd_log_t *result = NULL;
+	RbtIterator cookiehandle;
+
+	dbgprintf("-> find_cookie\n");
+
+	cookiehandle = rbtFind(rbcookies, (void *)cookie);
+	if (cookiehandle != rbtEnd(rbcookies)) {
+		result = gettreeitem(rbcookies, cookiehandle);
+		if (result->cookieexpires <= time(NULL)) result = NULL;
+	}
+
+	dbgprintf("<- find_cookie\n");
+
+	return result;
+}
+
+void clear_cookie(hobbitd_log_t *log)
+{
+	RbtIterator cookiehandle;
+
+	if (log->cookie <= 0) return;
+
+	cookiehandle = rbtFind(rbcookies, (void *)log->cookie);
+	log->cookie = -1; log->cookieexpires = 0;
+
+	if (cookiehandle == rbtEnd(rbcookies)) return;
+
+	rbtErase(rbcookies, cookiehandle);
+}
+
+
+void handle_status(unsigned char *msg, char *sender, char *hostname, char *testname, char *grouplist, 
+		   hobbitd_log_t *log, int newcolor, char *downcause)
+{
+	int validity = 30;	/* validity is counted in minutes */
+	time_t now = time(NULL);
+	int msglen, issummary;
+	enum alertstate_t oldalertstatus, newalertstatus;
+
+	dbgprintf("->handle_status\n");
+
+	if (msg == NULL) {
+		errprintf("handle_status got a NULL message for %s.%s, sender %s, color %s\n", 
+			  textornull(hostname), textornull(testname), textornull(sender), colorname(newcolor));
+		return;
+	}
+
+	msglen = strlen(msg);
+	if (msglen == 0) {
+		errprintf("Bogus status message for %s.%s contains no data: Sent from %s\n", 
+			  textornull(hostname), textornull(testname), textornull(sender));
+		return;
+	}
+	if (msg_data(msg) == (char *)msg) {
+		errprintf("Bogus status message: msg_data finds no host.test. Sent from: '%s', data:'%s'\n",
+			  sender, msg);
+		return;
+	}
+
+	issummary = (log->host->hosttype == H_SUMMARY);
+
+	if (strncmp(msg, "status+", 7) == 0) {
+		validity = durationvalue(msg+7);
+	}
+
+	if (log->enabletime == DISABLED_UNTIL_OK) {
+		/* The test is disabled until we get an OK status */
+		if ((newcolor != COL_BLUE) && (decide_alertstate(newcolor) == A_OK)) {
+			/* It's OK now - clear the disable status */
+			log->enabletime = 0;
+			if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; }
+			posttochannel(enadischn, channelnames[C_ENADIS], msg, sender, log->host->hostname, log, NULL);
+		}
+		else {
+			/* Still not OK - keep it BLUE */
+			newcolor = COL_BLUE;
+		}
+	}
+	else if (log->enabletime > now) {
+		/* The test is currently disabled. */
+		newcolor = COL_BLUE;
+	}
+	else if (log->enabletime) {
+		/* A disable has expired. Clear the timestamp and the message buffer */
+		log->enabletime = 0;
+		if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; }
+		posttochannel(enadischn, channelnames[C_ENADIS], msg, sender, log->host->hostname, log, NULL);
+	}
+	else {
+		/* If we got a downcause, and the status is not disabled, use downcause as the disable text */
+		if (log->dismsg) { xfree(log->dismsg); log->dismsg = NULL; }
+		if (downcause && (newcolor == COL_BLUE)) log->dismsg = strdup(downcause);
+	}
+
+	if (log->acktime) {
+		/* Handling of ack'ed tests */
+
+		if (decide_alertstate(newcolor) == A_OK) {
+			/* The test recovered. Clear the ack. */
+			log->acktime = 0;
+		}
+
+		if (log->acktime > now) {
+			/* Dont need to do anything about an acked test */
+		}
+		else {
+			/* The acknowledge has expired. Clear the timestamp and the message buffer */
+			log->acktime = 0;
+			if (log->ackmsg) { xfree(log->ackmsg); log->ackmsg = NULL; }
+		}
+	}
+
+	log->logtime = now;
+
+	/*
+	 * Decide how long this status is valid.
+	 *
+	 * Normally we'll just set the valid time according 
+	 * to the validity of the status report.
+	 *
+	 * If the status is acknowledged, make it valid for the longest period
+	 * of the acknowledgment and the normal validity (so an acknowledged status
+	 * does not go purple because it is not being updated due to the host being down).
+	 *
+	 * Same tweak must be done for disabled tests.
+	 */
+	log->validtime = now + validity*60;
+	if (log->acktime    && (log->acktime > log->validtime))    log->validtime = log->acktime;
+	if (log->enabletime) {
+		if (log->enabletime == DISABLED_UNTIL_OK) log->validtime = INT_MAX;
+		else if (log->enabletime > log->validtime) log->validtime = log->enabletime;
+	}
+
+	strncpy(log->sender, sender, sizeof(log->sender)-1);
+	*(log->sender + sizeof(log->sender) - 1) = '\0';
+	log->oldcolor = log->color;
+	log->color = newcolor;
+	oldalertstatus = decide_alertstate(log->oldcolor);
+	newalertstatus = decide_alertstate(newcolor);
+	if (log->grouplist) xfree(log->grouplist);
+	if (grouplist) log->grouplist = strdup(grouplist);
+
+	if (log->acklist) {
+		ackinfo_t *awalk;
+
+		if ((oldalertstatus != A_OK) && (newalertstatus == A_OK)) {
+			/* The status recovered. Set the "clearack" timer */
+			time_t cleartime = getcurrenttime(NULL) + ACKCLEARDELAY;
+			for (awalk = log->acklist; (awalk); awalk = awalk->next) awalk->cleartime = cleartime;
+		}
+		else if ((oldalertstatus == A_OK) && (newalertstatus != A_OK)) {
+			/* The status went into a failure-mode. Any acks are revived */
+			for (awalk = log->acklist; (awalk); awalk = awalk->next) awalk->cleartime = awalk->validuntil;
+		}
+	}
+
+	if (msg != log->message) { /* They can be the same when called from handle_enadis() or check_purple_status() */
+		char *p;
+
+		/*
+		 * Note here:
+		 * - log->msgsz is the buffer size INCLUDING the final \0.
+		 * - msglen is the message length WITHOUT the final \0.
+		 */
+		if ((log->message == NULL) || (log->msgsz == 0)) {
+			/* No buffer - get one */
+			log->message = (unsigned char *)malloc(msglen+1);
+			memcpy(log->message, msg, msglen+1);
+			log->msgsz = msglen+1;
+		}
+		else if (log->msgsz > msglen) {
+			/* Message - including \0 - fits into the existing buffer. */
+			memcpy(log->message, msg, msglen+1);
+		}
+		else {
+			/* Message does not fit into existing buffer. Grow it. */
+			log->message = (unsigned char *)realloc(log->message, msglen+1);
+			memcpy(log->message, msg, msglen+1);
+			log->msgsz = msglen+1;
+		}
+
+		/* Get at the test flags. They are immediately after the color */
+		p = msg_data(msg);
+		p += strlen(colorname(newcolor));
+
+		if (strncmp(p, " <!-- [flags:", 13) == 0) {
+			char *flagstart = p+13;
+			char *flagend = strchr(flagstart, ']');
+
+			if (flagend) {
+				*flagend = '\0';
+				if (log->testflags == NULL)
+					log->testflags = strdup(flagstart);
+				else if (strlen(log->testflags) >= strlen(flagstart))
+					strcpy(log->testflags, flagstart);
+				else {
+					log->testflags = realloc(log->testflags, strlen(flagstart));
+					strcpy(log->testflags, flagstart);
+				}
+				*flagend = ']';
+			}
+		}
+	}
+
+	/* If in an alert state, we may need to generate a cookie */
+	if (newalertstatus == A_ALERT) {
+		if (log->cookieexpires < now) {
+			int newcookie;
+
+			clear_cookie(log);
+
+			/* Need to ensure that cookies are unique, hence the loop */
+			log->cookie = -1; log->cookieexpires = 0;
+			do {
+				newcookie = (random() % 1000000);
+			} while (find_cookie(newcookie));
+
+			log->cookie = newcookie;
+			rbtInsert(rbcookies, (void *)newcookie, log);
+
+			/*
+			 * This is fundamentally flawed. The cookie should be generated by
+			 * the alert module, because it may not be sent to the user for
+			 * a long time, depending on the alert configuration.
+			 * That's for 4.1 - for now, we'll just give it a long enough 
+			 * lifetime so that cookies will be valid.
+			 */
+			log->cookieexpires = now + 86400; /* Valid for 1 day */
+		}
+	}
+	else {
+		/* Not alert state, so clear any cookies */
+		if (log->cookie >= 0) clear_cookie(log);
+	}
+
+	if (!issummary && (!log->histsynced || (log->oldcolor != newcolor))) {
+		/*
+		 * Change of color goes to the status-change channel.
+		 */
+		dbgprintf("posting to stachg channel: host=%s, test=%s\n", hostname, testname);
+		posttochannel(stachgchn, channelnames[C_STACHG], msg, sender, hostname, log, NULL);
+		log->histsynced = 1;
+
+		/*
+		 * Dont update the log->lastchange timestamp while DOWNTIME is active.
+		 * (It is only seen as active if the color has been forced BLUE).
+		 */
+		if (!log->downtimeactive && (log->oldcolor != newcolor)) {
+			if (log->host->clientmsg && !log->host->clientmsgposted && (newalertstatus == A_ALERT) && log->test->clientsave) {
+				posttochannel(clichgchn, channelnames[C_CLICHG], msg, sender, 
+						hostname, log, NULL);
+			}
+			log->lastchange = time(NULL);
+		}
+	}
+
+	if (!issummary) {
+		if (newalertstatus == A_ALERT) {
+			/* Status is critical, send alerts */
+			dbgprintf("posting alert to page channel\n");
+
+			log->activealert = 1;
+			posttochannel(pagechn, channelnames[C_PAGE], msg, sender, hostname, log, NULL);
+		}
+		else if (log->activealert && (oldalertstatus != A_OK) && (newalertstatus == A_OK)) {
+			/* Status has recovered, send recovery notice */
+			dbgprintf("posting recovery to page channel\n");
+
+			log->activealert = 0;
+			posttochannel(pagechn, channelnames[C_PAGE], msg, sender, hostname, log, NULL);
+		}
+		else if (log->activealert && (log->oldcolor != newcolor)) {
+			/* 
+			 * Status is in-between critical and recovered, but we do have an
+			 * active alert for this status. So tell the pager module that the
+			 * color has changed.
+			 */
+			dbgprintf("posting color change to page channel\n");
+			posttochannel(pagechn, channelnames[C_PAGE], msg, sender, hostname, log, NULL);
+		}
+	}
+
+	dbgprintf("posting to status channel\n");
+	posttochannel(statuschn, channelnames[C_STATUS], msg, sender, hostname, log, NULL);
+
+	dbgprintf("<-handle_status\n");
+	return;
+}
+
+void handle_meta(char *msg, hobbitd_log_t *log)
+{
+	/*
+	 * msg has the format "meta HOST.TEST metaname\nmeta-value\n"
+	 */
+	char *metaname = NULL, *eoln, *line1 = NULL;
+	htnames_t *nwalk;
+	hobbitd_meta_t *mwalk;
+
+	dbgprintf("-> handle_meta\n");
+
+	eoln = strchr(msg, '\n'); 
+	if (eoln) {
+		char *tok;
+
+		*eoln = '\0'; 
+		line1 = strdup(msg);
+		*eoln = '\n';
+
+		tok = strtok(line1, " ");		/* "meta" */
+		if (tok) tok = strtok(NULL, " ");	/* "host.test" */
+		if (tok) tok = strtok(NULL, " ");	/* metaname */
+		if (tok) metaname = tok;
+	}
+	if (!metaname) {
+		if (line1) xfree(line1);
+		errprintf("Malformed 'meta' message: '%s'\n", msg);
+		return;
+	}
+
+	for (nwalk = metanames; (nwalk && strcmp(nwalk->name, metaname)); nwalk = nwalk->next) ;
+	if (nwalk == NULL) {
+		nwalk = (htnames_t *)malloc(sizeof(htnames_t));
+		nwalk->name = strdup(metaname);
+		nwalk->next = metanames;
+		metanames = nwalk;
+	}
+
+	for (mwalk = log->metas; (mwalk && (mwalk->metaname != nwalk)); mwalk = mwalk->next);
+	if (mwalk == NULL) {
+		mwalk = (hobbitd_meta_t *)malloc(sizeof(hobbitd_meta_t));
+		mwalk->metaname = nwalk;
+		mwalk->value = strdup(eoln+1);
+		mwalk->next = log->metas;
+		log->metas = mwalk;
+	}
+	else {
+		if (mwalk->value) xfree(mwalk->value);
+		mwalk->value = strdup(eoln+1);
+	}
+
+	if (line1) xfree(line1);
+
+	dbgprintf("<- handle_meta\n");
+}
+
+void handle_data(char *msg, char *sender, char *origin, char *hostname, char *testname)
+{
+	char *chnbuf;
+	int buflen = 0;
+
+	dbgprintf("->handle_data\n");
+
+	if (origin) buflen += strlen(origin); else dbgprintf("   origin is NULL\n");
+	if (hostname) buflen += strlen(hostname); else dbgprintf("  hostname is NULL\n");
+	if (testname) buflen += strlen(testname); else dbgprintf("  testname is NULL\n");
+	if (msg) buflen += strlen(msg); else dbgprintf("  msg is NULL\n");
+	buflen += 4;
+
+	chnbuf = (char *)malloc(buflen);
+	snprintf(chnbuf, buflen, "%s|%s|%s\n%s", 
+		 (origin ? origin : ""), 
+		 (hostname ? hostname : ""), 
+		 (testname ? testname : ""), 
+		 msg);
+
+	posttochannel(datachn, channelnames[C_DATA], msg, sender, hostname, NULL, chnbuf);
+	xfree(chnbuf);
+	dbgprintf("<-handle_data\n");
+}
+
+void handle_notes(char *msg, char *sender, char *hostname)
+{
+	dbgprintf("->handle_notes\n");
+	posttochannel(noteschn, channelnames[C_NOTES], msg, sender, hostname, NULL, NULL);
+	dbgprintf("<-handle_notes\n");
+}
+
+void handle_usermsg(char *msg, char *sender, char *hostname)
+{
+	dbgprintf("->handle_usermsg\n");
+	posttochannel(userchn, channelnames[C_USER], msg, sender, hostname, NULL, NULL);
+	dbgprintf("<-handle_usermsg\n");
+}
+
+void handle_enadis(int enabled, conn_t *msg, char *sender)
+{
+	char *firstline = NULL, *hosttest = NULL, *durstr = NULL, *txtstart = NULL;
+	char *hname = NULL, *tname = NULL;
+	time_t expires = 0;
+	int alltests = 0;
+	RbtIterator hosthandle, testhandle;
+	hobbitd_hostlist_t *hwalk = NULL;
+	testinfo_t *twalk = NULL;
+	hobbitd_log_t *log;
+	char *p;
+	char hostip[IP_ADDR_STRLEN];
+
+	dbgprintf("->handle_enadis\n");
+
+	MEMDEFINE(hostip);
+
+	p = strchr(msg->buf, '\n'); if (p) *p = '\0';
+	firstline = strdup(msg->buf);
+	if (p) *p = '\n';
+
+	p = strtok(firstline, " \t");
+	if (p) hosttest = strtok(NULL, " \t");
+	if (hosttest) durstr = strtok(NULL, " \t");
+	if (!hosttest) {
+		errprintf("Invalid enable/disable from %s - no host/test specified\n", sender);
+		goto done;
+	}
+
+	if (!enabled) {
+		if (durstr) {
+			if (strcmp(durstr, "-1") == 0) {
+				expires = DISABLED_UNTIL_OK;
+			}
+			else {
+				expires = 60*durationvalue(durstr) + time(NULL);
+			}
+
+			txtstart = msg->buf + (durstr + strlen(durstr) - firstline);
+			txtstart += strspn(txtstart, " \t\r\n");
+			if (*txtstart == '\0') txtstart = "(No reason given)";
+		}
+		else {
+			errprintf("Invalid disable from %s - no duration specified\n", sender);
+			goto done;
+		}
+	}
+
+	p = hosttest + strlen(hosttest) - 1;
+	if (*p == '*') {
+		/* It ends with a '*' so assume this is for all tests */
+		alltests = 1;
+		*p = '\0';
+		p--;
+		if (*p == '.') *p = '\0';
+	}
+	else {
+		/* No wildcard -> get the test name */
+		p = strrchr(hosttest, '.');
+		if (p == NULL) goto done; /* "enable foo" ... surely you must be joking. */
+		*p = '\0';
+		tname = (p+1);
+	}
+	p = hosttest; while ((p = strchr(p, ',')) != NULL) *p = '.';
+	hname = knownhost(hosttest, hostip, ghosthandling);
+	if (hname == NULL) goto done;
+
+	hosthandle = rbtFind(rbhosts, hname);
+	if (hosthandle == rbtEnd(rbhosts)) {
+		/* Unknown host */
+		goto done;
+	}
+	else hwalk = gettreeitem(rbhosts, hosthandle);
+
+	if (!oksender(maintsenders, hwalk->ip, msg->addr.sin_addr, msg->buf)) goto done;
+
+	if (tname) {
+		testhandle = rbtFind(rbtests, tname);
+		if (testhandle == rbtEnd(rbtests)) {
+			/* Unknown test */
+			goto done;
+		}
+		else twalk = gettreeitem(rbtests, testhandle);
+	}
+
+	if (enabled) {
+		/* Enable is easy - just clear the enabletime */
+		if (alltests) {
+			for (log = hwalk->logs; (log); log = log->next) {
+				log->enabletime = 0;
+				if (log->dismsg) {
+					xfree(log->dismsg);
+					log->dismsg = NULL;
+				}
+				posttochannel(enadischn, channelnames[C_ENADIS], msg->buf, sender, log->host->hostname, log, NULL);
+			}
+		}
+		else {
+			for (log = hwalk->logs; (log && (log->test != twalk)); log = log->next) ;
+			if (log) {
+				log->enabletime = 0;
+				if (log->dismsg) {
+					xfree(log->dismsg);
+					log->dismsg = NULL;
+				}
+				posttochannel(enadischn, channelnames[C_ENADIS], msg->buf, sender, log->host->hostname, log, NULL);
+			}
+		}
+	}
+	else {
+		/* disable code goes here */
+
+		if (alltests) {
+			for (log = hwalk->logs; (log); log = log->next) {
+				log->enabletime = expires;
+				log->validtime = (expires == DISABLED_UNTIL_OK) ? INT_MAX : log->validtime;
+				if (txtstart) {
+					if (log->dismsg) xfree(log->dismsg);
+					log->dismsg = strdup(txtstart);
+				}
+				posttochannel(enadischn, channelnames[C_ENADIS], msg->buf, sender, log->host->hostname, log, NULL);
+				/* Trigger an immediate status update */
+				handle_status(log->message, sender, log->host->hostname, log->test->name, log->grouplist, log, COL_BLUE, NULL);
+			}
+		}
+		else {
+			for (log = hwalk->logs; (log && (log->test != twalk)); log = log->next) ;
+			if (log) {
+				log->enabletime = expires;
+				log->validtime = (expires == DISABLED_UNTIL_OK) ? INT_MAX : log->validtime;
+				if (txtstart) {
+					if (log->dismsg) xfree(log->dismsg);
+					log->dismsg = strdup(txtstart);
+				}
+				posttochannel(enadischn, channelnames[C_ENADIS], msg->buf, sender, log->host->hostname, log, NULL);
+
+				/* Trigger an immediate status update */
+				handle_status(log->message, sender, log->host->hostname, log->test->name, log->grouplist, log, COL_BLUE, NULL);
+			}
+		}
+
+	}
+
+done:
+	MEMUNDEFINE(hostip);
+	xfree(firstline);
+
+	dbgprintf("<-handle_enadis\n");
+
+	return;
+}
+
+
+void handle_ack(char *msg, char *sender, hobbitd_log_t *log, int duration)
+{
+	char *p;
+
+	dbgprintf("->handle_ack\n");
+
+	log->acktime = time(NULL)+duration*60;
+	if (log->validtime < log->acktime) log->validtime = log->acktime;
+
+	p = msg;
+	p += strspn(p, " \t");			/* Skip the space ... */
+	p += strspn(p, "-0123456789");		/* and the cookie ... */
+	p += strspn(p, " \t");			/* and the space ... */
+	p += strspn(p, "0123456789hdwmy");	/* and the duration ... */
+	p += strspn(p, " \t");			/* and the space ... */
+	log->ackmsg = strdup(p);
+
+	/* Tell the pagers */
+	posttochannel(pagechn, "ack", log->ackmsg, sender, log->host->hostname, log, NULL);
+
+	dbgprintf("<-handle_ack\n");
+	return;
+}
+
+void handle_ackinfo(char *msg, char *sender, hobbitd_log_t *log)
+{
+	int level = -1;
+	time_t validuntil = -1, itemval;
+	time_t received = getcurrenttime(NULL);
+	char *ackedby = NULL, *ackmsg = NULL;
+	char *tok, *item;
+	int itemno = 0;
+
+	tok = msg;
+	while (tok) {
+		tok += strspn(tok, " \t\n");
+		item = tok; itemno++;
+		tok = strchr(tok, '\n'); if (tok) { *tok = '\0'; tok++; }
+
+		switch (itemno) {
+		  case 1: break; /* First line has just the HOST.TEST */
+		  case 2: level = atoi(item); break;
+		  case 3: itemval = atoi(item); 
+			  if (itemval == -1) itemval = 365*24*60*60; /* 1 year */
+			  validuntil = received + itemval; 
+			  break;
+		  case 4: ackedby = strdup(item); break;
+		  case 5: ackmsg = strdup(item); break;
+		}
+	}
+
+	if ((level >= 0) && (validuntil > received) && ackedby && ackmsg) {
+		ackinfo_t *newack;
+		int isnew;
+
+		dbgprintf("Got ackinfo: Level=%d,until=%d,ackby=%s,msg=%s\n", level, validuntil, ackedby, ackmsg);
+
+		/* See if we already have this ack in the list */
+		for (newack = log->acklist; (newack && ((level != newack->level) || strcmp(newack->ackedby, ackedby))); newack = newack->next);
+
+		isnew = (newack == NULL);
+		dbgprintf("This ackinfo is %s\n", (isnew ? "new" : "old"));
+		if (isnew) {
+			dbgprintf("Creating new ackinfo record\n");
+			newack = (ackinfo_t *)malloc(sizeof(ackinfo_t));
+		}
+		else {
+			/* Drop the old data so we dont leak memory */
+			dbgprintf("Dropping old ackinfo data: From %s, msg=%s\n", newack->ackedby, newack->msg);
+			if (newack->ackedby) xfree(newack->ackedby); 
+			if (newack->msg) xfree(newack->msg);
+		}
+
+		newack->level = level;
+		newack->received = received;
+		newack->validuntil = newack->cleartime = validuntil;
+		newack->ackedby = ackedby;
+		newack->msg = ackmsg;
+
+		if (isnew) {
+			newack->next = log->acklist;
+			log->acklist = newack;
+		}
+
+		if (ackinfologfd) {
+			char timestamp[25];
+
+			strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&received));
+			fprintf(ackinfologfd, "%s %s %s %s %d %d %d %d %s\n",
+				timestamp, log->host->hostname, log->test->name,
+				newack->ackedby, newack->level, 
+				(int)log->lastchange, (int)newack->received, (int)newack->validuntil, 
+				nlencode(newack->msg));
+			fflush(ackinfologfd);
+		}
+	}
+	else {
+		if (ackedby) xfree(ackedby);
+		if (ackmsg) xfree(ackmsg);
+	}
+}
+
+void handle_notify(char *msg, char *sender, char *hostname, char *testname)
+{
+	char *msgtext, *channelmsg;
+	namelist_t *hi;
+
+	dbgprintf("-> handle_notify\n");
+
+	hi = hostinfo(hostname);
+
+	msgtext = msg_data(msg);
+	channelmsg = (char *)malloc(1024 + strlen(msgtext));
+
+	/* Tell the pagers */
+	sprintf(channelmsg, "%s|%s|%s\n%s", 
+		hostname, (testname ? testname : ""), (hi ? hi->page->pagepath : ""), msgtext);
+	posttochannel(pagechn, "notify", msg, sender, hostname, NULL, channelmsg);
+
+	xfree(channelmsg);
+
+	dbgprintf("<- handle_notify\n");
+	return;
+}
+
+void handle_client(char *msg, char *sender, char *hostname, char *clientos, char *clientclass)
+{
+	char *chnbuf, *theclass;
+	int msglen, buflen = 0;
+	RbtIterator hosthandle;
+
+	dbgprintf("->handle_client\n");
+
+	/* Default class is the OS */
+	theclass = (clientclass ? clientclass : clientos);
+	buflen += strlen(hostname) + strlen(clientos) + strlen(theclass);
+	if (msg) { msglen = strlen(msg); buflen += msglen; } else { dbgprintf("  msg is NULL\n"); return; }
+	buflen += 5;
+
+	if (clientsavemem) {
+		hosthandle = rbtFind(rbhosts, hostname);
+		if (hosthandle != rbtEnd(rbhosts)) {
+			hobbitd_hostlist_t *hwalk;
+			hwalk = gettreeitem(rbhosts, hosthandle);
+
+			if (hwalk->clientmsg) {
+				if (strlen(hwalk->clientmsg) >= msglen)
+					strcpy(hwalk->clientmsg, msg);
+				else {
+					xfree(hwalk->clientmsg);
+					hwalk->clientmsg = strdup(msg);
+				}
+			}
+			else {
+				hwalk->clientmsg = strdup(msg);
+			}
+			hwalk->clientmsgtstamp = time(NULL);
+			hwalk->clientmsgposted = 0;
+		}
+	}
+
+	chnbuf = (char *)malloc(buflen);
+	snprintf(chnbuf, buflen, "%s|%s|%s\n%s", hostname, clientos, theclass, msg);
+	posttochannel(clientchn, channelnames[C_CLIENT], msg, sender, hostname, NULL, chnbuf);
+	xfree(chnbuf);
+	dbgprintf("<-handle_client\n");
+}
+
+
+
+void flush_acklist(hobbitd_log_t *zombie, int flushall)
+{
+	ackinfo_t *awalk, *newhead = NULL, *newtail = NULL;
+	time_t now = getcurrenttime(NULL);
+
+	awalk = zombie->acklist;
+	while (awalk) {
+		ackinfo_t *tmp = awalk;
+		awalk = awalk->next;
+
+		if (flushall || (tmp->cleartime < now) || (tmp->validuntil < now)) {
+			if (tmp->ackedby) xfree(tmp->ackedby);
+			if (tmp->msg) xfree(tmp->msg);
+			xfree(tmp);
+		}
+		else {
+			/* We have a record we want to keep */
+			if (newhead == NULL) {
+				newhead = newtail = tmp;
+			}
+			else {
+				newtail->next = tmp;
+				newtail = tmp;
+			}
+		}
+	}
+
+	if (newtail) newtail->next = NULL;
+	zombie->acklist = newhead;
+}
+
+char *acklist_string(hobbitd_log_t *log, int level)
+{
+	static strbuffer_t *res = NULL;
+	ackinfo_t *awalk;
+	char tmpstr[512];
+
+	if (log->acklist == NULL) return NULL;
+
+	if (res) clearstrbuffer(res); else res = newstrbuffer(0);
+
+	for (awalk = log->acklist; (awalk); awalk = awalk->next) {
+		if ((level != -1) && (awalk->level != level)) continue;
+		snprintf(tmpstr, sizeof(tmpstr), "%d:%d:%d:%s:%s\n", 
+			 (int)awalk->received, (int)awalk->validuntil, 
+			 awalk->level, awalk->ackedby, awalk->msg);
+		tmpstr[sizeof(tmpstr)-1] = '\0';
+		addtobuffer(res, tmpstr);
+	}
+
+	return STRBUF(res);
+}
+
+void free_log_t(hobbitd_log_t *zombie)
+{
+	hobbitd_meta_t *mwalk, *mtmp;
+
+	dbgprintf("-> free_log_t\n");
+	mwalk = zombie->metas;
+	while (mwalk) {
+		mtmp = mwalk;
+		mwalk = mwalk->next;
+
+		if (mtmp->value) xfree(mtmp->value);
+		xfree(mtmp);
+	}
+
+	if (zombie->message) xfree(zombie->message);
+	if (zombie->dismsg) xfree(zombie->dismsg);
+	if (zombie->ackmsg) xfree(zombie->ackmsg);
+	if (zombie->grouplist) xfree(zombie->grouplist);
+	flush_acklist(zombie, 1);
+	xfree(zombie);
+	dbgprintf("<- free_log_t\n");
+}
+
+void handle_dropnrename(enum droprencmd_t cmd, char *sender, char *hostname, char *n1, char *n2)
+{
+	char hostip[IP_ADDR_STRLEN];
+	RbtIterator hosthandle, testhandle;
+	hobbitd_hostlist_t *hwalk;
+	testinfo_t *twalk, *newt;
+	hobbitd_log_t *lwalk;
+	char *marker = NULL;
+	char *canonhostname;
+
+	dbgprintf("-> handle_dropnrename\n");
+	MEMDEFINE(hostip);
+
+	{
+		/*
+		 * We pass drop- and rename-messages to the workers, whether 
+		 * we know about this host or not. It could be that the drop command
+		 * arrived after we had already re-loaded the bb-hosts file, and 
+		 * so the host is no longer known by us - but there is still some
+		 * data stored about it that needs to be cleaned up.
+		 */
+
+		char *msgbuf = (char *)malloc(20 + strlen(hostname) + (n1 ? strlen(n1) : 0) + (n2 ? strlen(n2) : 0));
+
+		*msgbuf = '\0';
+		switch (cmd) {
+		  case CMD_DROPTEST:
+			marker = "droptest";
+			sprintf(msgbuf, "%s|%s", hostname, n1);
+			break;
+		  case CMD_DROPHOST:
+			marker = "drophost";
+			sprintf(msgbuf, "%s", hostname);
+			break;
+		  case CMD_RENAMEHOST:
+			marker = "renamehost";
+			sprintf(msgbuf, "%s|%s", hostname, n1);
+			break;
+		  case CMD_RENAMETEST:
+			marker = "renametest";
+			sprintf(msgbuf, "%s|%s|%s", hostname, n1, n2);
+			break;
+		  case CMD_DROPSTATE:
+			marker = "dropstate";
+			sprintf(msgbuf, "%s", hostname);
+			break;
+		}
+
+		if (strlen(msgbuf)) {
+			/* Tell the workers */
+			posttochannel(statuschn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(stachgchn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(pagechn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(datachn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(noteschn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(enadischn, marker, NULL, sender, NULL, NULL, msgbuf);
+			posttochannel(clientchn, marker, NULL, sender, NULL, NULL, msgbuf);
+		}
+
+		xfree(msgbuf);
+	}
+
+
+	/*
+	 * Now clean up our internal state info, if there is any.
+	 * NB: knownhost() may return NULL, if the bb-hosts file was re-loaded before
+	 * we got around to cleaning up a host.
+	 */
+	canonhostname = knownhost(hostname, hostip, ghosthandling);
+	if (canonhostname) hostname = canonhostname;
+
+	hosthandle = rbtFind(rbhosts, hostname);
+	if (hosthandle == rbtEnd(rbhosts)) goto done;
+	else hwalk = gettreeitem(rbhosts, hosthandle);
+
+	switch (cmd) {
+	  case CMD_DROPTEST:
+		testhandle = rbtFind(rbtests, n1);
+		if (testhandle == rbtEnd(rbtests)) goto done;
+		twalk = gettreeitem(rbtests, testhandle);
+
+		for (lwalk = hwalk->logs; (lwalk && (lwalk->test != twalk)); lwalk = lwalk->next) ;
+		if (lwalk == NULL) goto done;
+		if (lwalk == hwalk->pinglog) hwalk->pinglog = NULL;
+		if (lwalk == hwalk->logs) {
+			hwalk->logs = hwalk->logs->next;
+		}
+		else {
+			hobbitd_log_t *plog;
+			for (plog = hwalk->logs; (plog->next != lwalk); plog = plog->next) ;
+			plog->next = lwalk->next;
+		}
+		free_log_t(lwalk);
+		break;
+
+	  case CMD_DROPHOST:
+	  case CMD_DROPSTATE:
+		/* Unlink the hostlist entry */
+		rbtErase(rbhosts, hosthandle);
+		hostcount--;
+
+		/* Loop through the host logs and free them */
+		lwalk = hwalk->logs;
+		while (lwalk) {
+			hobbitd_log_t *tmp = lwalk;
+			lwalk = lwalk->next;
+
+			free_log_t(tmp);
+		}
+
+		/* Free the hostlist entry */
+		xfree(hwalk->hostname);
+		if (hwalk->clientmsg) xfree(hwalk->clientmsg);
+		xfree(hwalk);
+		break;
+
+	  case CMD_RENAMEHOST:
+		rbtErase(rbhosts, hosthandle);
+		if (strlen(hwalk->hostname) <= strlen(n1)) {
+			strcpy(hwalk->hostname, n1);
+		}
+		else {
+			xfree(hwalk->hostname);
+			hwalk->hostname = strdup(n1);
+		}
+		rbtInsert(rbhosts, hwalk->hostname, hwalk);
+		break;
+
+	  case CMD_RENAMETEST:
+		testhandle = rbtFind(rbtests, n1);
+		if (testhandle == rbtEnd(rbtests)) goto done;
+		twalk = gettreeitem(rbtests, testhandle);
+
+		for (lwalk = hwalk->logs; (lwalk && (lwalk->test != twalk)); lwalk = lwalk->next) ;
+		if (lwalk == NULL) goto done;
+
+		if (lwalk == hwalk->pinglog) hwalk->pinglog = NULL;
+
+		testhandle = rbtFind(rbtests, n2);
+		if (testhandle == rbtEnd(rbtests)) {
+			newt = create_testinfo(n2);
+		}
+		else {
+			newt = gettreeitem(rbtests, testhandle);
+		}
+		lwalk->test = newt;
+		break;
+	}
+
+done:
+	MEMUNDEFINE(hostip);
+
+	dbgprintf("<- handle_dropnrename\n");
+
+	return;
+}
+
+
+unsigned char *get_filecache(char *fn)
+{
+	RbtIterator handle;
+	filecache_t *item;
+	unsigned char *result;
+
+	handle = rbtFind(rbfilecache, fn);
+	if (handle == rbtEnd(rbfilecache)) return NULL;
+
+	item = (filecache_t *)gettreeitem(rbfilecache, handle);
+	if (item->len < 0) return NULL;
+
+	result = (unsigned char *)malloc(item->len);
+	memcpy(result, item->fdata, item->len);
+
+	return result;
+}
+
+
+void add_filecache(char *fn, unsigned char *buf, off_t buflen)
+{
+	RbtIterator handle;
+	filecache_t *newitem;
+
+	handle = rbtFind(rbfilecache, fn);
+	if (handle == rbtEnd(rbfilecache)) {
+		newitem = (filecache_t *)malloc(sizeof(filecache_t));
+		newitem->fn = strdup(fn);
+		newitem->len = buflen;
+		newitem->fdata = (unsigned char *)malloc(buflen);
+		memcpy(newitem->fdata, buf, buflen);
+		rbtInsert(rbfilecache, newitem->fn, newitem);
+	}
+	else {
+		newitem = (filecache_t *)gettreeitem(rbfilecache, handle);
+		if (newitem->fdata) xfree(newitem->fdata);
+		newitem->len = buflen;
+		newitem->fdata = (unsigned char *)malloc(buflen);
+		memcpy(newitem->fdata, buf, buflen);
+	}
+}
+
+
+void flush_filecache(void)
+{
+	RbtIterator handle;
+
+	for (handle = rbtBegin(rbfilecache); (handle != rbtEnd(rbfilecache)); handle = rbtNext(rbfilecache, handle)) {
+		filecache_t *item = (filecache_t *)gettreeitem(rbfilecache, handle);
+		if (item->fdata) xfree(item->fdata);
+		item->len = -1;
+	}
+}
+
+
+int get_config(char *fn, conn_t *msg)
+{
+	char fullfn[PATH_MAX];
+	FILE *fd = NULL;
+	strbuffer_t *inbuf, *result;
+
+	dbgprintf("-> get_config %s\n", fn);
+	sprintf(fullfn, "%s/etc/%s", xgetenv("BBHOME"), fn);
+	fd = stackfopen(fullfn, "r", NULL);
+	if (fd == NULL) {
+		errprintf("Config file %s not found\n", fn);
+		return -1;
+	}
+
+	inbuf = newstrbuffer(0);
+	result = newstrbuffer(0);
+	while (stackfgets(inbuf, NULL) != NULL) addtostrbuffer(result, inbuf);
+	stackfclose(fd);
+	freestrbuffer(inbuf);
+
+	msg->buflen = STRBUFLEN(result);
+	msg->buf = grabstrbuffer(result);
+	msg->bufp = msg->buf + msg->buflen;
+
+	dbgprintf("<- get_config\n");
+
+	return 0;
+}
+
+int get_binary(char *fn, conn_t *msg)
+{
+	char fullfn[PATH_MAX];
+	int fd;
+	struct stat st;
+	unsigned char *result;
+
+	dbgprintf("-> get_binary %s\n", fn);
+	sprintf(fullfn, "%s/download/%s", xgetenv("BBHOME"), fn);
+
+	result = get_filecache(fullfn);
+	if (!result) {
+		fd = open(fullfn, O_RDONLY);
+		if (fd == -1) {
+			errprintf("Download file %s not found\n", fn);
+			return -1;
+		}
+
+		if (fstat(fd, &st) == 0) {
+			ssize_t n;
+
+			result = (unsigned char *)malloc(st.st_size);
+			n = read(fd, result, st.st_size);
+			if (n != st.st_size) {
+				errprintf("Error reading from %s : %s\n", fn, strerror(errno));
+				xfree(result); result = NULL;
+				close(fd);
+				return -1;
+			}
+		}
+
+		add_filecache(fullfn, result, st.st_size);
+	}
+
+	msg->buflen = st.st_size;
+	msg->buf = result;
+	msg->bufp = msg->buf + msg->buflen;
+
+	dbgprintf("<- get_binary\n");
+
+	return 0;
+}
+
+char *timestr(time_t tstamp)
+{
+	static char result[30];
+	char *p;
+
+	MEMDEFINE(result);
+
+	if (tstamp == 0) {
+		MEMUNDEFINE(result);
+		return "N/A";
+	}
+
+	strcpy(result, ctime(&tstamp));
+	p = strchr(result, '\n'); if (p) *p = '\0';
+
+	MEMUNDEFINE(result);
+	return result;
+}
+
+void setup_filter(char *buf, char *defaultfields, 
+		  pcre **spage, pcre **shost, pcre **snet, 
+		  pcre **stest, int *scolor, int *acklevel, char **fields,
+		  char **chspage, char **chshost, char **chsnet, char **chstest)
+{
+	char *tok, *s;
+	int idx = 0;
+
+	dbgprintf("-> setup_filter: %s\n", buf);
+
+	*spage = *shost = *snet = *stest = NULL;
+	if (chspage) *chspage = NULL;
+	if (chshost) *chshost = NULL;
+	if (chsnet)  *chsnet  = NULL;
+	if (chstest) *chstest = NULL;
+	*fields = NULL;
+	*scolor = -1;
+
+	tok = strtok(buf, " \t\r\n");
+	if (tok) tok = strtok(NULL, " \t\r\n");
+	while (tok) {
+		/* Get filter */
+		if (strncmp(tok, "page=", 5) == 0) {
+			if (*(tok+5)) {
+				*spage = compileregex(tok+5);
+				if (chspage) *chspage = tok+5;
+			}
+		}
+		else if (strncmp(tok, "host=", 5) == 0) {
+			if (*(tok+5)) {
+				*shost = compileregex(tok+5);
+				if (chshost) *chshost = tok+5;
+			}
+		}
+		else if (strncmp(tok, "net=", 4) == 0) {
+			if (*(tok+4)) {
+				*snet = compileregex(tok+4);
+				if (chsnet) *chsnet = tok+4;
+			}
+		}
+		else if (strncmp(tok, "test=", 5) == 0) {
+			if (*(tok+5)) {
+				*stest = compileregex(tok+5);
+				if (chstest) *chstest = tok+5;
+			}
+		}
+		else if (strncmp(tok, "fields=", 7) == 0) *fields = tok+7;
+		else if (strncmp(tok, "color=", 6) == 0) *scolor = colorset(tok+6, 0);
+		else if (strncmp(tok, "acklevel=", 9) == 0) *acklevel = atoi(tok+9);
+		else {
+			/* Might be an old-style HOST.TEST request */
+			char *hname, *tname, hostip[IP_ADDR_STRLEN];
+			char *hnameexp, *tnameexp;
+
+			MEMDEFINE(hostip);
+
+			hname = tok;
+			tname = strrchr(tok, '.');
+			if (tname) { *tname = '\0'; tname++; }
+			s = hname; while ((s = strchr(s, ',')) != NULL) *s = '.';
+			hname = knownhost(hname, hostip, ghosthandling);
+
+			if (hname && tname) {
+				hnameexp = (char *)malloc(strlen(hname)+3);
+				sprintf(hnameexp, "^%s$", hname);
+				*shost = compileregex(hnameexp);
+				xfree(hnameexp);
+
+				tnameexp = (char *)malloc(strlen(tname)+3);
+				sprintf(tnameexp, "^%s$", tname);
+				*stest = compileregex(tnameexp);
+				xfree(tnameexp);
+
+				if (chshost) *chshost = hname;
+				if (chstest) *chstest = tname;
+			}
+		}
+
+		tok = strtok(NULL, " \t\r\n");
+	}
+
+	/* If no fields given, provide the default set. */
+	if (*fields == NULL) *fields = defaultfields;
+
+	s = strdup(*fields);
+	tok = strtok(s, ",");
+	while (tok) {
+		enum boardfield_t fieldid = F_LAST;
+		enum bbh_item_t bbhfieldid = BBH_LAST;
+		int validfield = 1;
+
+		if (strncmp(tok, "BBH_", 4) == 0) {
+			fieldid = F_HOSTINFO;
+			bbhfieldid = bbh_key_idx(tok);
+			validfield = (bbhfieldid != BBH_LAST);
+		}
+		else {
+			int i;
+			for (i=0; (boardfieldnames[i].name && strcmp(tok, boardfieldnames[i].name)); i++) ;
+			if (boardfieldnames[i].name) {
+				fieldid = boardfieldnames[i].id;
+				bbhfieldid = BBH_LAST;
+			}
+		}
+
+		if ((fieldid != F_LAST) && (idx < BOARDFIELDS_MAX) && validfield) {
+			boardfields[idx].field = fieldid;
+			boardfields[idx].bbhfield = bbhfieldid;
+			idx++;
+		}
+
+		tok = strtok(NULL, ",");
+	}
+	boardfields[idx].field = F_NONE;
+	boardfields[idx].bbhfield = BBH_LAST;
+
+	xfree(s);
+
+	dbgprintf("<- setup_filter: %s\n", buf);
+}
+
+int match_host_filter(namelist_t *hinfo, pcre *spage, pcre *shost, pcre *snet)
+{
+	char *match;
+
+	match = bbh_item(hinfo, BBH_HOSTNAME);
+	if (shost && match && !matchregex(match, shost)) return 0;
+	match = bbh_item(hinfo, BBH_PAGEPATH);
+	if (spage && match && !matchregex(match, spage)) return 0;
+	match = bbh_item(hinfo, BBH_NET);
+	if (snet  && match && !matchregex(match, snet))  return 0;
+
+	return 1;
+}
+
+int match_test_filter(hobbitd_log_t *log, pcre *stest, int scolor)
+{
+	/* Testname filter */
+	if (stest && !matchregex(log->test->name, stest)) return 0;
+
+	/* Color filter */
+	if ((scolor != -1) && (((1 << log->color) & scolor) == 0)) return 0;
+
+	return 1;
+}
+
+
+
+void generate_outbuf(char **outbuf, char **outpos, int *outsz, 
+		     hobbitd_hostlist_t *hwalk, hobbitd_log_t *lwalk, int acklevel)
+{
+	int f_idx;
+	char *buf, *bufp;
+	int bufsz;
+	char *eoln;
+	namelist_t *hinfo = NULL;
+	char *acklist = NULL;
+	int needed, used;
+	enum boardfield_t f_type;
+
+	buf = *outbuf;
+	bufp = *outpos;
+	bufsz = *outsz;
+	needed = 1024;
+
+	/* First calculate how much buffer space is needed */
+	for (f_idx = 0, f_type = boardfields[0].field; ((f_type != F_NONE) && (f_type != F_LAST)); f_type = boardfields[++f_idx].field) {
+		if ((lwalk == NULL) && (f_type != F_HOSTINFO)) continue;
+
+		switch (f_type) {
+		  case F_ACKMSG: if (lwalk->ackmsg) needed += 2*strlen(lwalk->ackmsg); break;
+		  case F_DISMSG: if (lwalk->dismsg) needed += 2*strlen(lwalk->dismsg); break;
+		  case F_MSG: needed += 2*strlen(lwalk->message); break;
+
+		  case F_ACKLIST:
+			flush_acklist(lwalk, 0);
+			acklist = acklist_string(lwalk, acklevel);
+			if (acklist) needed += 2*strlen(acklist);
+			break;
+
+		  case F_HOSTINFO:
+			if (!hinfo) hinfo = hostinfo(hwalk->hostname);
+			if (hinfo) {
+				char *infostr = bbh_item(hinfo, boardfields[f_idx].bbhfield);
+				if (infostr) needed += strlen(infostr);
+			}
+			break;
+
+		  default: break;
+		}
+	}
+
+	/* Make sure the buffer is large enough */
+	used = (bufp - buf);
+	if ((bufsz - used) < needed) {
+		bufsz += needed;
+		buf = (char *)realloc(buf, bufsz);
+		bufp = buf + used;
+	}
+
+	/* Now generate the data */
+	for (f_idx = 0, f_type = boardfields[0].field; ((f_type != F_NONE) && (f_type != F_LAST)); f_type = boardfields[++f_idx].field) {
+		if ((lwalk == NULL) && (f_type != F_HOSTINFO)) continue;
+		if (f_idx > 0) bufp += sprintf(bufp, "|");
+
+		switch (f_type) {
+		  case F_NONE: break;
+		  case F_HOSTNAME: bufp += sprintf(bufp, "%s", hwalk->hostname); break;
+		  case F_TESTNAME: bufp += sprintf(bufp, "%s", lwalk->test->name); break;
+		  case F_COLOR: bufp += sprintf(bufp, "%s", colnames[lwalk->color]); break;
+		  case F_FLAGS: bufp += sprintf(bufp, "%s", (lwalk->testflags ? lwalk->testflags : "")); break;
+		  case F_LASTCHANGE: bufp += sprintf(bufp, "%d", (int)lwalk->lastchange); break;
+		  case F_LOGTIME: bufp += sprintf(bufp, "%d", (int)lwalk->logtime); break;
+		  case F_VALIDTIME: bufp += sprintf(bufp, "%d", (int)lwalk->validtime); break;
+		  case F_ACKTIME: bufp += sprintf(bufp, "%d", (int)lwalk->acktime); break;
+		  case F_DISABLETIME: bufp += sprintf(bufp, "%d", (int)lwalk->enabletime); break;
+		  case F_SENDER: bufp += sprintf(bufp, "%s", lwalk->sender); break;
+		  case F_COOKIE: bufp += sprintf(bufp, "%d", lwalk->cookie); break;
+
+		  case F_LINE1:
+			eoln = strchr(lwalk->message, '\n'); if (eoln) *eoln = '\0';
+			bufp += sprintf(bufp, "%s", msg_data(lwalk->message));
+			if (eoln) *eoln = '\n';
+			break;
+
+		  case F_ACKMSG: if (lwalk->ackmsg) bufp += sprintf(bufp, "%s", nlencode(lwalk->ackmsg)); break;
+		  case F_DISMSG: if (lwalk->dismsg) bufp += sprintf(bufp, "%s", nlencode(lwalk->dismsg)); break;
+		  case F_MSG: bufp += sprintf(bufp, "%s", nlencode(lwalk->message)); break;
+		  case F_CLIENT: bufp += sprintf(bufp, "%s", (hwalk->clientmsg ? "Y" : "N")); break;
+		  case F_CLIENTTSTAMP: bufp += sprintf(bufp, "%ld", (hwalk->clientmsg ? (long) hwalk->clientmsgtstamp : 0)); break;
+		  case F_ACKLIST: if (acklist) bufp += sprintf(bufp, "%s", nlencode(acklist)); break;
+
+		  case F_HOSTINFO:
+			if (hinfo) {	/* hinfo has been set above while scanning for the needed bufsize */
+				char *infostr = bbh_item(hinfo, boardfields[f_idx].bbhfield);
+				if (infostr) bufp += sprintf(bufp, "%s", infostr);
+			}
+			break;
+
+		  case F_LAST: break;
+		}
+	}
+	bufp += sprintf(bufp, "\n");
+
+	*outbuf = buf;
+	*outpos = bufp;
+	*outsz = bufsz;
+}
+
+
+void generate_hostinfo_outbuf(char **outbuf, char **outpos, int *outsz, namelist_t *hinfo)
+{
+	int f_idx;
+	char *buf, *bufp;
+	int bufsz;
+	char *infostr = NULL;
+
+	buf = *outbuf;
+	bufp = *outpos;
+	bufsz = *outsz;
+
+	for (f_idx = 0; (boardfields[f_idx].field != F_NONE); f_idx++) {
+		int needed = 1024;
+		int used = (bufp - buf);
+
+		switch (boardfields[f_idx].field) {
+		  case F_HOSTINFO:
+			infostr = bbh_item(hinfo, boardfields[f_idx].bbhfield);
+			if (infostr) needed += strlen(infostr);
+			break;
+
+		  default: break;
+		}
+
+		if ((bufsz - used) < needed) {
+			bufsz += 4096 + needed;
+			buf = (char *)realloc(buf, bufsz);
+			bufp = buf + used;
+		}
+
+		if (f_idx > 0) bufp += sprintf(bufp, "|");
+
+		switch (boardfields[f_idx].field) {
+		  case F_HOSTINFO: if (infostr) bufp += sprintf(bufp, "%s", infostr); break;
+		  default: break;
+		}
+	}
+
+	bufp += sprintf(bufp, "\n");
+
+	*outbuf = buf;
+	*outpos = bufp;
+	*outsz = bufsz;
+}
+
+
+void do_message(conn_t *msg, char *origin)
+{
+	static int nesting = 0;
+	hobbitd_hostlist_t *h;
+	testinfo_t *t;
+	hobbitd_log_t *log;
+	int color;
+	char *downcause;
+	char sender[IP_ADDR_STRLEN];
+	char *grouplist;
+	time_t now;
+	char *msgfrom;
+
+	nesting++;
+	if (debug) {
+		char *eoln = strchr(msg->buf, '\n');
+
+		if (eoln) *eoln = '\0';
+		dbgprintf("-> do_message/%d (%d bytes): %s\n", nesting, msg->buflen, msg->buf);
+		if (eoln) *eoln = '\n';
+	}
+
+	MEMDEFINE(sender);
+
+	/* Most likely, we will not send a response */
+	msg->doingwhat = NOTALK;
+	strncpy(sender, inet_ntoa(msg->addr.sin_addr), sizeof(sender));
+	now = time(NULL);
+
+	if (traceall || tracelist) {
+		int found = 0;
+
+		if (traceall) {
+			found = 1;
+		}
+		else {
+			int i = 0;
+			do {
+				if ((tracelist[i].ipval & tracelist[i].ipmask) == (ntohl(msg->addr.sin_addr.s_addr) & tracelist[i].ipmask)) {
+					found = 1;
+				}
+				i++;
+			} while (!found && (tracelist[i].ipval != 0));
+		}
+
+		if (found) {
+			char tracefn[PATH_MAX];
+			struct timeval tv;
+			struct timezone tz;
+			FILE *fd;
+
+			gettimeofday(&tv, &tz);
+
+			sprintf(tracefn, "%s/%d_%06d_%s.trace", xgetenv("BBTMP"), 
+				(int) tv.tv_sec, (int) tv.tv_usec, sender);
+			fd = fopen(tracefn, "w");
+			if (fd) {
+				fwrite(msg->buf, msg->buflen, 1, fd);
+				fclose(fd);
+			}
+
+			if (ignoretraced) goto done;
+		}
+	}
+
+	/* Count statistics */
+	update_statistics(msg->buf);
+
+	if (strncmp(msg->buf, "combo\n", 6) == 0) {
+		char *currmsg, *nextmsg;
+
+		currmsg = msg->buf+6;
+		do {
+			int validsender = 1;
+
+			nextmsg = strstr(currmsg, "\n\nstatus");
+			if (nextmsg) { *(nextmsg+1) = '\0'; nextmsg += 2; }
+
+			/* Pick out the real sender of this message */
+			msgfrom = strstr(currmsg, "\nStatus message received from ");
+			if (msgfrom) {
+				sscanf(msgfrom, "\nStatus message received from %16s\n", sender);
+				*msgfrom = '\0';
+			}
+
+			if (statussenders) {
+				get_hts(currmsg, sender, origin, &h, &t, &grouplist, &log, &color, &downcause, NULL, 0, 0);
+				if (!oksender(statussenders, (h ? h->ip : NULL), msg->addr.sin_addr, currmsg)) validsender = 0;
+			}
+
+			if (validsender) {
+				get_hts(currmsg, sender, origin, &h, &t, &grouplist, &log, &color, &downcause, NULL, 1, 1);
+				if (h && dbgfd && dbghost && (strcasecmp(h->hostname, dbghost) == 0)) {
+					fprintf(dbgfd, "\n---- combo message from %s ----\n%s---- end message ----\n", sender, currmsg);
+					fflush(dbgfd);
+				}
+
+				if (color == COL_PURPLE) {
+					errprintf("Ignored PURPLE status update from %s for %s.%s\n",
+						  sender, (h ? h->hostname : "<unknown>"), (t ? t->name : "unknown"));
+				}
+				else {
+					/* Count individual status-messages also */
+					update_statistics(currmsg);
+
+					if (h && t && log && (color != -1)) {
+						handle_status(currmsg, sender, h->hostname, t->name, grouplist, log, color, downcause);
+					}
+				}
+			}
+
+			currmsg = nextmsg;
+		} while (currmsg);
+	}
+	else if (strncmp(msg->buf, "meta", 4) == 0) {
+		char *currmsg, *nextmsg;
+
+		currmsg = msg->buf;
+		do {
+			nextmsg = strstr(currmsg, "\n\nmeta");
+			if (nextmsg) { *(nextmsg+1) = '\0'; nextmsg += 2; }
+
+			get_hts(currmsg, sender, origin, &h, &t, NULL, &log, &color, NULL, NULL, 0, 0);
+			if (h && t && log && oksender(statussenders, (h ? h->ip : NULL), msg->addr.sin_addr, currmsg)) {
+				handle_meta(currmsg, log);
+			}
+
+			currmsg = nextmsg;
+		} while (currmsg);
+	}
+	else if (strncmp(msg->buf, "status", 6) == 0) {
+		msgfrom = strstr(msg->buf, "\nStatus message received from ");
+		if (msgfrom) {
+			sscanf(msgfrom, "\nStatus message received from %16s\n", sender);
+			*msgfrom = '\0';
+		}
+
+		if (statussenders) {
+			get_hts(msg->buf, sender, origin, &h, &t, &grouplist, &log, &color, &downcause, NULL, 0, 0);
+			if (!oksender(statussenders, (h ? h->ip : NULL), msg->addr.sin_addr, msg->buf)) goto done;
+		}
+
+		get_hts(msg->buf, sender, origin, &h, &t, &grouplist, &log, &color, &downcause, NULL, 1, 1);
+		if (h && dbgfd && dbghost && (strcasecmp(h->hostname, dbghost) == 0)) {
+			fprintf(dbgfd, "\n---- status message from %s ----\n%s---- end message ----\n", sender, msg->buf);
+			fflush(dbgfd);
+		}
+
+		if (color == COL_PURPLE) {
+			errprintf("Ignored PURPLE status update from %s for %s.%s\n",
+				  sender, (h ? h->hostname : "<unknown>"), (t ? t->name : "unknown"));
+		}
+		else {
+			if (h && t && log && (color != -1)) {
+				handle_status(msg->buf, sender, h->hostname, t->name, grouplist, log, color, downcause);
+			}
+		}
+	}
+	else if (strncmp(msg->buf, "data", 4) == 0) {
+		char *hostname = NULL, *testname = NULL;
+		char *bhost, *ehost, *btest;
+		char savechar;
+
+		msgfrom = strstr(msg->buf, "\nStatus message received from ");
+		if (msgfrom) {
+			sscanf(msgfrom, "\nStatus message received from %16s\n", sender);
+			*msgfrom = '\0';
+		}
+
+		bhost = msg->buf + strlen("data"); bhost += strspn(bhost, " \t");
+		ehost = bhost + strcspn(bhost, " \t\r\n");
+		savechar = *ehost; *ehost = '\0';
+
+		btest = strrchr(bhost, '.');
+		if (btest) {
+			char *p;
+
+			*btest = '\0';
+			hostname = strdup(bhost);
+			p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.';
+			*btest = '.';
+			testname = strdup(btest+1);
+
+			if (*hostname == '\0') { errprintf("Invalid data message from %s - blank hostname\n", sender); xfree(hostname); hostname = NULL; }
+			if (*testname == '\0') { errprintf("Invalid data message from %s - blank testname\n", sender); xfree(testname); testname = NULL; }
+		}
+		else {
+			errprintf("Invalid data message - no testname in '%s'\n", bhost);
+		}
+
+		*ehost = savechar;
+
+		if (hostname && testname) {
+			char *hname, hostip[IP_ADDR_STRLEN];
+
+			MEMDEFINE(hostip);
+
+			hname = knownhost(hostname, hostip, ghosthandling);
+
+			if (hname == NULL) {
+				log_ghost(hostname, sender, msg->buf);
+			}
+			else if (!oksender(statussenders, hostip, msg->addr.sin_addr, msg->buf)) {
+				/* Invalid sender */
+				errprintf("Invalid data message - sender %s not allowed for host %s\n", sender, hostname);
+			}
+			else {
+				handle_data(msg->buf, sender, origin, hname, testname);
+			}
+
+			xfree(hostname); xfree(testname);
+
+			MEMUNDEFINE(hostip);
+		}
+	}
+	else if (strncmp(msg->buf, "summary", 7) == 0) {
+		/* Summaries are always allowed. Or should we ? */
+		get_hts(msg->buf, sender, origin, &h, &t, NULL, &log, &color, NULL, NULL, 1, 1);
+		if (h && t && log && (color != -1)) {
+			handle_status(msg->buf, sender, h->hostname, t->name, NULL, log, color, NULL);
+		}
+	}
+	else if ((strncmp(msg->buf, "notes", 5) == 0) || (strncmp(msg->buf, "usermsg", 7) == 0)) {
+		char *hostname, *bhost, *ehost, *p;
+		char savechar;
+
+		bhost = msg->buf + strcspn(msg->buf, " \t\r\n"); bhost += strspn(bhost, " \t");
+		ehost = bhost + strcspn(bhost, " \t\r\n");
+		savechar = *ehost; *ehost = '\0';
+		hostname = strdup(bhost);
+		*ehost = savechar;
+
+		p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.';
+		if (*hostname == '\0') { errprintf("Invalid notes/user message from %s - blank hostname\n", sender); xfree(hostname); hostname = NULL; }
+
+		if (hostname) {
+			char *hname, hostip[IP_ADDR_STRLEN];
+
+			MEMDEFINE(hostip);
+
+			hname = knownhost(hostname, hostip, ghosthandling);
+			if (hname == NULL) {
+				log_ghost(hostname, sender, msg->buf);
+			}
+			else {
+				if (*msg->buf == 'n') {
+					/* "notes" message */
+					if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) {
+						/* Invalid sender */
+						errprintf("Invalid notes message - sender %s not allowed for host %s\n", sender, hostname);
+					}
+					else {
+						handle_notes(msg->buf, sender, hostname);
+					}
+				}
+				else if (*msg->buf == 'u') {
+					/* "usermsg" message */
+					if (!oksender(statussenders, NULL, msg->addr.sin_addr, msg->buf)) {
+						/* Invalid sender */
+						errprintf("Invalid user message - sender %s not allowed for host %s\n", sender, hostname);
+					}
+					else {
+						handle_usermsg(msg->buf, sender, hostname);
+					}
+				}
+			}
+
+			xfree(hostname);
+
+			MEMUNDEFINE(hostip);
+		}
+	}
+	else if (strncmp(msg->buf, "enable", 6) == 0) {
+		handle_enadis(1, msg, sender);
+	}
+	else if (strncmp(msg->buf, "disable", 7) == 0) {
+		handle_enadis(0, msg, sender);
+	}
+	else if (allow_downloads && (strncmp(msg->buf, "config", 6) == 0)) {
+		char *conffn, *p;
+
+		if (!oksender(statussenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		p = msg->buf + 6; p += strspn(p, " \t");
+		p = strtok(p, " \t\r\n");
+		conffn = strdup(p);
+		xfree(msg->buf);
+		if (conffn && (strstr(conffn, "../") == NULL) && (get_config(conffn, msg) == 0) ) {
+			msg->doingwhat = RESPONDING;
+			msg->bufp = msg->buf;
+		}
+	}
+	else if (allow_downloads && (strncmp(msg->buf, "download", 8) == 0)) {
+		char *fn, *p;
+
+		if (!oksender(statussenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		p = msg->buf + 8; p += strspn(p, " \t");
+		p = strtok(p, " \t\r\n");
+		fn = strdup(p);
+		xfree(msg->buf);
+		if (fn && (strstr(fn, "../") == NULL) && (get_binary(fn, msg) == 0) ) {
+			msg->doingwhat = RESPONDING;
+			msg->bufp = msg->buf;
+		}
+	}
+	else if (strncmp(msg->buf, "flush filecache", 15) == 0) {
+		flush_filecache();
+	}
+	else if (strncmp(msg->buf, "query ", 6) == 0) {
+		get_hts(msg->buf, sender, origin, &h, &t, NULL, &log, &color, NULL, NULL, 0, 0);
+		if (!oksender(statussenders, (h ? h->ip : NULL), msg->addr.sin_addr, msg->buf)) goto done;
+
+		if (log) {
+			xfree(msg->buf);
+			msg->doingwhat = RESPONDING;
+			if (log->message) {
+				unsigned char *bol, *eoln;
+				int msgcol;
+				char response[500];
+
+				bol = msg_data(log->message);
+				msgcol = parse_color(bol);
+				if (msgcol != -1) {
+					/* Skip the color - it may be different in real life */
+					bol += strlen(colorname(msgcol));
+					bol += strspn(bol, " \t");
+				}
+				eoln = strchr(bol, '\n'); if (eoln) *eoln = '\0';
+				snprintf(response, sizeof(response), "%s %s\n", colorname(log->color), bol);
+				response[sizeof(response)-1] = '\0';
+				if (eoln) *eoln = '\n';
+
+				msg->buf = msg->bufp = strdup(response);
+				msg->buflen = strlen(msg->buf);
+			}
+			else {
+				msg->buf = msg->bufp = strdup("");
+				msg->buflen = 0;
+			}
+		}
+	}
+	else if (strncmp(msg->buf, "hobbitdlog ", 11) == 0) {
+		/* 
+		 * Request for a single status log
+		 * hobbitdlog HOST.TEST [fields=FIELDLIST]
+		 *
+		 */
+
+		pcre *spage = NULL, *shost = NULL, *snet = NULL, *stest = NULL;
+		char *chspage, *chshost, *chsnet, *chstest, *fields = NULL;
+		int scolor = -1, acklevel = -1;
+
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		setup_filter(msg->buf, 
+		 	     "hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,ackmsg,dismsg,client",
+			     &spage, &shost, &snet, &stest, &scolor, &acklevel, &fields,
+			     &chspage, &chshost, &chsnet, &chstest);
+
+		log = find_log(chshost, chstest, "", &h);
+		if (log) {
+			char *buf, *bufp;
+			int bufsz;
+
+			flush_acklist(log, 0);
+			if (log->message == NULL) {
+				errprintf("%s.%s has a NULL message\n", log->host->hostname, log->test->name);
+				log->message = strdup("");
+			}
+
+			bufsz = 1024 + strlen(log->message);
+			if (log->ackmsg) bufsz += 2*strlen(log->ackmsg);
+			if (log->dismsg) bufsz += 2*strlen(log->dismsg);
+
+			xfree(msg->buf);
+			bufp = buf = (char *)malloc(bufsz);
+			generate_outbuf(&buf, &bufp, &bufsz, h, log, acklevel);
+			bufp += sprintf(bufp, "%s", msg_data(log->message));
+
+			msg->doingwhat = RESPONDING;
+			msg->bufp = msg->buf = buf;
+			msg->buflen = (bufp - buf);
+		}
+
+		freeregex(spage); freeregex(shost); freeregex(snet); freeregex(stest);
+	}
+	else if (strncmp(msg->buf, "hobbitdxlog ", 12) == 0) {
+		/* 
+		 * Request for a single status log in XML format
+		 * hobbitdxlog HOST.TEST
+		 *
+		 */
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		get_hts(msg->buf, sender, origin, &h, &t, NULL, &log, &color, NULL, NULL, 0, 0);
+		if (log) {
+			char *buf, *bufp;
+			int bufsz, buflen;
+			hobbitd_meta_t *mwalk;
+
+			flush_acklist(log, 0);
+			if (log->message == NULL) {
+				errprintf("%s.%s has a NULL message\n", log->host->hostname, log->test->name);
+				log->message = strdup("");
+			}
+
+			bufsz = 4096 + strlen(log->message);
+			if (log->ackmsg) bufsz += strlen(log->ackmsg);
+			if (log->dismsg) bufsz += strlen(log->dismsg);
+
+			xfree(msg->buf);
+			bufp = buf = (char *)malloc(bufsz);
+			buflen = 0;
+
+			bufp += sprintf(bufp, "<?xml version='1.0' encoding='ISO-8859-1'?>\n");
+			bufp += sprintf(bufp, "<ServerStatus>\n");
+			bufp += sprintf(bufp, "  <ServerName>%s</ServerName>\n", h->hostname);
+			bufp += sprintf(bufp, "  <Type>%s</Type>\n", log->test->name);
+			bufp += sprintf(bufp, "  <Status>%s</Status>\n", colnames[log->color]);
+			bufp += sprintf(bufp, "  <TestFlags>%s</TestFlags>\n", (log->testflags ? log->testflags : ""));
+			bufp += sprintf(bufp, "  <LastChange>%s</LastChange>\n", timestr(log->lastchange));
+			bufp += sprintf(bufp, "  <LogTime>%s</LogTime>\n", timestr(log->logtime));
+			bufp += sprintf(bufp, "  <ValidTime>%s</ValidTime>\n", timestr(log->validtime));
+			bufp += sprintf(bufp, "  <AckTime>%s</AckTime>\n", timestr(log->acktime));
+			bufp += sprintf(bufp, "  <DisableTime>%s</DisableTime>\n", timestr(log->enabletime));
+			bufp += sprintf(bufp, "  <Sender>%s</Sender>\n", log->sender);
+
+			if (log->cookie > 0)
+				bufp += sprintf(bufp, "  <Cookie>%d</Cookie>\n", log->cookie);
+			else
+				bufp += sprintf(bufp, "  <Cookie>N/A</Cookie>\n");
+
+			if (log->ackmsg && (log->acktime > now))
+				bufp += sprintf(bufp, "  <AckMsg><![CDATA[%s]]></AckMsg>\n", log->ackmsg);
+			else
+				bufp += sprintf(bufp, "  <AckMsg>N/A</AckMsg>\n");
+
+			if (log->dismsg && (log->enabletime > now))
+				bufp += sprintf(bufp, "  <DisMsg><![CDATA[%s]]></DisMsg>\n", log->dismsg);
+			else
+				bufp += sprintf(bufp, "  <DisMsg>N/A</DisMsg>\n");
+
+			bufp += sprintf(bufp, "  <Message><![CDATA[%s]]></Message>\n", msg_data(log->message));
+			for (mwalk = log->metas; (mwalk); mwalk = mwalk->next) {
+				bufp += sprintf(bufp, "<%s>\n%s</%s>\n", 
+						mwalk->metaname->name, mwalk->value, mwalk->metaname->name);
+			}
+			bufp += sprintf(bufp, "</ServerStatus>\n");
+
+			msg->doingwhat = RESPONDING;
+			msg->bufp = msg->buf = buf;
+			msg->buflen = (bufp - buf);
+		}
+	}
+	else if (strncmp(msg->buf, "hobbitdboard", 12) == 0) {
+		/* 
+		 * Request for a summmary of all known status logs
+		 *
+		 */
+		RbtIterator hosthandle;
+		hobbitd_hostlist_t *hwalk;
+		hobbitd_log_t *lwalk, *firstlog;
+		hobbitd_log_t infologrec, rrdlogrec;
+		testinfo_t trendstest, infotest;
+		char *buf, *bufp;
+		int bufsz;
+		pcre *spage = NULL, *shost = NULL, *snet = NULL, *stest = NULL;
+		char *chspage = NULL, *chshost = NULL, *chsnet = NULL, *chstest = NULL;
+		char *fields = NULL;
+		int scolor = -1, acklevel = -1;
+		static unsigned int lastboardsize = 0;
+
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		setup_filter(msg->buf, 
+			     "hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1",
+			     &spage, &shost, &snet, &stest, &scolor, &acklevel, &fields,
+			     &chspage, &chshost, &chsnet, &chstest);
+
+		if (lastboardsize <= 8192) {
+			/* A guesstimate - 8 tests per hosts, 1KB/test (only 1st line of msg) */
+			bufsz = (hostcount+1)*8*1024; 
+		}
+		else {
+			/* Add 10% to the last size we used */
+			bufsz = lastboardsize + (lastboardsize / 10);
+		}
+		bufp = buf = (char *)malloc(bufsz);
+
+		/* Setup fake log-records for the "info" and "trends" data. */
+		memset(&infotest, 0, sizeof(infotest));
+		infotest.name = xgetenv("INFOCOLUMN");
+		memset(&infologrec, 0, sizeof(infologrec));
+		infologrec.test = &infotest;
+
+		memset(&trendstest, 0, sizeof(trendstest));
+		trendstest.name = xgetenv("TRENDSCOLUMN");
+		memset(&rrdlogrec, 0, sizeof(rrdlogrec));
+		rrdlogrec.test = &trendstest;
+
+		infologrec.color = rrdlogrec.color = COL_GREEN;
+		infologrec.message = rrdlogrec.message = "";
+
+		for (hosthandle = rbtBegin(rbhosts); (hosthandle != rbtEnd(rbhosts)); hosthandle = rbtNext(rbhosts, hosthandle)) {
+			hwalk = gettreeitem(rbhosts, hosthandle);
+			if (!hwalk) {
+				errprintf("host-tree has a record with no data\n");
+				continue;
+			}
+
+			/* If there is a hostname filter, drop the "summary" 'hosts' */
+			if (shost && (hwalk->hosttype != H_NORMAL)) continue;
+
+			firstlog = hwalk->logs;
+
+			if (hwalk->hosttype == H_NORMAL) {
+				namelist_t *hinfo = hostinfo(hwalk->hostname);
+
+				if (!hinfo) {
+					errprintf("Hostname '%s' in tree, but no host-info\n", hwalk->hostname);
+					continue;
+				}
+
+				/* Host/pagename filter */
+				if (!match_host_filter(hinfo, spage, shost, snet)) continue;
+
+				/* Handle NOINFO and NOTRENDS here */
+				if (!bbh_item(hinfo, BBH_FLAG_NOINFO)) {
+					infologrec.next = firstlog;
+					firstlog = &infologrec;
+				}
+				if (!bbh_item(hinfo, BBH_FLAG_NOTRENDS)) {
+					rrdlogrec.next = firstlog;
+					firstlog = &rrdlogrec;
+				}
+			}
+
+			for (lwalk = firstlog; (lwalk); lwalk = lwalk->next) {
+				if (!match_test_filter(lwalk, stest, scolor)) continue;
+
+				if (lwalk->message == NULL) {
+					errprintf("%s.%s has a NULL message\n", lwalk->host->hostname, lwalk->test->name);
+					lwalk->message = strdup("");
+				}
+
+				generate_outbuf(&buf, &bufp, &bufsz, hwalk, lwalk, acklevel);
+			}
+		}
+		*bufp = '\0';
+
+		xfree(msg->buf);
+		msg->doingwhat = RESPONDING;
+		msg->bufp = msg->buf = buf;
+		msg->buflen = (bufp - buf);
+		if (msg->buflen > lastboardsize) lastboardsize = msg->buflen;
+
+		freeregex(spage); freeregex(shost); freeregex(snet); freeregex(stest);
+	}
+	else if (strncmp(msg->buf, "hobbitdxboard", 13) == 0) {
+		/* 
+		 * Request for a summmary of all known status logs in XML format
+		 *
+		 */
+		RbtIterator hosthandle;
+		hobbitd_hostlist_t *hwalk;
+		hobbitd_log_t *lwalk;
+		char *buf, *bufp;
+		int bufsz;
+		pcre *spage = NULL, *shost = NULL, *snet = NULL, *stest = NULL;
+		char *chspage = NULL, *chshost = NULL, *chsnet = NULL, *chstest = NULL;
+		char *fields = NULL;
+		int scolor = -1, acklevel = -1;
+		static unsigned int lastboardsize = 0;
+
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		setup_filter(msg->buf,
+			     "hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1",
+			     &spage, &shost, &snet, &stest, &scolor, &acklevel, &fields,
+			     &chspage, &chshost, &chsnet, &chstest);
+
+		if (lastboardsize <= 8192) {
+			/* A guesstimate - 8 tests per hosts, 2KB/test (only 1st line of msg) */
+			bufsz = (hostcount+1)*8*2048; 
+		}
+		else {
+			/* Add 10% to the last size we used */
+			bufsz = lastboardsize + (lastboardsize / 10);
+		}
+		bufp = buf = (char *)malloc(bufsz);
+
+		bufp += sprintf(bufp, "<?xml version='1.0' encoding='ISO-8859-1'?>\n");
+		bufp += sprintf(bufp, "<StatusBoard>\n");
+
+		for (hosthandle = rbtBegin(rbhosts); (hosthandle != rbtEnd(rbhosts)); hosthandle = rbtNext(rbhosts, hosthandle)) {
+			namelist_t *hinfo;
+
+			hwalk = gettreeitem(rbhosts, hosthandle);
+			if (!hwalk) {
+				errprintf("host-tree has a record with no data\n");
+				continue;
+			}
+
+			hinfo = hostinfo(hwalk->hostname);
+
+			/* Host/pagename filter */
+			if (!match_host_filter(hinfo, spage, shost, snet)) continue;
+
+			for (lwalk = hwalk->logs; (lwalk); lwalk = lwalk->next) {
+				char *eoln;
+				int buflen = (bufp - buf);
+
+				if (!match_test_filter(lwalk, stest, scolor)) continue;
+
+				if (lwalk->message == NULL) {
+					errprintf("%s.%s has a NULL message\n", lwalk->host->hostname, lwalk->test->name);
+					lwalk->message = strdup("");
+				}
+
+				eoln = strchr(lwalk->message, '\n');
+				if (eoln) *eoln = '\0';
+				if ((bufsz - buflen - strlen(lwalk->message)) < 4096) {
+					bufsz += (16384 + strlen(lwalk->message));
+					buf = (char *)realloc(buf, bufsz);
+					bufp = buf + buflen;
+				}
+
+				bufp += sprintf(bufp, "  <ServerStatus>\n");
+				bufp += sprintf(bufp, "    <ServerName>%s</ServerName>\n", hwalk->hostname);
+				bufp += sprintf(bufp, "    <Type>%s</Type>\n", lwalk->test->name);
+				bufp += sprintf(bufp, "    <Status>%s</Status>\n", colnames[lwalk->color]);
+				bufp += sprintf(bufp, "    <TestFlags>%s</TestFlags>\n", (lwalk->testflags ? lwalk->testflags : ""));
+				bufp += sprintf(bufp, "    <LastChange>%s</LastChange>\n", timestr(lwalk->lastchange));
+				bufp += sprintf(bufp, "    <LogTime>%s</LogTime>\n", timestr(lwalk->logtime));
+				bufp += sprintf(bufp, "    <ValidTime>%s</ValidTime>\n", timestr(lwalk->validtime));
+				bufp += sprintf(bufp, "    <AckTime>%s</AckTime>\n", timestr(lwalk->acktime));
+				bufp += sprintf(bufp, "    <DisableTime>%s</DisableTime>\n", timestr(lwalk->enabletime));
+				bufp += sprintf(bufp, "    <Sender>%s</Sender>\n", lwalk->sender);
+
+				if (lwalk->cookie > 0)
+					bufp += sprintf(bufp, "    <Cookie>%d</Cookie>\n", lwalk->cookie);
+				else
+					bufp += sprintf(bufp, "    <Cookie>N/A</Cookie>\n");
+
+				bufp += sprintf(bufp, "    <MessageSummary><![CDATA[%s]]></MessageSummary>\n", lwalk->message);
+				bufp += sprintf(bufp, "  </ServerStatus>\n");
+				if (eoln) *eoln = '\n';
+			}
+		}
+		bufp += sprintf(bufp, "</StatusBoard>\n");
+
+		xfree(msg->buf);
+		msg->doingwhat = RESPONDING;
+		msg->bufp = msg->buf = buf;
+		msg->buflen = (bufp - buf);
+		if (msg->buflen > lastboardsize) lastboardsize = msg->buflen;
+
+		freeregex(spage); freeregex(shost); freeregex(snet); freeregex(stest);
+	}
+	else if (strncmp(msg->buf, "hostinfo", 8) == 0) {
+		/* 
+		 * Request for host configuration info
+		 *
+		 */
+		namelist_t *hinfo;
+		char *buf, *bufp;
+		int bufsz;
+		pcre *spage = NULL, *shost = NULL, *stest = NULL, *snet = NULL;
+		char *chspage = NULL, *chshost = NULL, *chsnet = NULL, *chstest = NULL;
+		char *fields = NULL;
+		int scolor = -1, acklevel = -1;
+		static unsigned int lastboardsize = 0;
+
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		setup_filter(msg->buf, 
+			     "BBH_HOSTNAME,BBH_IP,BBH_RAW",
+			     &spage, &shost, &snet, &stest, &scolor, &acklevel, &fields,
+			     &chspage, &chshost, &chsnet, &chstest);
+
+		if (lastboardsize == 0) {
+			/* A guesstimate - 500 bytes per host */
+			bufsz = (hostcount+1)*500;
+		}
+		else {
+			/* Add 10% to the last size we used */
+			bufsz = lastboardsize + (lastboardsize / 10);
+		}
+		bufp = buf = (char *)malloc(bufsz);
+
+		for (hinfo = first_host(); (hinfo); hinfo = hinfo->next) {
+			if (!match_host_filter(hinfo, spage, shost, snet)) continue;
+			generate_hostinfo_outbuf(&buf, &bufp, &bufsz, hinfo);
+		}
+
+		*bufp = '\0';
+
+		xfree(msg->buf);
+		msg->doingwhat = RESPONDING;
+		msg->bufp = msg->buf = buf;
+		msg->buflen = (bufp - buf);
+		if (msg->buflen > lastboardsize) lastboardsize = msg->buflen;
+
+		freeregex(spage); freeregex(shost); freeregex(snet); freeregex(stest);
+	}
+
+	else if ((strncmp(msg->buf, "hobbitdack", 10) == 0) || (strncmp(msg->buf, "ack ack_event", 13) == 0)) {
+		/* hobbitdack COOKIE DURATION TEXT */
+		char *p;
+		int cookie, duration;
+		char durstr[100];
+		hobbitd_log_t *lwalk;
+
+		if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		MEMDEFINE(durstr);
+
+		/*
+		 * For just a bit of compatibility with the old BB system,
+		 * we will accept an "ack ack_event" message. This allows us
+		 * to work with existing acknowledgement scripts.
+		 */
+		if (strncmp(msg->buf, "hobbitdack", 10) == 0) p = msg->buf + 10;
+		else if (strncmp(msg->buf, "ack ack_event", 13) == 0) p = msg->buf + 13;
+		else p = msg->buf;
+
+		if (sscanf(p, "%d %99s", &cookie, durstr) == 2) {
+			log = find_cookie(abs(cookie));
+			if (log) {
+				duration = durationvalue(durstr);
+				if (cookie > 0)
+					handle_ack(p, sender, log, duration);
+				else {
+					/*
+					 * Negative cookies mean to ack all pending alerts for
+					 * the host. So loop over the host logs and ack all that
+					 * have a valid cookie (i.e. not -1)
+					 */
+					for (lwalk = log->host->logs; (lwalk); lwalk = lwalk->next) {
+						if (lwalk->cookie != -1) handle_ack(p, sender, lwalk, duration);
+					}
+				}
+			}
+			else {
+				errprintf("Cookie %d not found, dropping ack\n", cookie);
+			}
+		}
+		else {
+			errprintf("Bogus ack message from %s: '%s'\n", sender, msg->buf);
+		}
+
+		MEMUNDEFINE(durstr);
+	}
+	else if (strncmp(msg->buf, "ackinfo ", 8) == 0) {
+		/* ackinfo HOST.TEST\nlevel\nvaliduntil\nackedby\nmsg */
+		int ackall = 0;
+
+		if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		get_hts(msg->buf, sender, origin, &h, &t, NULL, &log, &color, NULL, &ackall, 0, 0);
+		if (log) {
+			handle_ackinfo(msg->buf, sender, log);
+		}
+		else if (ackall) {
+			hobbitd_log_t *lwalk;
+
+			for (lwalk = h->logs; (lwalk); lwalk = lwalk->next) {
+				if (decide_alertstate(lwalk->color) != A_OK) {
+					handle_ackinfo(msg->buf, sender, lwalk);
+				}
+			}
+		}
+	}
+	else if (strncmp(msg->buf, "drop ", 5) == 0) {
+		char *hostname = NULL, *testname = NULL;
+		char *p;
+
+		if (!oksender(adminsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		p = msg->buf + 4; p += strspn(p, " \t");
+		hostname = strtok(p, " \t");
+		if (hostname) testname = strtok(NULL, " \t");
+
+		if (hostname && !testname) {
+			handle_dropnrename(CMD_DROPHOST, sender, hostname, NULL, NULL);
+		}
+		else if (hostname && testname) {
+			handle_dropnrename(CMD_DROPTEST, sender, hostname, testname, NULL);
+		}
+	}
+	else if (strncmp(msg->buf, "rename ", 7) == 0) {
+		char *hostname = NULL, *n1 = NULL, *n2 = NULL;
+		char *p;
+
+		if (!oksender(adminsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		p = msg->buf + 6; p += strspn(p, " \t");
+		hostname = strtok(p, " \t");
+		if (hostname) n1 = strtok(NULL, " \t");
+		if (n1) n2 = strtok(NULL, " \t");
+
+		if (hostname && n1 && !n2) {
+			/* Host rename */
+			handle_dropnrename(CMD_RENAMEHOST, sender, hostname, n1, NULL);
+		}
+		else if (hostname && n1 && n2) {
+			/* Test rename */
+			handle_dropnrename(CMD_RENAMETEST, sender, hostname, n1, n2);
+		}
+	}
+	else if (strncmp(msg->buf, "dummy", 5) == 0) {
+		/* Do nothing */
+	}
+	else if (strncmp(msg->buf, "ping", 4) == 0) {
+		/* Tell them we're here */
+		char id[128];
+
+		sprintf(id, "hobbitd %s\n", VERSION);
+		msg->doingwhat = RESPONDING;
+		xfree(msg->buf);
+		msg->bufp = msg->buf = strdup(id);
+		msg->buflen = strlen(msg->buf);
+	}
+	else if (strncmp(msg->buf, "notify", 6) == 0) {
+		if (!oksender(maintsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+		get_hts(msg->buf, sender, origin, &h, &t, NULL, &log, &color, NULL, NULL, 0, 0);
+		if (h && t) handle_notify(msg->buf, sender, h->hostname, t->name);
+	}
+	else if (strncmp(msg->buf, "schedule", 8) == 0) {
+		char *cmd;
+
+		/*
+		 * Schedule a later command. This is either
+		 * "schedule" - no params: list the currently scheduled commands.
+		 * "schedule TIME COMMAND": Add a COMMAND to run at TIME.
+		 * "schedule cancel ID": Cancel the scheduled command with id ID.
+		 */
+		cmd = msg->buf + 8; cmd += strspn(cmd, " ");
+
+		if (strlen(cmd) == 0) {
+			char *buf, *bufp;
+			int bufsz, buflen;
+			scheduletask_t *swalk;
+
+			bufsz = 4096;
+			bufp = buf = (char *)malloc(bufsz);
+			*buf = '\0'; buflen = 0;
+
+			for (swalk = schedulehead; (swalk); swalk = swalk->next) {
+				int needed = 128 + strlen(swalk->command);
+
+				if ((bufsz - (bufp - buf)) < needed) {
+					int buflen = (bufp - buf);
+					bufsz += 4096 + needed;
+					buf = (char *)realloc(buf, bufsz);
+					bufp = buf + buflen;
+				}
+
+				bufp += sprintf(bufp, "%d|%d|%s|%s\n", swalk->id,
+						(int)swalk->executiontime, swalk->sender, nlencode(swalk->command));
+			}
+
+			xfree(msg->buf);
+			msg->doingwhat = RESPONDING;
+			msg->bufp = msg->buf = buf;
+			msg->buflen = (bufp - buf);
+		}
+		else {
+			if (strncmp(cmd, "cancel", 6) != 0) {
+				scheduletask_t *newitem = (scheduletask_t *)malloc(sizeof(scheduletask_t));
+
+				newitem->id = nextschedid++;
+				newitem->executiontime = (time_t) atoi(cmd);
+				cmd += strspn(cmd, "0123456789");
+				cmd += strspn(cmd, " ");
+				newitem->sender = strdup(sender);
+				newitem->command = strdup(cmd);
+				newitem->next = schedulehead;
+				schedulehead = newitem;
+			}
+			else {
+				scheduletask_t *swalk, *sprev;
+				int id;
+				
+				id = atoi(cmd + 6);
+				swalk = schedulehead; sprev = NULL; 
+				while (swalk && (swalk->id != id)) {
+					sprev = swalk; 
+					swalk = swalk->next;
+				}
+
+				if (swalk) {
+					xfree(swalk->sender);
+					xfree(swalk->command);
+					if (sprev == NULL) {
+						schedulehead = swalk->next;
+					}
+					else {
+						sprev->next = swalk->next;
+					}
+					xfree(swalk);
+				}
+			}
+		}
+	}
+	else if (strncmp(msg->buf, "client ", 7) == 0) {
+		/* "client HOSTNAME.CLIENTOS CLIENTCLASS" */
+		char *hostname = NULL, *clientos = NULL, *clientclass = NULL;
+		char *hname = NULL;
+		char *line1, *p;
+		char savech;
+
+		msgfrom = strstr(msg->buf, "\n[proxy]\n");
+		if (msgfrom) {
+			char *ipline = strstr(msgfrom, "\nClientIP:");
+			if (ipline) { 
+				sscanf(ipline, "\nClientIP:%16s\n", sender);
+			}
+		}
+
+		p = msg->buf + strcspn(msg->buf, "\r\n");
+		if ((*p == '\r') || (*p == '\n')) {
+			savech = *p;
+			*p = '\0';
+		}
+		else {
+			p = NULL;
+		}
+		line1 = strdup(msg->buf); if (p) *p = savech;
+
+		p = strtok(line1, " \t"); /* Skip the client keyword */
+		if (p) hostname = strtok(NULL, " \t"); /* Actually, HOSTNAME.CLIENTOS */
+		if (hostname) {
+			clientos = strrchr(hostname, '.'); 
+			if (clientos) { *clientos = '\0'; clientos++; }
+			p = hostname; while ((p = strchr(p, ',')) != NULL) *p = '.';
+			clientclass = strtok(NULL, " \t");
+		}
+
+		if (hostname && clientos) {
+			char hostip[IP_ADDR_STRLEN];
+
+			MEMDEFINE(hostip);
+
+			hname = knownhost(hostname, hostip, ghosthandling);
+
+			if (hname == NULL) {
+				log_ghost(hostname, sender, msg->buf);
+			}
+			else if (!oksender(statussenders, hostip, msg->addr.sin_addr, msg->buf)) {
+				/* Invalid sender */
+				errprintf("Invalid client message - sender %s not allowed for host %s\n", sender, hostname);
+				hname = NULL;
+			}
+			else {
+				namelist_t *hinfo = hostinfo(hname);
+
+				handle_client(msg->buf, sender, hname, clientos, clientclass);
+
+				if (hinfo) {
+					if (clientos) bbh_set_item(hinfo, BBH_OS, clientos);
+					if (clientclass) {
+						/*
+						 * If the client sends an explicit class,
+						 * save it for later use unless there is an
+						 * explicit override (BBH_CLASS is alread set).
+						 */
+						char *forcedclass = bbh_item(hinfo, BBH_CLASS);
+
+						if (!forcedclass) 
+							bbh_set_item(hinfo, BBH_CLASS, clientclass);
+						else 
+							clientclass = forcedclass;
+					}
+				}
+			}
+
+			MEMUNDEFINE(hostip);
+		}
+
+		if (hname) {
+			char *cfg;
+			
+			cfg = get_clientconfig(hname, clientclass, clientos);
+			if (cfg) {
+				msg->doingwhat = RESPONDING;
+				xfree(msg->buf);
+				msg->bufp = msg->buf = strdup(cfg);
+				msg->buflen = strlen(msg->buf);
+			}
+		}
+
+		xfree(line1);
+	}
+	else if (strncmp(msg->buf, "clientlog ", 10) == 0) {
+		char *hostname, *p;
+		RbtIterator hosthandle;
+		if (!oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) goto done;
+
+		p = msg->buf + strlen("clientlog"); p += strspn(p, "\t ");
+		hostname = p; p += strcspn(p, "\t "); if (*p) { *p = '\0'; p++; }
+
+		hosthandle = rbtFind(rbhosts, hostname);
+		if (hosthandle != rbtEnd(rbhosts)) {
+			hobbitd_hostlist_t *hwalk;
+			hwalk = gettreeitem(rbhosts, hosthandle);
+
+			if (hwalk->clientmsg) {
+				char *sections = NULL;
+
+				if (strncmp(p, "section=", 8) == 0) sections = strdup(p+8);
+
+				xfree(msg->buf);
+				msg->buf = NULL;
+				msg->doingwhat = RESPONDING;
+
+				if (!sections) {
+					msg->bufp = msg->buf = strdup(hwalk->clientmsg);
+					msg->buflen = strlen(msg->buf);
+				}
+				else {
+					char *onesect;
+					strbuffer_t *resp;
+
+					onesect = strtok(sections, ",");
+					resp = newstrbuffer(0);
+					while (onesect) {
+						char *sectmarker = (char *)malloc(strlen(onesect) + 4);
+						char *beginp, *endp;
+
+						sprintf(sectmarker, "\n[%s]", onesect);
+						beginp = strstr(hwalk->clientmsg, sectmarker);
+						if (beginp) {
+							beginp += 1; /* Skip the newline */
+							endp = strstr(beginp, "\n[");
+							if (endp) { endp++; *endp = '\0'; }
+							addtobuffer(resp, beginp);
+							if (endp) *endp = '[';
+						}
+
+						xfree(sectmarker);
+						onesect = strtok(NULL, ",");
+					}
+
+					msg->buflen = STRBUFLEN(resp);
+					msg->buf = grabstrbuffer(resp);
+					if (!msg->buf) msg->buf = strdup("");
+					msg->bufp = msg->buf;
+				}
+			}
+		}
+	}
+	else if (strncmp(msg->buf, "ghostlist", 9) == 0) {
+		if (oksender(wwwsenders, NULL, msg->addr.sin_addr, msg->buf)) {
+			RbtHandle ghandle;
+			ghostlist_t *gwalk;
+			strbuffer_t *resp;
+			char msgline[1024];
+
+			resp = newstrbuffer(0);
+
+			for (ghandle = rbtBegin(rbghosts); (ghandle != rbtEnd(rbghosts)); ghandle = rbtNext(rbghosts, ghandle)) {
+				gwalk = (ghostlist_t *)gettreeitem(rbghosts, ghandle);
+				snprintf(msgline, sizeof(msgline), "%s|%s|%ld\n", 
+					 gwalk->name, gwalk->sender, (long int)gwalk->tstamp);
+				addtobuffer(resp, msgline);
+			}
+
+			msg->doingwhat = RESPONDING;
+			xfree(msg->buf);
+			msg->buflen = STRBUFLEN(resp);
+			msg->buf = grabstrbuffer(resp);
+			if (!msg->buf) msg->buf = strdup("");
+			msg->bufp = msg->buf;
+		}
+	}
+
+done:
+	if (msg->doingwhat == RESPONDING) {
+		shutdown(msg->sock, SHUT_RD);
+	}
+	else {
+		shutdown(msg->sock, SHUT_RDWR);
+		close(msg->sock);
+		msg->sock = -1;
+	}
+
+	MEMUNDEFINE(sender);
+
+	dbgprintf("<- do_message/%d\n", nesting);
+	nesting--;
+}
+
+
+void save_checkpoint(void)
+{
+	char *tempfn;
+	FILE *fd;
+	RbtIterator hosthandle;
+	hobbitd_hostlist_t *hwalk;
+	hobbitd_log_t *lwalk;
+	time_t now = time(NULL);
+	scheduletask_t *swalk;
+	ackinfo_t *awalk;
+	int iores = 0;
+
+	if (checkpointfn == NULL) return;
+
+	dbgprintf("-> save_checkpoint\n");
+	tempfn = malloc(strlen(checkpointfn) + 20);
+	sprintf(tempfn, "%s.%d", checkpointfn, (int)now);
+	fd = fopen(tempfn, "w");
+	if (fd == NULL) {
+		errprintf("Cannot open checkpoint file %s : %s\n", tempfn, strerror(errno));
+		xfree(tempfn);
+		return;
+	}
+
+	for (hosthandle = rbtBegin(rbhosts); ((hosthandle != rbtEnd(rbhosts)) && (iores >= 0)); hosthandle = rbtNext(rbhosts, hosthandle)) {
+		char *msgstr;
+
+		hwalk = gettreeitem(rbhosts, hosthandle);
+
+		for (lwalk = hwalk->logs; (lwalk); lwalk = lwalk->next) {
+			if (lwalk->dismsg && (lwalk->enabletime < now) && (lwalk->enabletime != DISABLED_UNTIL_OK)) {
+				xfree(lwalk->dismsg);
+				lwalk->dismsg = NULL;
+				lwalk->enabletime = 0;
+			}
+			if (lwalk->ackmsg && (lwalk->acktime < now)) {
+				xfree(lwalk->ackmsg);
+				lwalk->ackmsg = NULL;
+				lwalk->acktime = 0;
+			}
+			flush_acklist(lwalk, 0);
+			iores = fprintf(fd, "@@HOBBITDCHK-V1|%s|%s|%s|%s|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%s", 
+				lwalk->origin, hwalk->hostname, lwalk->test->name, lwalk->sender,
+				colnames[lwalk->color], 
+				(lwalk->testflags ? lwalk->testflags : ""),
+				colnames[lwalk->oldcolor],
+				(int)lwalk->logtime, (int) lwalk->lastchange, (int) lwalk->validtime, 
+				(int) lwalk->enabletime, (int) lwalk->acktime, 
+				lwalk->cookie, (int) lwalk->cookieexpires,
+				nlencode(lwalk->message));
+			if (lwalk->dismsg) msgstr = nlencode(lwalk->dismsg); else msgstr = "";
+			if (iores >= 0) iores = fprintf(fd, "|%s", msgstr);
+			if (lwalk->ackmsg) msgstr = nlencode(lwalk->ackmsg); else msgstr = "";
+			if (iores >= 0) iores = fprintf(fd, "|%s", msgstr);
+			if (iores >= 0) iores = fprintf(fd, "\n");
+
+			for (awalk = lwalk->acklist; (awalk && (iores >= 0)); awalk = awalk->next) {
+				iores = fprintf(fd, "@@HOBBITDCHK-V1|.acklist.|%s|%s|%d|%d|%d|%d|%s|%s\n",
+						hwalk->hostname, lwalk->test->name,
+			 			(int)awalk->received, (int)awalk->validuntil, (int)awalk->cleartime,
+						awalk->level, awalk->ackedby, awalk->msg);
+			}
+		}
+	}
+
+	for (swalk = schedulehead; (swalk && (iores >= 0)); swalk = swalk->next) {
+		iores = fprintf(fd, "@@HOBBITDCHK-V1|.task.|%d|%d|%s|%s\n", 
+			swalk->id, (int)swalk->executiontime, swalk->sender, nlencode(swalk->command));
+	}
+
+	if (iores < 0) {
+		errprintf("I/O error while saving the checkpoint file: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	iores = fclose(fd);
+	if (iores == EOF) {
+		errprintf("I/O error while closing the checkpoint file: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	iores = rename(tempfn, checkpointfn);
+	if (iores == -1) {
+		errprintf("I/O error while renaming the checkpoint file: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	xfree(tempfn);
+	dbgprintf("<- save_checkpoint\n");
+}
+
+
+void load_checkpoint(char *fn)
+{
+	FILE *fd;
+	strbuffer_t *inbuf;
+	char *item;
+	int i, err;
+	char hostip[IP_ADDR_STRLEN];
+	RbtIterator hosthandle, testhandle, originhandle;
+	hobbitd_hostlist_t *hitem = NULL;
+	testinfo_t *t = NULL;
+	char *origin = NULL;
+	hobbitd_log_t *ltail = NULL;
+	char *originname, *hostname, *testname, *sender, *testflags, *statusmsg, *disablemsg, *ackmsg; 
+	time_t logtime, lastchange, validtime, enabletime, acktime, cookieexpires;
+	int color = COL_GREEN, oldcolor = COL_GREEN, cookie;
+	int count = 0;
+
+	fd = fopen(fn, "r");
+	if (fd == NULL) {
+		errprintf("Cannot access checkpoint file %s for restore\n", fn);
+		return;
+	}
+
+	MEMDEFINE(hostip);
+
+	inbuf = newstrbuffer(0);
+	initfgets(fd);
+	while (unlimfgets(inbuf, fd)) {
+		originname = hostname = testname = sender = testflags = statusmsg = disablemsg = ackmsg = NULL;
+		logtime = lastchange = validtime = enabletime = acktime = cookieexpires = 0;
+		cookie = -1;
+		err = 0;
+
+		if (strncmp(STRBUF(inbuf), "@@HOBBITDCHK-V1|.task.|", 23) == 0) {
+			scheduletask_t *newtask = (scheduletask_t *)calloc(1, sizeof(scheduletask_t));
+
+			item = gettok(STRBUF(inbuf), "|\n"); i = 0;
+			while (item && !err) {
+				switch (i) {
+				  case 0: break;
+				  case 1: break;
+				  case 2: newtask->id = atoi(item); break;
+				  case 3: newtask->executiontime = (time_t) atoi(item); break;
+				  case 4: newtask->sender = strdup(item); break;
+				  case 5: nldecode(item); newtask->command = strdup(item); break;
+				  default: break;
+				}
+				item = gettok(NULL, "|\n"); i++;
+			}
+
+			if (newtask->id && (newtask->executiontime > time(NULL)) && newtask->sender && newtask->command) {
+				newtask->next = schedulehead;
+				schedulehead = newtask;
+			}
+			else {
+				if (newtask->sender) xfree(newtask->sender);
+				if (newtask->command) xfree(newtask->command);
+				xfree(newtask);
+			}
+
+			continue;
+		}
+
+		if (strncmp(STRBUF(inbuf), "@@HOBBITDCHK-V1|.acklist.|", 26) == 0) {
+			hobbitd_log_t *log = NULL;
+			ackinfo_t *newack = (ackinfo_t *)calloc(1, sizeof(ackinfo_t));
+
+			hitem = NULL;
+
+			item = gettok(STRBUF(inbuf), "|\n"); i = 0;
+			while (item) {
+
+				switch (i) {
+				  case 0: break;
+				  case 1: break;
+				  case 2: 
+					hosthandle = rbtFind(rbhosts, item); 
+					hitem = gettreeitem(rbhosts, hosthandle);
+					break;
+				  case 3: 
+					testhandle = rbtFind(rbtests, item);
+					t = (testhandle == rbtEnd(rbtests)) ? NULL : gettreeitem(rbtests, testhandle);
+					break;
+				  case 4: newack->received = atoi(item); break;
+				  case 5: newack->validuntil = atoi(item); break;
+				  case 6: newack->cleartime = atoi(item); break;
+				  case 7: newack->level = atoi(item); break;
+				  case 8: newack->ackedby = strdup(item); break;
+				  case 9: newack->msg = strdup(item); break;
+				  default: break;
+				}
+				item = gettok(NULL, "|\n"); i++;
+			}
+
+			if (hitem && t) {
+				for (log = hitem->logs; (log && (log->test != t)); log = log->next) ;
+			}
+
+			if (log && newack->msg) {
+				newack->next = log->acklist;
+				log->acklist = newack;
+			}
+			else {
+				if (newack->ackedby) xfree(newack->ackedby);
+				if (newack->msg) xfree(newack->msg);
+				xfree(newack);
+			}
+
+			continue;
+		}
+
+		if (strncmp(STRBUF(inbuf), "@@HOBBITDCHK-V1|.", 17) == 0) continue;
+
+		item = gettok(STRBUF(inbuf), "|\n"); i = 0;
+		while (item && !err) {
+			switch (i) {
+			  case 0: err = ((strcmp(item, "@@HOBBITDCHK-V1") != 0) && (strcmp(item, "@@BBGENDCHK-V1") != 0)); break;
+			  case 1: originname = item; break;
+			  case 2: if (strlen(item)) hostname = item; else err=1; break;
+			  case 3: if (strlen(item)) testname = item; else err=1; break;
+			  case 4: sender = item; break;
+			  case 5: color = parse_color(item); if (color == -1) err = 1; break;
+			  case 6: testflags = item; break;
+			  case 7: oldcolor = parse_color(item); if (oldcolor == -1) oldcolor = NO_COLOR; break;
+			  case 8: logtime = atoi(item); break;
+			  case 9: lastchange = atoi(item); break;
+			  case 10: validtime = atoi(item); break;
+			  case 11: enabletime = atoi(item); break;
+			  case 12: acktime = atoi(item); break;
+			  case 13: cookie = atoi(item); break;
+			  case 14: cookieexpires = atoi(item); break;
+			  case 15: if (strlen(item)) statusmsg = item; else err=1; break;
+			  case 16: disablemsg = item; break;
+			  case 17: ackmsg = item; break;
+			  default: err = 1;
+			}
+
+			item = gettok(NULL, "|\n"); i++;
+		}
+
+		if (i < 17) {
+			errprintf("Too few fields in record - found %d, expected 17\n", i);
+			err = 1;
+		}
+
+		if (err) continue;
+
+		/* Only load hosts we know; they may have been dropped while we were offline */
+		hostname = knownhost(hostname, hostip, ghosthandling);
+		if (hostname == NULL) continue;
+
+		/* Ignore the "info" and "trends" data, since we generate on the fly now. */
+		if (strcmp(testname, xgetenv("INFOCOLUMN")) == 0) continue;
+		if (strcmp(testname, xgetenv("TRENDSCOLUMN")) == 0) continue;
+
+		dbgprintf("Status: Host=%s, test=%s\n", hostname, testname); count++;
+
+		hosthandle = rbtFind(rbhosts, hostname);
+		if (hosthandle == rbtEnd(rbhosts)) {
+			/* New host */
+			hitem = create_hostlist_t(hostname, hostip);
+			hostcount++;
+		}
+		else {
+			hitem = gettreeitem(rbhosts, hosthandle);
+		}
+
+		testhandle = rbtFind(rbtests, testname);
+		if (testhandle == rbtEnd(rbtests)) {
+			t = create_testinfo(testname);
+		}
+		else t = gettreeitem(rbtests, testhandle);
+
+		originhandle = rbtFind(rborigins, originname);
+		if (originhandle == rbtEnd(rborigins)) {
+			origin = strdup(originname);
+			rbtInsert(rborigins, origin, origin);
+		}
+		else origin = gettreeitem(rborigins, originhandle);
+
+		if (hitem->logs == NULL) {
+			ltail = hitem->logs = (hobbitd_log_t *) calloc(1, sizeof(hobbitd_log_t));
+		}
+		else {
+			ltail->next = (hobbitd_log_t *)calloc(1, sizeof(hobbitd_log_t));
+			ltail = ltail->next;
+		}
+
+		if (strcmp(testname, xgetenv("PINGCOLUMN")) == 0) hitem->pinglog = ltail;
+
+		/* Fixup validtime in case of ack'ed or disabled tests */
+		if (validtime < acktime) validtime = acktime;
+		if (validtime < enabletime) validtime = enabletime;
+
+		ltail->test = t;
+		ltail->host = hitem;
+		ltail->origin = origin;
+		ltail->color = color;
+		ltail->oldcolor = oldcolor;
+		ltail->activealert = (decide_alertstate(color) == A_ALERT);
+		ltail->histsynced = 0;
+		ltail->testflags = ( (testflags && strlen(testflags)) ? strdup(testflags) : NULL);
+		strcpy(ltail->sender, sender);
+		ltail->logtime = logtime;
+		ltail->lastchange = lastchange;
+		ltail->validtime = validtime;
+		ltail->enabletime = enabletime;
+		if (ltail->enabletime == DISABLED_UNTIL_OK) ltail->validtime = INT_MAX;
+		ltail->acktime = acktime;
+		nldecode(statusmsg);
+		ltail->message = strdup(statusmsg);
+		ltail->msgsz = strlen(statusmsg)+1;
+		if (disablemsg && strlen(disablemsg)) {
+			nldecode(disablemsg);
+			ltail->dismsg = strdup(disablemsg);
+		}
+		else 
+			ltail->dismsg = NULL;
+		if (ackmsg && strlen(ackmsg)) {
+			nldecode(ackmsg);
+			ltail->ackmsg = strdup(ackmsg);
+		}
+		else 
+			ltail->ackmsg = NULL;
+		ltail->cookie = cookie;
+		if (cookie > 0) rbtInsert(rbcookies, (void *)cookie, ltail);
+		ltail->cookieexpires = cookieexpires;
+		ltail->metas = NULL;
+		ltail->acklist = NULL;
+		ltail->next = NULL;
+	}
+
+	fclose(fd);
+	freestrbuffer(inbuf);
+	dbgprintf("Loaded %d status logs\n", count);
+
+	MEMDEFINE(hostip);
+}
+
+
+void check_purple_status(void)
+{
+	RbtIterator hosthandle;
+	hobbitd_hostlist_t *hwalk;
+	hobbitd_log_t *lwalk;
+	time_t now = time(NULL);
+
+	dbgprintf("-> check_purple_status\n");
+	for (hosthandle = rbtBegin(rbhosts); (hosthandle != rbtEnd(rbhosts)); hosthandle = rbtNext(rbhosts, hosthandle)) {
+		hwalk = gettreeitem(rbhosts, hosthandle);
+
+		lwalk = hwalk->logs;
+		while (lwalk) {
+			if (lwalk->validtime < now) {
+				dbgprintf("Purple log from %s %s\n", hwalk->hostname, lwalk->test->name);
+				if (hwalk->hosttype == H_SUMMARY) {
+					/*
+					 * A summary has gone stale. Drop it.
+					 */
+					hobbitd_log_t *tmp;
+
+					if (lwalk == hwalk->logs) {
+						tmp = hwalk->logs;
+						hwalk->logs = lwalk->next;
+						lwalk = lwalk->next;
+					}
+					else {
+						for (tmp = hwalk->logs; (tmp->next != lwalk); tmp = tmp->next);
+						tmp->next = lwalk->next;
+						tmp = lwalk;
+						lwalk = lwalk->next;
+					}
+					free_log_t(tmp);
+				}
+				else {
+					int newcolor = COL_PURPLE;
+
+					/*
+					 * See if this is a host where the "conn" test shows it is down.
+					 * If yes, then go CLEAR, instead of PURPLE.
+					 */
+					if (hwalk->pinglog) {
+						switch (hwalk->pinglog->color) {
+						  case COL_RED:
+						  case COL_YELLOW:
+						  case COL_BLUE:
+						  case COL_CLEAR: /* if the "route:" tag is involved */
+							newcolor = COL_CLEAR;
+							break;
+
+						  default:
+							newcolor = COL_PURPLE;
+							break;
+						}
+					}
+
+					/* Tests on dialup hosts go clear, not purple */
+					if (newcolor == COL_PURPLE) {
+						namelist_t *hinfo = hostinfo(hwalk->hostname);
+						if (hinfo && bbh_item(hinfo, BBH_FLAG_DIALUP)) newcolor = COL_CLEAR;
+					}
+
+					handle_status(lwalk->message, "hobbitd", 
+						hwalk->hostname, lwalk->test->name, lwalk->grouplist, lwalk, newcolor, NULL);
+					lwalk = lwalk->next;
+				}
+			}
+			else {
+				lwalk = lwalk->next;
+			}
+		}
+	}
+	dbgprintf("<- check_purple_status\n");
+}
+
+void sig_handler(int signum)
+{
+	switch (signum) {
+	  case SIGCHLD:
+		break;
+
+	  case SIGALRM:
+		gotalarm = 1;
+		break;
+
+	  case SIGTERM:
+	  case SIGINT:
+		running = 0;
+		break;
+
+	  case SIGHUP:
+		reloadconfig = 1;
+		dologswitch = 1;
+		break;
+
+	  case SIGUSR1:
+		nextcheckpoint = 0;
+		break;
+	}
+}
+
+
+int main(int argc, char *argv[])
+{
+	conn_t *connhead = NULL, *conntail=NULL;
+	char *listenip = "0.0.0.0";
+	int listenport = 0;
+	char *bbhostsfn = NULL;
+	char *restartfn = NULL;
+	char *logfn = NULL;
+	int checkpointinterval = 900;
+	int do_purples = 1;
+	time_t nextpurpleupdate;
+	struct sockaddr_in laddr;
+	int lsocket, opt;
+	int listenq = 512;
+	int argi;
+	struct timeval tv;
+	struct timezone tz;
+	int daemonize = 0;
+	char *pidfile = NULL;
+	struct sigaction sa;
+	time_t conn_timeout = 30;
+	char *envarea = NULL;
+
+	MEMDEFINE(colnames);
+
+	boottime = time(NULL);
+
+	/* Create our trees */
+	rbhosts = rbtNew(name_compare);
+	rbtests = rbtNew(name_compare);
+	rborigins = rbtNew(name_compare);
+	rbcookies = rbtNew(int_compare);
+	rbfilecache = rbtNew(name_compare);
+	rbghosts = rbtNew(name_compare);
+
+	/* For wildcard notify's */
+	create_testinfo("*");
+
+	colnames[COL_GREEN] = "green";
+	colnames[COL_YELLOW] = "yellow";
+	colnames[COL_RED] = "red";
+	colnames[COL_CLEAR] = "clear";
+	colnames[COL_BLUE] = "blue";
+	colnames[COL_PURPLE] = "purple";
+	colnames[NO_COLOR] = "none";
+	gettimeofday(&tv, &tz);
+	srandom(tv.tv_usec);
+
+	/* Load alert config */
+	alertcolors = colorset(xgetenv("ALERTCOLORS"), ((1 << COL_GREEN) | (1 << COL_BLUE)));
+	okcolors = colorset(xgetenv("OKCOLORS"), (1 << COL_RED));
+
+	for (argi=1; (argi < argc); argi++) {
+		if (argnmatch(argv[argi], "--debug")) {
+			debug = 1;
+		}
+		else if (argnmatch(argv[argi], "--listen=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+
+			listenip = strdup(p);
+			p = strchr(listenip, ':');
+			if (p) {
+				*p = '\0';
+				listenport = atoi(p+1);
+			}
+		}
+		else if (argnmatch(argv[argi], "--timeout=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+			int newconn_timeout = atoi(p);
+			if ((newconn_timeout < 5) || (newconn_timeout > 60)) 
+				errprintf("--timeout must be between 5 and 60\n");
+			else
+				conn_timeout = newconn_timeout;
+		}
+		else if (argnmatch(argv[argi], "--bbhosts=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+			bbhostsfn = strdup(p);
+		}
+		else if (argnmatch(argv[argi], "--checkpoint-file=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+			checkpointfn = strdup(p);
+		}
+		else if (argnmatch(argv[argi], "--checkpoint-interval=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+			checkpointinterval = atoi(p);
+		}
+		else if (argnmatch(argv[argi], "--restart=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+			restartfn = strdup(p);
+		}
+		else if (argnmatch(argv[argi], "--ghosts=")) {
+			char *p = strchr(argv[argi], '=') + 1;
+
+			if (strcmp(p, "allow") == 0) ghosthandling = 0;
+			else if (strcmp(p, "drop") == 0) ghosthandling = 1;
+			else if (strcmp(p, "log") == 0) ghosthandling = 2;
+		}
+		else if (argnmatch(argv[argi], "--no-purple")) {
+			do_purples = 0;
+		}
+		else if (argnmatch(argv[argi], "--daemon")) {
+			daemonize = 1;
+		}
+		else if (argnmatch(argv[argi], "--no-daemon")) {
+			daemonize = 0;
+		}
+		else if (argnmatch(argv[argi], "--pidfile=")) {
+			char *p = strchr(argv[argi], '=');
+			pidfile = strdup(p+1);
+		}
+		else if (argnmatch(argv[argi], "--log=")) {
+			char *p = strchr(argv[argi], '=');
+			logfn = strdup(p+1);
+		}
+		else if (argnmatch(argv[argi], "--ack-log=")) {
+			char *p = strchr(argv[argi], '=');
+			ackinfologfn = strdup(p+1);
+		}
+		else if (argnmatch(argv[argi], "--maint-senders=")) {
+			/* Who is allowed to send us "enable", "disable", "ack", "notes" messages */
+			char *p = strchr(argv[argi], '=');
+			maintsenders = getsenderlist(p+1);
+		}
+		else if (argnmatch(argv[argi], "--status-senders=")) {
+			/* Who is allowed to send us "status", "combo", "summary", "data" messages */
+			char *p = strchr(argv[argi], '=');
+			statussenders = getsenderlist(p+1);
+		}
+		else if (argnmatch(argv[argi], "--admin-senders=")) {
+			/* Who is allowed to send us "drop", "rename", "config", "query" messages */
+			char *p = strchr(argv[argi], '=');
+			adminsenders = getsenderlist(p+1);
+		}
+		else if (argnmatch(argv[argi], "--www-senders=")) {
+			/* Who is allowed to send us "hobbitdboard", "hobbitdlog"  messages */
+			char *p = strchr(argv[argi], '=');
+			wwwsenders = getsenderlist(p+1);
+		}
+		else if (argnmatch(argv[argi], "--dbghost=")) {
+			char *p = strchr(argv[argi], '=');
+
+			dbghost = strdup(p+1);
+		}
+		else if (argnmatch(argv[argi], "--env=")) {
+			char *p = strchr(argv[argi], '=');
+			loadenv(p+1, envarea);
+		}
+		else if (argnmatch(argv[argi], "--area=")) {
+			char *p = strchr(argv[argi], '=');
+			envarea = strdup(p+1);
+		}
+		else if (argnmatch(argv[argi], "--trace=")) {
+			char *p = strchr(argv[argi], '=');
+			tracelist = getsenderlist(p+1);
+		}
+		else if (strcmp(argv[argi], "--trace-all") == 0) {
+			traceall = 1;
+		}
+		else if (strcmp(argv[argi], "--ignore-traced") == 0) {
+			ignoretraced = 1;
+		}
+		else if (strcmp(argv[argi], "--no-clientlog") == 0) {
+			 clientsavemem = 0;
+			 clientsavedisk = 0;
+		}
+		else if (argnmatch(argv[argi], "--store-clientlogs")) {
+			int anypos = 0, anyneg = 0;
+			char *val = strchr(argv[argi], '=');
+
+			if (!val) {
+				clientsavedisk = 1;
+			}
+			else {
+				char *tok;
+
+				val = strdup(val+1);
+				tok = strtok(val, ",");
+				while (tok) {
+					testinfo_t *t;
+					
+					if (*tok == '!') {
+						anyneg = 1;
+						t = create_testinfo(tok+1);
+						t->clientsave = 0;
+					}
+					else {
+						anypos = 1;
+						t = create_testinfo(tok);
+						t->clientsave = 1;
+					}
+
+					tok = strtok(NULL, ",");
+				}
+				xfree(val);
+
+				/* 
+				 * If only positive testnames listed, let default
+				 * be NOT to save, and vice versa.  If mixed, 
+				 * warn about it.
+				 */
+				if (anypos && !anyneg) clientsavedisk = 0;
+				else if (anyneg && !anypos) clientsavedisk = 1;
+				else {
+					errprintf("Mixed list of testnames for --store-clientlogs option, will only save listed tests.\n");
+					clientsavedisk = 0;
+				}
+
+			}
+
+			if (anypos || clientsavedisk) clientsavemem = 1;
+		}
+		else if (strcmp(argv[argi], "--no-download") == 0) {
+			 allow_downloads = 0;
+		}
+		else if (argnmatch(argv[argi], "--help")) {
+			printf("Options:\n");
+			printf("\t--listen=IP:PORT              : The address the daemon listens on\n");
+			printf("\t--bbhosts=FILENAME            : The bb-hosts file\n");
+			printf("\t--ghosts=allow|drop|log       : How to handle unknown hosts\n");
+			return 1;
+		}
+		else {
+			errprintf("Unknown option '%s' - ignored\n", argv[argi]);
+		}
+	}
+
+	if (xgetenv("BBHOSTS") && (bbhostsfn == NULL)) {
+		bbhostsfn = strdup(xgetenv("BBHOSTS"));
+	}
+
+	if (listenport == 0) {
+		if (xgetenv("BBPORT"))
+			listenport = atoi(xgetenv("BBPORT"));
+		else
+			listenport = 1984;
+	}
+
+	if (ghosthandling == -1) {
+		if (xgetenv("BBGHOSTS")) ghosthandling = atoi(xgetenv("BBGHOSTS"));
+		else ghosthandling = 0;
+	}
+
+	if (ghosthandling && (bbhostsfn == NULL)) {
+		errprintf("No bb-hosts file specified, required when using ghosthandling\n");
+		exit(1);
+	}
+
+	errprintf("Loading hostnames\n");
+	load_hostnames(bbhostsfn, NULL, get_fqdn());
+	load_clientconfig();
+
+	if (restartfn) {
+		errprintf("Loading saved state\n");
+		load_checkpoint(restartfn);
+	}
+
+	nextcheckpoint = time(NULL) + checkpointinterval;
+	nextpurpleupdate = time(NULL) + 600;	/* Wait 10 minutes the first time */
+	last_stats_time = time(NULL);	/* delay sending of the first status report until we're fully running */
+
+
+	/* Set up a socket to listen for new connections */
+	errprintf("Setting up network listener on %s:%d\n", listenip, listenport);
+	memset(&laddr, 0, sizeof(laddr));
+	inet_aton(listenip, (struct in_addr *) &laddr.sin_addr.s_addr);
+	laddr.sin_port = htons(listenport);
+	laddr.sin_family = AF_INET;
+	lsocket = socket(AF_INET, SOCK_STREAM, 0);
+	if (lsocket == -1) {
+		errprintf("Cannot create listen socket (%s)\n", strerror(errno));
+		return 1;
+	}
+	opt = 1;
+	setsockopt(lsocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+	fcntl(lsocket, F_SETFL, O_NONBLOCK);
+	if (bind(lsocket, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) {
+		errprintf("Cannot bind to listen socket (%s)\n", strerror(errno));
+		return 1;
+	}
+	if (listen(lsocket, listenq) == -1) {
+		errprintf("Cannot listen (%s)\n", strerror(errno));
+		return 1;
+	}
+
+	/* Go daemon */
+	if (daemonize) {
+		pid_t childpid;
+
+		/* Become a daemon */
+		childpid = fork();
+		if (childpid < 0) {
+			/* Fork failed */
+			errprintf("Could not fork\n");
+			exit(1);
+		}
+		else if (childpid > 0) {
+			/* Parent just exits */
+			exit(0);
+		}
+
+		/* Child (daemon) continues here */
+		setsid();
+	}
+
+	if (pidfile == NULL) {
+		/* Setup a default pid-file */
+		char fn[PATH_MAX];
+
+		sprintf(fn, "%s/hobbitd.pid", xgetenv("BBSERVERLOGS"));
+		pidfile = strdup(fn);
+	}
+
+	/* Save PID */
+	{
+		FILE *fd = fopen(pidfile, "w");
+		if (fd) {
+			if (fprintf(fd, "%d\n", (int)getpid()) <= 0) {
+				errprintf("Error writing PID file %s: %s\n", pidfile, strerror(errno));
+			}
+			fclose(fd);
+		}
+		else {
+			errprintf("Cannot open PID file %s: %s\n", pidfile, strerror(errno));
+		}
+	}
+
+	errprintf("Setting up signal handlers\n");
+	setup_signalhandler("hobbitd");
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = sig_handler;
+	sigaction(SIGINT, &sa, NULL);
+	sigaction(SIGTERM, &sa, NULL);
+	sigaction(SIGUSR1, &sa, NULL);
+	sigaction(SIGHUP, &sa, NULL);
+	sigaction(SIGCHLD, &sa, NULL);
+	sigaction(SIGALRM, &sa, NULL);
+
+	errprintf("Setting up hobbitd channels\n");
+	statuschn = setup_channel(C_STATUS, CHAN_MASTER);
+	if (statuschn == NULL) { errprintf("Cannot setup status channel\n"); return 1; }
+	stachgchn = setup_channel(C_STACHG, CHAN_MASTER);
+	if (stachgchn == NULL) { errprintf("Cannot setup stachg channel\n"); return 1; }
+	pagechn   = setup_channel(C_PAGE, CHAN_MASTER);
+	if (pagechn == NULL) { errprintf("Cannot setup page channel\n"); return 1; }
+	datachn   = setup_channel(C_DATA, CHAN_MASTER);
+	if (datachn == NULL) { errprintf("Cannot setup data channel\n"); return 1; }
+	noteschn  = setup_channel(C_NOTES, CHAN_MASTER);
+	if (noteschn == NULL) { errprintf("Cannot setup notes channel\n"); return 1; }
+	enadischn  = setup_channel(C_ENADIS, CHAN_MASTER);
+	if (enadischn == NULL) { errprintf("Cannot setup enadis channel\n"); return 1; }
+	clientchn  = setup_channel(C_CLIENT, CHAN_MASTER);
+	if (clientchn == NULL) { errprintf("Cannot setup client channel\n"); return 1; }
+	clichgchn  = setup_channel(C_CLICHG, CHAN_MASTER);
+	if (clichgchn == NULL) { errprintf("Cannot setup clichg channel\n"); return 1; }
+	userchn  = setup_channel(C_USER, CHAN_MASTER);
+	if (userchn == NULL) { errprintf("Cannot setup user channel\n"); return 1; }
+
+	errprintf("Setting up logfiles\n");
+	setvbuf(stdout, NULL, _IONBF, 0);
+	setvbuf(stderr, NULL, _IONBF, 0);
+	freopen("/dev/null", "r", stdin);
+	if (logfn) {
+		freopen(logfn, "a", stdout);
+		freopen(logfn, "a", stderr);
+	}
+
+	if (ackinfologfn) {
+		ackinfologfd = fopen(ackinfologfn, "a");
+		if (ackinfologfd == NULL) {
+			errprintf("Cannot open ack logfile %s: %s\n", ackinfologfn, strerror(errno));
+		}
+	}
+
+	if (dbghost) {
+		char fname[PATH_MAX];
+
+		sprintf(fname, "%s/hobbitd.dbg", xgetenv("BBTMP"));
+		dbgfd = fopen(fname, "a");
+		if (dbgfd == NULL) errprintf("Cannot open debug file %s: %s\n", fname, strerror(errno));
+	}
+
+	errprintf("Setup complete\n");
+	do {
+		/*
+		 * The endless loop.
+		 *
+		 * First attend to the housekeeping chores:
+		 * - send out our heartbeat signal;
+		 * - pick up children to avoid zombies;
+		 * - rotate logs, if we have been asked to;
+		 * - re-load the bb-hosts configuration if needed;
+		 * - check for stale status-logs that must go purple;
+		 * - inject our own statistics message.
+		 * - save the checkpoint file;
+		 *
+		 * Then do the network I/O.
+		 */
+		struct timeval seltmo;
+		fd_set fdread, fdwrite;
+		int maxfd, n;
+		conn_t *cwalk;
+		time_t now = time(NULL);
+		int childstat;
+
+		/* Pickup any finished child processes to avoid zombies */
+		while (wait3(&childstat, WNOHANG, NULL) > 0) ;
+
+		if (logfn && dologswitch) {
+			freopen(logfn, "a", stdout);
+			freopen(logfn, "a", stderr);
+			if (ackinfologfd) freopen(ackinfologfn, "a", ackinfologfd);
+			dologswitch = 0;
+			posttochannel(statuschn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(stachgchn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(pagechn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(datachn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(noteschn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(enadischn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+			posttochannel(clientchn, "logrotate", NULL, "hobbitd", NULL, NULL, "");
+		}
+
+		if (reloadconfig && bbhostsfn) {
+			RbtIterator hosthandle;
+
+			reloadconfig = 0;
+			load_hostnames(bbhostsfn, NULL, get_fqdn());
+
+			/* Scan our list of hosts and weed out those we do not know about any more */
+			hosthandle = rbtBegin(rbhosts);
+			while (hosthandle != rbtEnd(rbhosts)) {
+				hobbitd_hostlist_t *hwalk;
+
+				hwalk = gettreeitem(rbhosts, hosthandle);
+
+				if (hwalk->hosttype == H_SUMMARY) {
+					/* Leave the summaries as-is */
+					hosthandle = rbtNext(rbhosts, hosthandle);
+				}
+				else if (hostinfo(hwalk->hostname) == NULL) {
+					/* Remove all state info about this host. This will NOT remove files. */
+					handle_dropnrename(CMD_DROPSTATE, "hobbitd", hwalk->hostname, NULL, NULL);
+
+					/* Must restart tree-walk after deleting node from the tree */
+					hosthandle = rbtBegin(rbhosts);
+				}
+				else {
+					hosthandle = rbtNext(rbhosts, hosthandle);
+				}
+			}
+
+			load_clientconfig();
+		}
+
+		if (do_purples && (now > nextpurpleupdate)) {
+			nextpurpleupdate = time(NULL) + 60;
+			check_purple_status();
+		}
+
+		if ((last_stats_time + 300) <= now) {
+			char *buf;
+			hobbitd_hostlist_t *h;
+			testinfo_t *t;
+			hobbitd_log_t *log;
+			int color;
+
+			buf = generate_stats();
+			get_hts(buf, "hobbitd", "", &h, &t, NULL, &log, &color, NULL, NULL, 1, 1);
+			if (!h || !t || !log) {
+				errprintf("hobbitd servername MACHINE='%s' not listed in bb-hosts, dropping hobbitd status\n",
+					  xgetenv("MACHINE"));
+			}
+			else {
+				handle_status(buf, "hobbitd", h->hostname, t->name, NULL, log, color, NULL);
+			}
+			last_stats_time = now;
+			flush_errbuf();
+		}
+
+		if (now > nextcheckpoint) {
+			pid_t childpid;
+
+			reloadconfig = 1;
+			nextcheckpoint = now + checkpointinterval;
+			childpid = fork();
+			if (childpid == -1) {
+				errprintf("Could not fork checkpoint child:%s\n", strerror(errno));
+			}
+			else if (childpid == 0) {
+				save_checkpoint();
+				exit(0);
+			}
+		}
+
+		/*
+		 * Prepare for the network I/O.
+		 * Find the largest open socket we have, from our active sockets,
+		 * and setup the select() FD sets.
+		 */
+		FD_ZERO(&fdread); FD_ZERO(&fdwrite);
+		FD_SET(lsocket, &fdread); maxfd = lsocket;
+
+		for (cwalk = connhead; (cwalk); cwalk = cwalk->next) {
+			switch (cwalk->doingwhat) {
+				case RECEIVING:
+					FD_SET(cwalk->sock, &fdread);
+					if (cwalk->sock > maxfd) maxfd = cwalk->sock;
+					break;
+				case RESPONDING:
+					FD_SET(cwalk->sock, &fdwrite);
+					if (cwalk->sock > maxfd) maxfd = cwalk->sock;
+					break;
+			}
+		}
+
+		/* 
+		 * Do the select() with a static 2 second timeout. 
+		 * This is long enough that we will suspend activity for
+		 * some time if there's nothing to do, but short enough for
+		 * us to attend to the housekeeping stuff without undue delay.
+		 */
+		seltmo.tv_sec = 2; seltmo.tv_usec = 0;
+		n = select(maxfd+1, &fdread, &fdwrite, NULL, &seltmo);
+		if (n <= 0) {
+			if ((errno == EINTR) || (n == 0)) {
+				/* Interrupted or a timeout happened */
+				continue;
+			}
+			else {
+				errprintf("Fatal error in select: %s\n", strerror(errno));
+				break;
+			}
+		}
+
+		/*
+		 * Now do the actual data exchange over the net.
+		 */
+		for (cwalk = connhead; (cwalk); cwalk = cwalk->next) {
+			switch (cwalk->doingwhat) {
+			  case RECEIVING:
+				if (FD_ISSET(cwalk->sock, &fdread)) {
+					n = read(cwalk->sock, cwalk->bufp, (cwalk->bufsz - cwalk->buflen - 1));
+					if (n <= 0) {
+						/* End of input data on this connection */
+						*(cwalk->bufp) = '\0';
+
+						/* FIXME - need to set origin here */
+						do_message(cwalk, "");
+					}
+					else {
+						/* Add data to the input buffer - within reason ... */
+						cwalk->bufp += n;
+						cwalk->buflen += n;
+						*(cwalk->bufp) = '\0';
+						if ((cwalk->bufsz - cwalk->buflen) < 2048) {
+							if (cwalk->bufsz < MAX_HOBBIT_INBUFSZ) {
+								cwalk->bufsz += HOBBIT_INBUF_INCREMENT;
+								cwalk->buf = (unsigned char *) realloc(cwalk->buf, cwalk->bufsz);
+								cwalk->bufp = cwalk->buf + cwalk->buflen;
+							}
+							else {
+								/* Someone is flooding us */
+								errprintf("Data flooding from %s, closing connection\n",
+									  inet_ntoa(cwalk->addr.sin_addr));
+								shutdown(cwalk->sock, SHUT_RDWR);
+								close(cwalk->sock); 
+								cwalk->sock = -1; 
+								cwalk->doingwhat = NOTALK;
+							}
+						}
+					}
+				}
+				break;
+
+			  case RESPONDING:
+				if (FD_ISSET(cwalk->sock, &fdwrite)) {
+					n = write(cwalk->sock, cwalk->bufp, cwalk->buflen);
+
+					if (n < 0) {
+						cwalk->buflen = 0;
+					}
+					else {
+						cwalk->bufp += n;
+						cwalk->buflen -= n;
+					}
+
+					if (cwalk->buflen == 0) {
+						shutdown(cwalk->sock, SHUT_WR);
+						close(cwalk->sock); 
+						cwalk->sock = -1; 
+						cwalk->doingwhat = NOTALK;
+					}
+				}
+				break;
+			}
+		}
+
+		/* Any scheduled tasks that need attending to? */
+		{
+			scheduletask_t *swalk, *sprev;
+
+			swalk = schedulehead; sprev = NULL;
+			while (swalk) {
+				if (swalk->executiontime <= now) {
+					scheduletask_t *runtask = swalk;
+					conn_t task;
+
+					/* Unlink the entry */
+					if (sprev == NULL) 
+						schedulehead = swalk->next;
+					else
+						sprev->next = swalk->next;
+					swalk = swalk->next;
+
+					memset(&task, 0, sizeof(task));
+					inet_aton(runtask->sender, (struct in_addr *) &task.addr.sin_addr.s_addr);
+					task.buf = task.bufp = runtask->command;
+					task.buflen = strlen(runtask->command); task.bufsz = task.buflen+1;
+					do_message(&task, "");
+
+					errprintf("Ran scheduled task %d from %s: %s\n", 
+						  runtask->id, runtask->sender, runtask->command);
+					xfree(runtask->sender); xfree(runtask->command); xfree(runtask);
+				}
+				else {
+					sprev = swalk;
+					swalk = swalk->next;
+				}
+			}
+		}
+
+		/* Clean up conn structs that are no longer used */
+		{
+			conn_t *tmp, *khead;
+
+			now = time(NULL);
+			khead = NULL; cwalk = connhead;
+			while (cwalk) {
+				/* Check for connections that timeout */
+				if (now > cwalk->timeout) {
+					update_statistics("");
+					cwalk->doingwhat = NOTALK;
+					if (cwalk->sock >= 0) {
+						shutdown(cwalk->sock, SHUT_RDWR);
+						close(cwalk->sock);
+						cwalk->sock = -1;
+					}
+				}
+
+				/* Move dead connections to a purge-list */
+				if ((cwalk == connhead) && (cwalk->doingwhat == NOTALK)) {
+					/* head of chain is dead */
+					tmp = connhead;
+					connhead = connhead->next;
+					tmp->next = khead;
+					khead = tmp;
+
+					cwalk = connhead;
+				}
+				else if (cwalk->next && (cwalk->next->doingwhat == NOTALK)) {
+					tmp = cwalk->next;
+					cwalk->next = tmp->next;
+					tmp->next = khead;
+					khead = tmp;
+
+					/* cwalk is unchanged */
+				}
+				else {
+					cwalk = cwalk->next;
+				}
+			}
+			if (connhead == NULL) {
+				conntail = NULL;
+			}
+			else {
+				conntail = connhead;
+				cwalk = connhead->next;
+				if (cwalk) {
+					while (cwalk->next) cwalk = cwalk->next;
+					conntail = cwalk;
+				}
+			}
+
+			/* Purge the dead connections */
+			while (khead) {
+				tmp = khead;
+				khead = khead->next;
+
+				if (tmp->buf) xfree(tmp->buf);
+				xfree(tmp);
+			}
+		}
+
+		/* Pick up new connections */
+		if (FD_ISSET(lsocket, &fdread)) {
+			struct sockaddr_in addr;
+			int addrsz = sizeof(addr);
+			int sock = accept(lsocket, (struct sockaddr *)&addr, &addrsz);
+
+			if (sock >= 0) {
+				if (connhead == NULL) {
+					connhead = conntail = (conn_t *)malloc(sizeof(conn_t));
+				}
+				else {
+					conntail->next = (conn_t *)malloc(sizeof(conn_t));
+					conntail = conntail->next;
+				}
+
+				conntail->sock = sock;
+				memcpy(&conntail->addr, &addr, sizeof(conntail->addr));
+				conntail->doingwhat = RECEIVING;
+				conntail->bufsz = HOBBIT_INBUF_INITIAL;
+				conntail->buf = (unsigned char *)malloc(conntail->bufsz);
+				conntail->bufp = conntail->buf;
+				conntail->buflen = 0;
+				conntail->timeout = now + conn_timeout;
+				conntail->next = NULL;
+			}
+		}
+	} while (running);
+
+	/* Tell the workers we to shutdown also */
+	running = 1;   /* Kludge, but it's the only way to get posttochannel to do something. */
+	posttochannel(statuschn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(stachgchn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(pagechn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(datachn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(noteschn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(enadischn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	posttochannel(clientchn, "shutdown", NULL, "hobbitd", NULL, NULL, "");
+	running = 0;
+
+	/* Close the channels */
+	close_channel(statuschn, CHAN_MASTER);
+	close_channel(stachgchn, CHAN_MASTER);
+	close_channel(pagechn, CHAN_MASTER);
+	close_channel(datachn, CHAN_MASTER);
+	close_channel(noteschn, CHAN_MASTER);
+	close_channel(enadischn, CHAN_MASTER);
+	close_channel(clientchn, CHAN_MASTER);
+	close_channel(clichgchn, CHAN_MASTER);
+
+	save_checkpoint();
+	unlink(pidfile);
+
+	if (dbgfd) fclose(dbgfd);
+
+	MEMUNDEFINE(colnames);
+
+	return 0;
+}
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_alert.c ./hobbitd/hobbitd_alert.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_alert.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd_alert.c	2006-10-03 12:55:27.000000000 +0200
@@ -124,6 +124,9 @@
 		curr = curr->next;
 
 		if (tmp->state == A_DEAD) {
+			if (tmp->osname) xfree(tmp->osname);
+			if (tmp->classname) xfree(tmp->classname);
+			if (tmp->groups) xfree(tmp->groups);
 			if (tmp->pagemessage) xfree(tmp->pagemessage);
 			if (tmp->ackmessage) xfree(tmp->ackmessage);
 			xfree(tmp);
@@ -285,7 +288,8 @@
 		}
 
 		if (i > 9) {
-			char *key, *valid = NULL;
+			char *valid = NULL;
+
 			activealerts_t *newalert = (activealerts_t *)calloc(1, sizeof(activealerts_t));
 			newalert->hostname = find_name(hostnames, item[0]);
 			newalert->testname = find_name(testnames, item[1]);
@@ -297,10 +301,13 @@
 			newalert->state = A_PAGING;
 
 			if (statusbuf) {
+				char *key;
+
 				key = (char *)malloc(strlen(newalert->hostname) + strlen(newalert->testname) + 100);
 				sprintf(key, "\n%s|%s|%s\n", newalert->hostname, newalert->testname, colorname(newalert->color));
 				valid = strstr(statusbuf, key);
 				if (!valid && (strncmp(statusbuf, key+1, strlen(key+1)) == 0)) valid = statusbuf;
+				xfree(key);
 			}
 			if (!valid) {
 				errprintf("Stale alert for %s:%s dropped\n", newalert->hostname, newalert->testname);
@@ -648,10 +655,12 @@
 
 			strcpy(awalk->ip, metadata[5]);
 			awalk->cookie = atoi(metadata[11]);
+			if (awalk->osname) xfree(awalk->osname);
 			awalk->osname    = (metadata[12] ? strdup(metadata[12]) : NULL);
+			if (awalk->classname) xfree(awalk->classname);
 			awalk->classname = (metadata[13] ? strdup(metadata[13]) : NULL);
+			if (awalk->groups) xfree(awalk->groups);
 			awalk->groups    = (metadata[14] ? strdup(metadata[14]) : NULL);
-
 			if (awalk->pagemessage) xfree(awalk->pagemessage);
 			awalk->pagemessage = strdup(restofmsg);
 		}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_buffer.c ./hobbitd/hobbitd_buffer.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_buffer.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd_buffer.c	2007-02-09 11:27:54.461530794 +0100
@@ -34,6 +34,7 @@
 		  case C_STACHG: v = getenv("MAXMSG_STACHG"); defvalue = shbufsz(C_STATUS); break;
 		  case C_PAGE:   v = getenv("MAXMSG_PAGE");   defvalue = shbufsz(C_STATUS); break;
 		  case C_ENADIS: v = getenv("MAXMSG_ENADIS"); defvalue =  32; break;
+		  case C_USER:   v = getenv("MAXMSG_USER");   defvalue = 128; break;
 		  default: break;
 		}
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_buffer.h ./hobbitd/hobbitd_buffer.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_buffer.h	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd_buffer.h	2007-02-09 11:27:54.469529339 +0100
@@ -11,7 +11,7 @@
 #ifndef __HOBBITD_BUFFER_H__
 #define __HOBBITD_BUFFER_H__
 
-enum msgchannels_t { C_STATUS=1, C_STACHG, C_PAGE, C_DATA, C_NOTES, C_ENADIS, C_CLIENT, C_CLICHG, C_LAST };
+enum msgchannels_t { C_STATUS=1, C_STACHG, C_PAGE, C_DATA, C_NOTES, C_ENADIS, C_CLIENT, C_CLICHG, C_USER, C_LAST };
 
 extern unsigned int shbufsz(enum msgchannels_t chnid);
 #endif
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_client.c ./hobbitd/hobbitd_client.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_client.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd_client.c	2006-10-03 13:52:29.000000000 +0200
@@ -896,7 +896,9 @@
 					swalk->sname+5);
 			}
 			addtobuffer(yellowdata, msgline);
+			addtobuffer(yellowdata, "<pre>\n");
 			addtostrbuffer(yellowdata, logsummary);
+			addtobuffer(yellowdata, "</pre>\n");
 			break;
 
 		  case COL_RED:
@@ -909,7 +911,9 @@
 					swalk->sname+5);
 			}
 			addtobuffer(reddata, msgline);
+			addtobuffer(yellowdata, "<pre>\n");
 			addtostrbuffer(reddata, logsummary);
+			addtobuffer(yellowdata, "</pre>\n");
 			break;
 		}
 
@@ -963,7 +967,9 @@
 				swalk->sname+5);
 		}
 		addtostatus(msgline);
+		addtobuffer(yellowdata, "<pre>\n");
 		addtostatus(swalk->sdata);
+		addtobuffer(yellowdata, "</pre>\n");
 		do { swalk=swalk->next; } while (swalk && strncmp(swalk->sname, "msgs:", 5));
 	}
 
@@ -1490,11 +1496,12 @@
 		else if (strcmp(s, "disk") == 0) {
 			unsigned long warnlevel, paniclevel;
 			int abswarn, abspanic, ignored;
+			char *groups;
 
 			printf("Filesystem: "); fflush(stdout);
 			fgets(s, sizeof(s), stdin); clean_instr(s);
 			cfid = get_disk_thresholds(hinfo, clientclass, s, &warnlevel, &paniclevel, 
-						   &abswarn, &abspanic, &ignored, NULL);
+						   &abswarn, &abspanic, &ignored, &groups);
 			if (ignored) 
 				printf("Ignored\n");
 			else
@@ -1506,6 +1513,7 @@
 			int pchecks = clear_process_counts(hinfo, clientclass);
 			char *pname, *pid;
 			int pcount, pmin, pmax, pcolor, ptrack;
+			char *groups;
 			FILE *fd;
 
 			if (pchecks == 0) {
@@ -1530,7 +1538,7 @@
 				}
 			} while (*s);
 
-			while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, NULL)) != NULL) {
+			while ((pname = check_process_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &groups)) != NULL) {
 				printf("Process %s color %s: Count=%d, min=%d, max=%d\n",
 					pname, colorname(pcolor), pcount, pmin, pmax);
 			}
@@ -1576,6 +1584,7 @@
 		else if (strcmp(s, "port") == 0) {
 			char *localstr, *remotestr, *statestr, *p, *pname, *pid;
 			int pcount, pmin, pmax, pcolor, pchecks, ptrack;
+			char *groups;
 			int localcol = 4, remotecol = 5, statecol = 6, portcolor = COL_GREEN;
 
 			pchecks = clear_port_counts(hinfo, clientclass);
@@ -1619,7 +1628,7 @@
 			} while (*s);
 
 			/* Check the number found for each monitored port */
- 			while ((pname = check_port_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, NULL)) != NULL) {
+ 			while ((pname = check_port_count(&pcount, &pmin, &pmax, &pcolor, &pid, &ptrack, &groups)) != NULL) {
  				char limtxt[1024];
 			
 				if (pmax == -1) {
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_ipc.c ./hobbitd/hobbitd_ipc.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitd_ipc.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitd_ipc.c	2007-02-09 11:27:54.477527885 +0100
@@ -49,6 +49,7 @@
 	"enadis",
 	"client",
 	"clichg",
+	"user",
 	NULL
 };
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitfetch.c ./hobbitd/hobbitfetch.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/hobbitfetch.c	2006-08-09 22:10:05.000000000 +0200
+++ ./hobbitd/hobbitfetch.c	2006-10-03 12:55:27.000000000 +0200
@@ -30,6 +30,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <arpa/nameser.h>
 #include <netdb.h>
 #include <ctype.h>
 #include <signal.h>
@@ -619,14 +620,36 @@
 					ip = strdup(bbh_item(hostwalk, BBH_IP));
 				}
 				else {
+					/* There is an explicit IP setting in the pulldata tag */
 					char *p;
 
 					ip++; /* Skip the '=' */
 					ip = strdup(ip);
 					p = strchr(ip, ':');
 					if (p) { *p = '\0'; port = atoi(p+1); }
+
+					if (*ip == '\0') {
+						/* No IP given, just a port number */
+						xfree(ip);
+						ip = strdup(bbh_item(hostwalk, BBH_IP));
+					}
+				}
+
+				if (strcmp(ip, "0.0.0.0") == 0) {
+					struct hostent *hent;
+
+					xfree(ip); ip = NULL;
+					hent = gethostbyname(clientwalk->hostname);
+					if (hent) {
+						struct in_addr addr;
+
+						memcpy(&addr, *(hent->h_addr_list), sizeof(addr));
+						ip = strdup(inet_ntoa(addr));
+					}
 				}
 
+				if (!ip) continue;
+
 				/* 
 				 * Build the "pullclient" request, which includes the latest
 				 * clientdata config we got from the server. Keep the clientdata
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/rrd/do_ncv.c ./hobbitd/rrd/do_ncv.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/rrd/do_ncv.c	2006-08-09 22:10:06.000000000 +0200
+++ ./hobbitd/rrd/do_ncv.c	2006-10-03 12:55:27.000000000 +0200
@@ -11,7 +11,7 @@
 /*                                                                            */
 /*----------------------------------------------------------------------------*/
 
-static char ncv_rcsid[] = "$Id: do_ncv.c,v 1.10 2006/06/09 22:23:49 henrik Rel $";
+static char ncv_rcsid[] = "$Id: do_ncv.c,v 1.10 2006/06/09 22:23:49 henrik Rel henrik $";
 
 int do_ncv_rrd(char *hostname, char *testname, char *msg, time_t tstamp) 
 { 
@@ -44,11 +44,22 @@
 
 		l += strspn(l, " \t\n");
 		if (*l) { 
+			/* See if this line contains a '=' or ':' sign */
 			name = l; 
-			l += strcspn(l, ":="); 
-			if( *l ) { *l = '\0'; l++; }
-			else break;
+			l += strcspn(l, ":=\n"); 
+
+			if (*l) {
+				if (( *l == '=') || (*l == ':')) { 
+					*l = '\0'; l++;
+				}
+				else {
+					/* No marker, so skip this line */
+					name = NULL;
+				}
+			}
+			else break;	/* We've hit the end of the message */
 		}
+
 		if (name) { 
 			val = l + strspn(l, " \t"); 
 			l = val + strspn(val, "0123456789."); 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/confreport_front ./hobbitd/webfiles/confreport_front
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/confreport_front	2006-08-09 22:10:10.000000000 +0200
+++ ./hobbitd/webfiles/confreport_front	2006-10-03 12:55:27.000000000 +0200
@@ -7,7 +7,6 @@
             <tr><th align=left valign=top width="25%">Aliases</th><td>Names for this host other than the primary name, e.g. a hostname used by a client installed on the server</td></tr>
             <tr><th align=left valign=top width="25%">Monitoring location</th><td>The location of this host on the Hobbit webpages</td></tr>
             <tr><th align=left valign=top width="25%">Comment<br>Description</th><td>Explanatory text about the host</td></tr>
-            <tr><th align=left valign=top width="25%">NK monitoring period</th><td>Time of day/week when the NK-enabled alerts will appear on the NK monitor</td></tr>
             <tr><th align=left valign=top width="25%">Planned downtime</th><td>Time of day/week when the host monitoring is disabled</td></tr>
             <tr><th align=left valign=top width="25%">SLA Reporting Period</th><td>Time of day/week where the status impacts the SLA availability calculation</td></tr>
          </table>
@@ -17,7 +16,7 @@
       <td>
          <table width="100%">
             <tr><th align=left valign=top width="25%">Service</th><td>Corresponds to the column-name on the Hobbit webpage</td></tr>
-            <tr><th align=left valign=top width="25%">NK</th><td>Whether this test appears on the NK view</td></tr>
+            <tr><th align=left valign=top width="25%">Critical</th><td>Whether this test appears on the Critical Systems view</td></tr>
             <tr><th align=left valign=top width="25%">C/Y/R limits</th><td>If set, this is the number of failures that must happen before the status changes to Clear/Yellow/Red</td></tr>
             <tr><th align=left valign=top width="25%">Specifics</th><td>Details about how this status is monitored</td></tr>
          </table>
@@ -27,7 +26,7 @@
       <td>
          <table width="100%">
             <tr><th align=left valign=top width="25%">Service</th><td>Corresponds to the column-name on the Hobbit webpage</td></tr>
-            <tr><th align=left valign=top width="25%">NK</th><td>Whether this test appears on the NK view</td></tr>
+            <tr><th align=left valign=top width="25%">Critical</th><td>Whether this test appears on the Critical view</td></tr>
             <tr><th align=left valign=top width="25%">C/Y/R limits</th><td>If set, this is the number of failures that must happen before the status changes to Clear/Yellow/Red</td></tr>
             <tr><th align=left valign=top width="25%">Configuration</th><td>Details about how this status is monitored. NOTE: The exact thresholds for each test are configured on the client, and may differ from that listed here.</td></tr>
          </table>
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/hobbitnk_footer ./hobbitd/webfiles/hobbitnk_footer
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/hobbitnk_footer	2006-08-09 22:10:10.000000000 +0200
+++ ./hobbitd/webfiles/hobbitnk_footer	2006-10-03 12:55:27.000000000 +0200
@@ -17,6 +17,7 @@
        </TD>
        <TD ALIGN=CENTER>
           <SELECT NAME="MINCOLOR">
+	    <OPTION VALUE="purple" &SELECT_MINCOLOR_PURPLE>Red+Yellow+Purple
 	    <OPTION VALUE="yellow" &SELECT_MINCOLOR_YELLOW>Red+Yellow
 	    <OPTION VALUE="red"    &SELECT_MINCOLOR_RED>Red only
 	  </SELECT>
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/info_header ./hobbitd/webfiles/info_header
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/info_header	2006-08-09 22:10:10.000000000 +0200
+++ ./hobbitd/webfiles/info_header	2006-10-03 13:52:29.000000000 +0200
@@ -20,6 +20,15 @@
         else
                 field.checked = val;
 }
+
+function mark4Disable(services) {
+	for(i=0; i<document.disableform.disabletest.length; i++) {
+		if (services.indexOf(',' + document.disableform.disabletest.options[i].value + ',') > -1) {
+			document.disableform.disabletest.options[i].selected = !document.disableform.disabletest.options[i].selected;
+		}
+	}
+}
+
 </script>
 
 <script language="JavaScript1.2" type="text/javascript">
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_footer ./hobbitd/webfiles/notify_footer
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_footer	1970-01-01 01:00:00.000000000 +0100
+++ ./hobbitd/webfiles/notify_footer	2006-08-09 22:10:10.000000000 +0200
@@ -0,0 +1,24 @@
+<BR><BR>
+
+<TABLE SUMMARY="Bottomline" WIDTH="100%">
+<TR>
+  <TD> <HR WIDTH="100%"> </TD>
+</TR>
+<TR>
+  <TD ALIGN=RIGHT><FONT FACE="Arial, Helvetica" SIZE="-2" COLOR="silver"><B><A HREF="http://hobbitmon.sourceforge.net/" style="text-decoration: none">Hobbit Monitor &HOBBITDREL</A></B></FONT></TD>
+</TR>
+</TABLE>
+
+
+<!-- menu script itself. you should not modify this file -->
+<script type="text/javascript" language="JavaScript" src="&BBMENUSKIN/menu.js"></script>
+<!-- items structure. menu hierarchy and links are stored there -->
+<script type="text/javascript" language="JavaScript" src="&BBMENUSKIN/menu_items.js"></script>
+<!-- files with geometry and styles structures -->
+<script type="text/javascript" language="JavaScript" src="&BBMENUSKIN/menu_tpl.js"></script>
+<script type="text/javascript" language="JavaScript">
+        new menu (MENU_ITEMS, MENU_POS);
+</script>
+
+</BODY>
+</HTML>
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_form ./hobbitd/webfiles/notify_form
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_form	1970-01-01 01:00:00.000000000 +0100
+++ ./hobbitd/webfiles/notify_form	2007-02-09 11:06:47.325879886 +0100
@@ -0,0 +1,102 @@
+<CENTER>
+<FORM METHOD="GET" ACTION="&SCRIPT_NAME">
+   <TABLE BORDER=0 SUMMARY="Notification time period">
+     <TR>
+       <TD><TABLE BORDER=0>
+         <TR>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Starting </B></FONT></TD>
+           <TD><INPUT TYPE=TEXT NAME="MAXTIME" SIZE=40></TD>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B> minutes ago </B>( Default 1440 )</FONT></TD> 
+         </TR>
+         <TR>
+           <TD COLSPAN=3 ALIGN=CENTER><FONT FACE="Arial, Helvetica" COLOR="silver"><B>&nbsp;- OR -&nbsp;</B></FONT></TD>
+         </TR>
+         <TR>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>From: </B></FONT></TD> 
+           <TD><INPUT TYPE=TEXT NAME="FROMTIME" SIZE=40></TD>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver">(ccyy/mm/dd@hh:mm:ss)</FONT></TD>
+         </TR>
+         <TR>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>To: </B></FONT></TD> 
+           <TD><INPUT TYPE=TEXT NAME="TOTIME" SIZE=40></TD>
+           <TD><FONT FACE="Arial, Helvetica" COLOR="silver">(ccyy/mm/dd@hh:mm:ss)</FONT></TD>
+         </TR>
+       </TABLE></TD>
+     </TR>
+   </TABLE>
+
+   <BR><BR>
+
+   <TABLE BORDER=0 SUMMARY="Notification criteria">
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Max # of notifications</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="MAXCOUNT" SIZE=40 VALUE=100></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( Default 100 )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Hosts to match</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="HOSTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: ^host.*$ )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Hosts to skip</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="EXHOSTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: ^host.*$ )</FONT></TD>
+     </TR>
+
+      <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Pages to match</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="PAGEMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: ^webservers/.*$ )</FONT></TD>
+     </TR>
+
+      <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Pages to skip</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="EXPAGEMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: ^webservers/.*$ )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Tests to match</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="TESTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: cpu|vmstat )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Tests to skip</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="EXTESTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: cpu|vmstat )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Recipients to match</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="RCPTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: admin@test.com )</FONT></TD>
+     </TR>
+
+     <TR>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"><B>Recipients to skip</B></FONT></TD>
+       <TD WIDTH=10>&nbsp;</TD>
+       <TD><INPUT TYPE=TEXT NAME="EXRCPTMATCH" SIZE=40></TD>
+       <TD><FONT FACE="Arial, Helvetica" COLOR="silver"> ( ex: admin@test.com )</FONT></TD>
+     </TR>
+
+     <TR>
+        <TD COLSPAN=3 ALIGN=CENTER>
+          <BR><BR>
+          <INPUT TYPE="SUBMIT" NAME="Send" VALUE="View log" ALT="View log">
+        </TD>
+     </TR>
+   </TABLE>
+</FORM>
+</CENTER>
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_header ./hobbitd/webfiles/notify_header
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/notify_header	1970-01-01 01:00:00.000000000 +0100
+++ ./hobbitd/webfiles/notify_header	2007-02-09 11:06:51.215173324 +0100
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+<META HTTP-EQUIV="EXPIRES" CONTENT="Sat, 01 Jan 2001 00:00:00 GMT">
+<TITLE>&BBBACKGROUND : Hobbit - Notification Log @ &BBDATE</TITLE>
+
+<!-- Styles for the menu bar -->
+<link rel="stylesheet" type="text/css" href="&BBMENUSKIN/menu.css">
+
+<!-- The favicon image -->
+<link rel="shortcut icon" href="&BBSKIN/favicon-&BBBACKGROUND.ico">
+
+</HEAD>
+
+<BODY BGCOLOR="&BBBACKGROUND" BACKGROUND="&BBSKIN/bkg-&BBBACKGROUND.gif" TEXT="#D8D8BF" LINK="#00FFAA" VLINK="#FFFF44">
+
+<TABLE SUMMARY="Topline" WIDTH="100%">
+<TR><TD HEIGHT=16>&nbsp;</TD></TR>  <!-- For the menu bar -->
+<TR>
+  <TD VALIGN=MIDDLE ALIGN=LEFT WIDTH="30%">
+    <FONT FACE="Arial, Helvetica" SIZE="+1" COLOR="silver"><B>&HOBBITLOGO</B></FONT>
+  </TD>
+  <TD VALIGN=MIDDLE ALIGN=CENTER WIDTH="40%">
+    <CENTER><FONT FACE="Arial, Helvetica" SIZE="+1" COLOR="silver"><B>Notification Log</B></FONT></CENTER>
+  </TD>
+  <TD VALIGN=MIDDLE ALIGN=RIGHT WIDTH="30%">
+   <FONT FACE="Arial, Helvetica" SIZE="+1" COLOR="silver"><B>&BBDATE</B></FONT>
+  </TD>
+</TR>
+<TR>
+  <TD COLSPAN=3> <HR WIDTH="100%"> </TD>
+</TR>
+</TABLE>
+<BR>
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/zoom.js ./hobbitd/webfiles/zoom.js
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/webfiles/zoom.js	2006-08-09 22:10:10.000000000 +0200
+++ ./hobbitd/webfiles/zoom.js	2006-10-03 12:55:27.000000000 +0200
@@ -601,6 +601,7 @@
   var idxCount =  gUrlObj.getUrlParameterValue("count");
   var graphWidth = gUrlObj.getUrlParameterValue("graph_width");
   var graphHeight = gUrlObj.getUrlParameterValue("graph_height");
+  var bgColor = gUrlObj.getUrlParameterValue("color");
 
   if (firstIdx != "") {
      idxStr = "&first=" + firstIdx;
@@ -609,7 +610,7 @@
      countStr = "&count=" + idxCount; 
   }
 
-  open(urlBase + "&host=" + host + "&service=" + service + "&disp=" + dispName + idxStr + countStr + "&graph_start=" + newGraphStart + "&graph_end=" + newGraphEnd + "&graph_height=" + graphHeight + "&graph_width=" + graphWidth, "_self");
+  open(urlBase + "&host=" + host + "&service=" + service + "&disp=" + dispName + idxStr + countStr + "&graph_start=" + newGraphStart + "&graph_end=" + newGraphEnd + "&graph_height=" + graphHeight + "&graph_width=" + graphWidth + "&color=" + bgColor, "_self");
  }
 
  if ((gMouseObj.leftButtonPressed()) && (gMouseObj.dragging)) {
@@ -655,6 +656,7 @@
    var idxCount =  gUrlObj.getUrlParameterValue("count");
    var graphWidth = gUrlObj.getUrlParameterValue("graph_width");
    var graphHeight = gUrlObj.getUrlParameterValue("graph_height");
+   var bgColor = gUrlObj.getUrlParameterValue("color");
 
    if (firstIdx != "") {
       idxStr = "&first=" + firstIdx;
@@ -663,7 +665,7 @@
       countStr = "&count=" + idxCount; 
    }
 
-   open(urlBase + "&host=" + host + "&service=" + service + "&disp=" + dispName + idxStr + countStr + "&graph_start=" + newGraphStart + "&graph_end=" + newGraphEnd + "&graph_height=" + graphHeight + "&graph_width=" + graphWidth, "_self");
+   open(urlBase + "&host=" + host + "&service=" + service + "&disp=" + dispName + idxStr + countStr + "&graph_start=" + newGraphStart + "&graph_end=" + newGraphEnd + "&graph_height=" + graphHeight + "&graph_width=" + graphWidth + "&color=" + bgColor, "_self");
   }
  }
 }
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/wwwfiles/menu/menu_items.js.DIST ./hobbitd/wwwfiles/menu/menu_items.js.DIST
--- /home/henrik/hobbit/release/hobbit-4.2.0/hobbitd/wwwfiles/menu/menu_items.js.DIST	2006-08-09 22:10:08.000000000 +0200
+++ ./hobbitd/wwwfiles/menu/menu_items.js.DIST	2007-02-09 11:06:18.670085600 +0100
@@ -9,8 +9,10 @@
 		['Availability Report', '@BBCGIURL@/bb-rep.sh'],
 		['Snapshot Report', '@BBCGIURL@/bb-snapshot.sh'],
 		['Config Report', '@BBCGIURL@/hobbit-confreport.sh'],
+		['Config Report (Critical)', '@BBCGIURL@/hobbit-confreport-critical.sh'],
 		['Metrics Report', '@BBCGIURL@/hobbit-hostgraphs.sh'],
 		['Ghost Clients', '@BBCGIURL@/hobbit-ghosts.sh'],
+		['Notification Report', '@BBCGIURL@/hobbit-notifylog.sh'],
 	],
 	['Administration', null, null,
 		['Find host', '@BBCGIURL@/bb-findhost.sh'],
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/include/libbbgen.h ./include/libbbgen.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/include/libbbgen.h	2006-08-09 22:10:13.000000000 +0200
+++ ./include/libbbgen.h	2007-02-09 11:06:18.657087961 +0100
@@ -42,6 +42,7 @@
 #include "../lib/eventlog.h"
 #include "../lib/headfoot.h"
 #include "../lib/htmllog.h"
+#include "../lib/notifylog.h"
 #include "../lib/reportlog.h"
 
 #include "../lib/availability.h"
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/Makefile ./lib/Makefile
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/Makefile	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/Makefile	2007-02-09 11:07:56.711274061 +0100
@@ -1,9 +1,9 @@
 # bbgen library Makefile
 #
 
-BBGENLIBOBJS = osdefs.o acklog.o availability.o calc.o cgi.o cgiurls.o clientlocal.o color.o digest.o encoding.o environ.o errormsg.o eventlog.o files.o headfoot.o hobbitrrd.o htmllog.o ipaccess.o loadalerts.o loadhosts.o loadnkconf.o links.o matching.o md5.o memory.o misc.o netservices.o rbtr.o reportlog.o rmd160c.o sendmsg.o sha1.o sig.o stackio.o strfunc.o suid.o timefunc.o timing.o url.o
+BBGENLIBOBJS = osdefs.o acklog.o availability.o calc.o cgi.o cgiurls.o clientlocal.o color.o digest.o encoding.o environ.o errormsg.o eventlog.o files.o headfoot.o hobbitrrd.o htmllog.o ipaccess.o loadalerts.o loadhosts.o loadnkconf.o links.o matching.o md5.o memory.o misc.o netservices.o notifylog.o rbtr.o reportlog.o rmd160c.o sendmsg.o sha1.o sig.o stackio.o strfunc.o suid.o timefunc.o timing.o url.o
 
-CLIENTLIBOBJS = osdefs.o cgiurls.o color.o digest.o encoding.o environ-client.o errormsg.o ipaccess.o loadhosts.o md5.o memory.o misc.o rbtr.o rmd160c.o sendmsg.o sha1.o sig.o stackio.o strfunc.o suid.o timefunc.o
+CLIENTLIBOBJS = osdefs.o cgiurls.o color-client.o digest.o encoding.o environ-client.o errormsg.o ipaccess.o loadhosts.o md5.o memory.o misc.o rbtr.o rmd160c.o sendmsg.o sha1.o sig.o stackio.o strfunc.o suid.o timefunc-client.o
 ifeq ($(LOCALCLIENT),yes)
 	CLIENTLIBOBJS += matching.o
 endif
@@ -32,6 +32,9 @@
 eventlog.o: eventlog.c
 	$(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $<
 
+notifylog.o: notifylog.c
+	$(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $<
+
 headfoot.o: headfoot.c
 	$(CC) $(CFLAGS) $(PCREINCDIR) -c -o $@ $<
 
@@ -53,6 +56,12 @@
 environ-client.o: environ.c
 	$(CC) $(CFLAGS) -DBBTOPDIR=\"$(BBTOPDIR)\" -DBBLOGDIR=\"$(BBLOGDIR)\" -DBBHOSTNAME=\"$(BBHOSTNAME)\" -DBBHOSTIP=\"$(BBHOSTIP)\" -DBBHOSTOS=\"$(BBHOSTOS)\" -DBUILD_HOME=\"$(BBTOPDIR)/client\" -c -o $@ environ.c
 
+color-client.o: color.c
+	$(CC) $(CFLAGS) -DCLIENTONLY -c -o $@ $<
+
+timefunc-client.o: timefunc.c
+	$(CC) $(CFLAGS) -DCLIENTONLY -c -o $@ $<
+
 loadhosts: loadhosts.c libbbgen.a
 	$(CC) $(CFLAGS) -DSTANDALONE -o $@ loadhosts.c ./libbbgen.a
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/cgi.c ./lib/cgi.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/cgi.c	2006-08-09 22:10:15.000000000 +0200
+++ ./lib/cgi.c	2006-10-03 12:55:27.000000000 +0200
@@ -139,7 +139,7 @@
 			token = strtok(NULL, "&");
 		}
 	}
-	else if ((cgi_method == CGI_POST) || (conttype && (strcasecmp(conttype, "multipart/form-data") == 0))) {
+	else if ((cgi_method == CGI_POST) && (conttype && (strcasecmp(conttype, "multipart/form-data") == 0))) {
 		char *bol, *eoln, *delim;
 		char eolnchar = '\n';
 		char *currelembegin = NULL, *currelemend = NULL;
@@ -252,3 +252,32 @@
 	return head;
 }
 
+char *get_cookie(char *cookiename)
+{
+	static char *ckdata = NULL;
+	char *tok, *p;
+	int n;
+
+	/* If no cookie, just return NULL */
+	p = getenv("HTTP_COOKIE");
+	if (!p) return NULL;
+
+	if (ckdata) xfree(ckdata);
+	n = strlen(cookiename);
+
+	/* Split the cookie variable into elements, separated by ";" and possible space. */
+	ckdata = strdup(p);
+	tok = strtok(ckdata, "; ");
+	while (tok) {
+		if ((strncmp(cookiename, tok, n) == 0) && (*(tok+n) == '=')) {
+			/* Got it */
+			return (tok+n+1);
+		}
+
+		tok = strtok(NULL, "; ");
+	}
+
+	xfree(ckdata); ckdata = NULL;
+	return NULL;
+}
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/cgi.h ./lib/cgi.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/cgi.h	2006-08-09 22:10:15.000000000 +0200
+++ ./lib/cgi.h	2006-10-03 12:55:27.000000000 +0200
@@ -23,6 +23,7 @@
 
 extern char *cgi_error(void);
 extern cgidata_t *cgi_request(void);
+extern char *get_cookie(char *cookiename);
 
 #endif
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/color.c ./lib/color.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/color.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/color.c	2006-10-03 12:55:27.000000000 +0200
@@ -111,6 +111,7 @@
 	return filename;
 }
 
+#ifndef CLIENTONLY
 int colorset(char *colspec, int excludeset)
 {
 	char *cspeccopy = strdup(colspec);
@@ -131,4 +132,5 @@
 	ac = (ac & ~excludeset);
 	return ac;
 }
+#endif
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/headfoot.c ./lib/headfoot.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/headfoot.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/headfoot.c	2006-10-03 13:52:28.000000000 +0200
@@ -316,7 +316,7 @@
 	walk = statusboard;
 	while (walk) {
 		eoln = strchr(walk, '\n'); if (eoln) *eoln = '\0';
-		if (strlen(walk) && (strncmp(walk, "summary|", 8) != 0) && (strncmp(walk, "dialup|", 7) != 0)) {
+		if (strlen(walk) && (strncmp(walk, "summary|", 8) != 0)) {
 			char *buf, *hname = NULL, *tname = NULL;
 			treerec_t *newrec;
 
@@ -1233,8 +1233,6 @@
 		inbuf[st.st_size] = '\0';
 		close(formfile);
 
-		printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
-
 		headfoot(output, headertemplate, "", "header", color);
 		if (pretext) fprintf(output, "%s", pretext);
 		output_parsed(output, inbuf, color, seltime);
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/hobbitrrd.c ./lib/hobbitrrd.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/hobbitrrd.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/hobbitrrd.c	2006-10-03 12:55:27.000000000 +0200
@@ -194,7 +194,7 @@
 }
 
 
-static char *hobbit_graph_text(char *hostname, char *dispname, char *service, 
+static char *hobbit_graph_text(char *hostname, char *dispname, char *service, int bgcolor,
 			      hobbitgraph_t *graphdef, int itemcount, hg_stale_rrds_t nostale, const char *fmt)
 {
 	static char *rrdurl = NULL;
@@ -273,6 +273,7 @@
 			strcat(svcurl, urlencode(dispname ? dispname : hostname));
 
 			if (nostale == HG_WITHOUT_STALE_RRDS) strcat(svcurl, "&amp;nostale");
+			if (bgcolor != -1) sprintf(svcurl+strlen(svcurl), "&amp;color=%s", colorname(bgcolor));
 
 			sprintf(rrdparturl, fmt, rrdservicename, svcurl, svcurl, rrdservicename, svcurl, xgetenv("BBSKIN"));
 			if ((strlen(rrdparturl) + strlen(rrdurl) + 1) >= rrdurlsize) {
@@ -294,12 +295,12 @@
 	return rrdurl;
 }
 
-char *hobbit_graph_data(char *hostname, char *dispname, char *service, 
+char *hobbit_graph_data(char *hostname, char *dispname, char *service, int bgcolor,
 			hobbitgraph_t *graphdef, int itemcount,
 			hg_stale_rrds_t nostale, hg_link_t wantmeta)
 {
 	return hobbit_graph_text(hostname, dispname, 
-				 service, graphdef, 
+				 service, bgcolor, graphdef, 
 				 itemcount, nostale,
 				 ((wantmeta == HG_META_LINK) ? metafmt : hobbitlinkfmt));
 }
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/hobbitrrd.h ./lib/hobbitrrd.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/hobbitrrd.h	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/hobbitrrd.h	2006-10-03 12:55:27.000000000 +0200
@@ -37,7 +37,8 @@
 
 extern hobbitrrd_t *find_hobbit_rrd(char *service, char *flags);
 extern hobbitgraph_t *find_hobbit_graph(char *rrdname);
-extern char *hobbit_graph_data(char *hostname, char *dispname, char *service, hobbitgraph_t *graphdef, int itemcount, 
+extern char *hobbit_graph_data(char *hostname, char *dispname, char *service, int bgcolor,
+		hobbitgraph_t *graphdef, int itemcount, 
 		hg_stale_rrds_t nostale, hg_link_t wantmeta);
 
 #endif
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/htmllog.c ./lib/htmllog.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/htmllog.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/htmllog.c	2006-10-03 12:55:27.000000000 +0200
@@ -358,7 +358,7 @@
 		xfree(multikey);
 
 		fprintf(output, "<!-- linecount=%d -->\n", linecount);
-		fprintf(output, "%s\n", hobbit_graph_data(hostname, displayname, service, graph, linecount, HG_WITHOUT_STALE_RRDS, HG_PLAIN_LINK));
+		fprintf(output, "%s\n", hobbit_graph_data(hostname, displayname, service, color, graph, linecount, HG_WITHOUT_STALE_RRDS, HG_PLAIN_LINK));
 	}
 
 	if (histlocation == HIST_BOTTOM) {
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts.c ./lib/loadhosts.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/loadhosts.c	2006-10-03 13:52:28.000000000 +0200
@@ -134,7 +134,6 @@
 
 	bbh_item_name[BBH_IP]                  = "BBH_IP";
 	bbh_item_name[BBH_CLIENTALIAS]         = "BBH_CLIENTALIAS";
-	bbh_item_name[BBH_BANKSIZE]            = "BBH_BANKSIZE";
 	bbh_item_name[BBH_HOSTNAME]            = "BBH_HOSTNAME";
 	bbh_item_name[BBH_PAGENAME]            = "BBH_PAGENAME";
 	bbh_item_name[BBH_PAGEPATH]            = "BBH_PAGEPATH";
@@ -300,9 +299,8 @@
 	/* If default method, just say yes */
 	if (ghosthandling == 0) return result;
 
-	/* Allow all summaries and modembanks */
+	/* Allow all summaries */
 	if (strcmp(hostname, "summary") == 0) return result;
-	if (strcmp(hostname, "dialup") == 0) return result;
 
 	return (walk ? result : NULL);
 }
@@ -403,11 +401,6 @@
 		  else return bbh_find_item(host, item);
 		  break;
 
-	  case BBH_BANKSIZE:
-		  if (host->banksize == 0) return NULL;
-		  sprintf(inttxt, "%d", host->banksize);
-		  return inttxt;
-
 	  case BBH_HOSTNAME: 
 		  return host->bbhostname;
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts.h ./lib/loadhosts.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts.h	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/loadhosts.h	2006-10-03 13:52:28.000000000 +0200
@@ -53,7 +53,6 @@
 	BBH_LDAPLOGIN,
 	BBH_IP,
 	BBH_HOSTNAME,
-	BBH_BANKSIZE,
 	BBH_DOCURL,
 	BBH_NOPROP,
 	BBH_PAGEINDEX,
@@ -80,7 +79,6 @@
 	char *bbhostname;	/* Name for item 2 of bb-hosts */
 	char *logname;		/* Name of the host directory in BBHISTLOGS (underscores replaces dots). */
 	int preference;		/* For host with multiple entries, mark if we have the preferred one */
-	int banksize;		/* For modem-bank entries only */
 	pagelist_t *page;	/* Host location in the page/subpage/subparent tree */
 	void *data;		/* Misc. data supplied by the user of this library function */
 	struct namelist_t *defaulthost;	/* Points to the latest ".default." host */
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts_file.c ./lib/loadhosts_file.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts_file.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/loadhosts_file.c	2006-10-03 13:52:28.000000000 +0200
@@ -49,7 +49,7 @@
 {
 	static void *bbhfiles = NULL;
 	FILE *bbhosts;
-	int ip1, ip2, ip3, ip4, banksize, groupid, pageidx;
+	int ip1, ip2, ip3, ip4, groupid, pageidx;
 	char hostname[4096];
 	strbuffer_t *inbuf;
 	pagelist_t *curtoppage, *curpage, *pgtail;
@@ -285,30 +285,6 @@
 			MEMUNDEFINE(clientname);
 			MEMUNDEFINE(downtime);
 		}
-		else if (sscanf(STRBUF(inbuf), "dialup %s %d.%d.%d.%d %d", hostname, &ip1, &ip2, &ip3, &ip4, &banksize) == 6) {
-			char groupidstr[10];
-			namelist_t *newitem = calloc(1, sizeof(namelist_t));
-
-			sprintf(newitem->ip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4);
-			sprintf(groupidstr, "%d", groupid);
-			newitem->bbhostname = (char *)malloc(strlen("@dialup.") + strlen(hostname) + 1);
-			sprintf(newitem->bbhostname, "@dialup.%s", hostname);
-			newitem->clientname = newitem->bbhostname;
-			newitem->page = curpage;
-			newitem->elems = (char **)malloc(sizeof(char *));
-			newitem->elems[0] = NULL;
-			newitem->banksize = banksize;
-			newitem->groupid = strdup(groupidstr);
-			newitem->pageindex = pageidx++;
-			newitem->next = NULL;
-
-			if (namehead == NULL) 
-				namehead = nametail = newitem;
-			else {
-				nametail->next = newitem;
-				nametail = newitem;
-			}
-		}
 	}
 	stackfclose(bbhosts);
 	freestrbuffer(inbuf);
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts_net.c ./lib/loadhosts_net.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/loadhosts_net.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/loadhosts_net.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,182 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Hobbit monitor library.                                                    */
-/*                                                                            */
-/* This is a library module for Hobbit, responsible for loading the host      */
-/* configuration from hobbitd.                                                */
-/*                                                                            */
-/* Copyright (C) 2006      Henrik Storner <henrik@hswn.dk>                    */
-/*                                                                            */
-/* This program is released under the GNU General Public License (GPL),       */
-/* version 2. See the file "COPYING" for details.                             */
-/*                                                                            */
-/*----------------------------------------------------------------------------*/
-
-static char rcsid_file[] = "$Id: loadhosts_net.c,v 1.1 2006/03/30 19:59:50 henrik Rel $";
-
-namelist_t *load_hostnames_net(void)
-{
-	char *configbuf = NULL, *inbuf = NULL;
-	namelist_t *nametail = NULL;
-	char bbmsg[200];
-	char *cnfptr;
-
-	configloaded = 1;
-	initialize_hostlist();
-
-	strcpy(bbmsg, "hostinfo fields=BBH_HOSTNAME,BBH_PAGEPATH,BBH_GROUPID,BBH_BANKSIZE,BBH_IP,BBH_RAW");
-	if (xgetenv("BBLOCATION")) sprintf(bbmsg+strlen(bbmsg), " net=%s", xgetenv("BBLOCATION"));
-
-	if (sendmessage(bbmsg, NULL, NULL, &configbuf, 1, BBTALK_TIMEOUT) != BB_OK) return NULL;
-	inbuf = strtok_r(bbmsg, "\n", &cnfptr);
-	while (inbuf) {
-		char *hostname, *pagepath, *groupid, *banksize, *ip;
-
-		namelist_t *newitem = calloc(1, sizeof(namelist_t));
-
-		hostname = pagepath = groupid = banksize = ip = NULL;
-		hostname = gettok(inbuf, "|");
-		if (hostname) pagepath = gettok(NULL, "|");
-		if (pagepath) groupid = gettok(NULL, "|");
-		if (groupid) banksize = gettok(NULL, "|");
-		if (banksize) ip = gettok(NULL, "|");
-
-		newitem->bbhostname = strdup(hostname);
-		newitem->clientname = newitem->bbhostname;
-		newitem->page = setup_page(pagepath);
-		newitem->groupid = strdup(groupid);
-		newitem->banksize = atoi(banksize);
-		strcpy(newitem->ip, ip);
-
-		if (strncmp(hostname, "@dialup", 7) == 0) {
-			/* No other elements for this type of entry */
-			newitem->elems = (char **)malloc(sizeof(char *));
-			newitem->elems[0] = NULL;
-			newitem->next = NULL;
-		}
-
-		if (namehead == NULL) 
-			namehead = nametail = newitem;
-		else {
-			nametail->next = newitem;
-			nametail = newitem;
-		}
-
-			newitem->bbhostname = strdup(hostname);
-			if (ip1 || ip2 || ip3 || ip4) newitem->preference = 1; else newitem->preference = 0;
-			newitem->clientname = NULL;
-			newitem->logname = strdup(newitem->bbhostname);
-			{ char *p = newitem->logname; while ((p = strchr(p, '.')) != NULL) { *p = '_'; } }
-			newitem->downtime = NULL;
-			newitem->page = curpage;
-			newitem->data = NULL;
-			newitem->defaulthost = defaulthost;
-
-			clientname[0] = downtime[0] = '\0';
-			startoftags = strchr(inbuf, '#');
-			if (startoftags == NULL) startoftags = ""; else startoftags++;
-			startoftags += strspn(startoftags, " \t\r\n");
-			newitem->allelems = strdup(startoftags);
-			elemsize = 5;
-			newitem->elems = (char **)malloc((elemsize+1)*sizeof(char *));
-
-			tag = newitem->allelems; elemidx = 0;
-			while (tag && *tag) {
-				if (elemidx == elemsize) {
-					elemsize += 5;
-					newitem->elems = (char **)realloc(newitem->elems, (elemsize+1)*sizeof(char *));
-				}
-				newitem->elems[elemidx] = tag;
-
-				/* Skip until we hit a whitespace or a quote */
-				tag += strcspn(tag, " \t\r\n\"");
-				if (*tag == '"') {
-					delim = tag;
-
-					/* Hit a quote - skip until the next matching quote */
-					tag = strchr(tag+1, '"');
-					if (tag != NULL) { 
-						/* Found end-quote, NULL the item here and move on */
-						*tag = '\0'; tag++; 
-					}
-
-					/* Now move quoted data one byte down (including the NUL) to kill quotechar */
-					memmove(delim, delim+1, strlen(delim));
-				}
-				else if (*tag) {
-					/* Normal end of item, NULL it and move on */
-					*tag = '\0'; tag++;
-				}
-				else {
-					/* End of line - no more to do. */
-					tag = NULL;
-				}
-
-				/* 
-				 * If we find a "noconn", drop preference value to 0.
-				 * If we find a "prefer", up reference value to 2.
-				 */
-				if ((newitem->preference == 1) && (strcmp(newitem->elems[elemidx], "noconn") == 0))
-					newitem->preference = 0;
-				else if (strcmp(newitem->elems[elemidx], "prefer") == 0)
-					newitem->preference = 2;
-
-				/* Skip whitespace until start of next tag */
-				if (tag) tag += strspn(tag, " \t\r\n");
-				elemidx++;
-			}
-
-			newitem->elems[elemidx] = NULL;
-
-			/* See if this host is defined before */
-			for (iwalk = namehead, iprev = NULL; (iwalk && strcmp(iwalk->bbhostname, newitem->bbhostname)); iprev = iwalk, iwalk = iwalk->next) ;
-			if (strcasecmp(newitem->bbhostname, ".default.") == 0) {
-				/* The pseudo DEFAULT host */
-				newitem->next = NULL;
-				defaulthost = newitem;
-			}
-			else if (iwalk == NULL) {
-				/* New item, so add to end of list */
-				newitem->next = NULL;
-				if (namehead == NULL) 
-					namehead = nametail = newitem;
-				else {
-					nametail->next = newitem;
-					nametail = newitem;
-				}
-			}
- 			else if (newitem->preference <= iwalk->preference) {
-				/* Add after the existing (more preferred) entry */
-				newitem->next = iwalk->next;
-				iwalk->next = newitem;
-			}
-			else {
-				/* New item has higher preference, so add before the iwalk item (i.e. after iprev) */
-				if (iprev == NULL) {
-					newitem->next = namehead;
-					namehead = newitem;
-				}
-				else {
-					newitem->next = iprev->next;
-					iprev->next = newitem;
-				}
-			}
-
-			newitem->clientname = bbh_find_item(newitem, BBH_CLIENTALIAS);
-			if (newitem->clientname == NULL) newitem->clientname = newitem->bbhostname;
-			newitem->downtime = bbh_find_item(newitem, BBH_DOWNTIME);
-
-			MEMUNDEFINE(clientname);
-			MEMUNDEFINE(downtime);
-		}
-
-
-		inbuf = strtok_r(NULL, "\n", &cnfptr);
-	}
-	xfree(configbuf);
-
-	build_hosttree();
-	return namehead;
-}
-
-
-
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/notifylog.c ./lib/notifylog.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/notifylog.c	1970-01-01 01:00:00.000000000 +0100
+++ ./lib/notifylog.c	2007-02-09 11:06:31.266797268 +0100
@@ -0,0 +1,360 @@
+/*----------------------------------------------------------------------------*/
+/* Hobbit monitor library.                                                    */
+/*                                                                            */
+/* This displays the "notification" log.                                      */
+/*                                                                            */
+/* Copyright (C) 2002-2006 Henrik Storner <henrik@storner.dk>                 */
+/* Host/test/color/start/end filtering code by Eric Schwimmer 2005            */
+/*                                                                            */
+/* This program is released under the GNU General Public License (GPL),       */
+/* version 2. See the file "COPYING" for details.                             */
+/*                                                                            */
+/*----------------------------------------------------------------------------*/
+
+static char rcsid[] = "$Id: notifylog.c,v 1.2 2007/02/07 21:51:31 henrik Exp $";
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+
+#include <pcre.h>
+
+#include "libbbgen.h"
+
+typedef struct notification_t {
+	struct namelist_t *host;
+	struct htnames_t *service;
+	time_t  eventtime;
+	char *recipient;
+	struct notification_t *next;
+} notification_t;
+
+
+static time_t convert_time(char *timestamp)
+{
+	time_t event = 0;
+	unsigned int year,month,day,hour,min,sec,count;
+	struct tm timeinfo;
+
+	count = sscanf(timestamp, "%u/%u/%u@%u:%u:%u",
+		&year, &month, &day, &hour, &min, &sec);
+	if(count != 6) {
+		return -1;
+	}
+	if(year < 1970) {
+		return 0;
+	}
+	else {
+		memset(&timeinfo, 0, sizeof(timeinfo));
+		timeinfo.tm_year  = year - 1900;
+		timeinfo.tm_mon   = month - 1;
+		timeinfo.tm_mday  = day;
+		timeinfo.tm_hour  = hour;
+		timeinfo.tm_min   = min;
+		timeinfo.tm_sec   = sec;
+		timeinfo.tm_isdst = -1;
+		event = mktime(&timeinfo);		
+	}
+
+	return event;
+}
+
+static htnames_t *namehead = NULL;
+static htnames_t *getname(char *name, int createit)
+{
+	htnames_t *walk;
+
+	for (walk = namehead; (walk && strcmp(walk->name, name)); walk = walk->next) ;
+	if (walk || (!createit)) return walk;
+
+	walk = (htnames_t *)malloc(sizeof(htnames_t));
+	walk->name = strdup(name);
+	walk->next = namehead;
+	namehead = walk;
+
+	return walk;
+}
+
+void do_notifylog(FILE *output, 
+		  int maxcount, int maxminutes, char *fromtime, char *totime, 
+		  char *pageregex, char *expageregex,
+		  char *hostregex, char *exhostregex,
+		  char *testregex, char *extestregex,
+		  char *rcptregex, char *exrcptregex)
+{
+	FILE *notifylog;
+	char notifylogfilename[PATH_MAX];
+	time_t firstevent = 0;
+	time_t lastevent = time(NULL);
+	notification_t *head, *walk;
+	struct stat st;
+	char l[MAX_LINE_LEN];
+	char title[200];
+
+	/* For the PCRE matching */
+	const char *errmsg = NULL;
+	int errofs = 0;
+	pcre *pageregexp = NULL;
+	pcre *expageregexp = NULL;
+	pcre *hostregexp = NULL;
+	pcre *exhostregexp = NULL;
+	pcre *testregexp = NULL;
+	pcre *extestregexp = NULL;
+	pcre *rcptregexp = NULL;
+	pcre *exrcptregexp = NULL;
+
+	if (maxminutes && (fromtime || totime)) {
+		fprintf(output, "<B>Only one time interval type is allowed!</B>");
+		return;
+	}
+
+	if (fromtime) {
+		firstevent = convert_time(fromtime);
+		if(firstevent < 0) {
+			fprintf(output,"<B>Invalid 'from' time: %s</B>", fromtime);
+			return;
+		}
+	}
+	else if (maxminutes) {
+		firstevent = time(NULL) - maxminutes*60;
+	}
+	else {
+		firstevent = time(NULL) - 86400;
+	}
+
+	if (totime) {
+		lastevent = convert_time(totime);
+		if (lastevent < 0) {
+			fprintf(output,"<B>Invalid 'to' time: %s</B>", totime);
+			return;
+		}
+		if (lastevent < firstevent) {
+			fprintf(output,"<B>'to' time must be after 'from' time.</B>");
+			return;
+		}
+	}
+
+	if (!maxcount) maxcount = 100;
+
+	if (pageregex && *pageregex) pageregexp = pcre_compile(pageregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (expageregex && *expageregex) expageregexp = pcre_compile(expageregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (hostregex && *hostregex) hostregexp = pcre_compile(hostregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (exhostregex && *exhostregex) exhostregexp = pcre_compile(exhostregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (testregex && *testregex) testregexp = pcre_compile(testregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (extestregex && *extestregex) extestregexp = pcre_compile(extestregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (rcptregex && *rcptregex) rcptregexp = pcre_compile(rcptregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+	if (exrcptregex && *exrcptregex) exrcptregexp = pcre_compile(exrcptregex, PCRE_CASELESS, &errmsg, &errofs, NULL);
+
+	sprintf(notifylogfilename, "%s/notifications.log", xgetenv("BBSERVERLOGS"));
+	notifylog = fopen(notifylogfilename, "r");
+
+	if (notifylog && (stat(notifylogfilename, &st) == 0)) {
+		time_t curtime;
+		int done = 0;
+
+		/* Find a spot in the notification log file close to where the firstevent time is */
+		fseeko(notifylog, 0, SEEK_END);
+		do {
+			/* Go back maxcount*80 bytes - one entry is ~80 bytes */
+			if (ftello(notifylog) > maxcount*80) {
+				unsigned int uicurtime;
+				fseeko(notifylog, -maxcount*80, SEEK_CUR); 
+				fgets(l, sizeof(l), notifylog); /* Skip to start of line */
+				fgets(l, sizeof(l), notifylog);
+				/* Sun Jan  7 10:29:08 2007 myhost.disk (130.225.226.90) foo@test.com 1168162147 100 */
+				sscanf(l, "%*s %*s %*u %*u:%*u:%*u %*u %*s %*s %*s %u %*d", &uicurtime);
+				curtime = uicurtime;
+				done = (curtime < firstevent);
+			}
+			else {
+				rewind(notifylog);
+				done = 1;
+			}
+		} while (!done);
+	}
+	
+	head = NULL;
+
+	while (notifylog && (fgets(l, sizeof(l), notifylog))) {
+
+		time_t eventtime;
+		char hostsvc[MAX_LINE_LEN];
+		char recipient[MAX_LINE_LEN];
+		char *hostname, *svcname, *p;
+		int itemsfound, pagematch, hostmatch, testmatch, rcptmatch;
+		notification_t *newrec;
+		struct namelist_t *eventhost;
+		struct htnames_t *eventcolumn;
+		int ovector[30];
+
+		itemsfound = sscanf(l, "%*s %*s %*u %*u:%*u:%*u %*u %s %*s %s %u %*d", hostsvc, recipient, &eventtime);
+		if (itemsfound != 3) continue;
+		if (eventtime < firstevent) continue;
+		if (eventtime > lastevent) break;
+
+		hostname = hostsvc; svcname = strrchr(hostsvc, '.'); if (svcname) { *svcname = '\0'; svcname++; } else svcname = "";
+		eventhost = hostinfo(hostname);
+		if (!eventhost) continue; /* Dont report hosts that no longer exist */
+		eventcolumn = getname(svcname, 1);
+
+		p = strchr(recipient, '['); if (p) *p = '\0';
+
+		if (pageregexp) {
+			char *pagename = bbh_item(eventhost, BBH_PAGEPATH);
+			pagematch = (pcre_exec(pageregexp, NULL, pagename, strlen(pagename), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		}
+		else
+			pagematch = 1;
+		if (!pagematch) continue;
+
+		if (expageregexp) {
+			char *pagename = bbh_item(eventhost, BBH_PAGEPATH);
+			pagematch = (pcre_exec(expageregexp, NULL, pagename, strlen(pagename), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		}
+		else
+			pagematch = 0;
+		if (pagematch) continue;
+
+		if (hostregexp)
+			hostmatch = (pcre_exec(hostregexp, NULL, hostname, strlen(hostname), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			hostmatch = 1;
+		if (!hostmatch) continue;
+
+		if (exhostregexp)
+			hostmatch = (pcre_exec(exhostregexp, NULL, hostname, strlen(hostname), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			hostmatch = 0;
+		if (hostmatch) continue;
+
+		if (testregexp)
+			testmatch = (pcre_exec(testregexp, NULL, svcname, strlen(svcname), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			testmatch = 1;
+		if (!testmatch) continue;
+
+		if (extestregexp)
+			testmatch = (pcre_exec(extestregexp, NULL, svcname, strlen(svcname), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			testmatch = 0;
+		if (testmatch) continue;
+
+		if (rcptregexp)
+			rcptmatch = (pcre_exec(rcptregexp, NULL, recipient, strlen(recipient), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			rcptmatch = 1;
+		if (!rcptmatch) continue;
+
+		if (exrcptregexp)
+			rcptmatch = (pcre_exec(exrcptregexp, NULL, recipient, strlen(recipient), 0, 0, 
+					ovector, (sizeof(ovector)/sizeof(int))) >= 0);
+		else
+			rcptmatch = 0;
+		if (rcptmatch) continue;
+
+		newrec = (notification_t *) malloc(sizeof(notification_t));
+		newrec->host       = eventhost;
+		newrec->service    = eventcolumn;
+		newrec->eventtime  = eventtime;
+		newrec->recipient  = strdup(recipient);
+		newrec->next       = head;
+		head = newrec;
+	}
+
+	if (head) {
+		char *bgcolors[2] = { "#000000", "#000066" };
+		int  bgcolor = 0;
+		int  count;
+		struct notification_t *lasttoshow = head;
+
+		count=0;
+		walk=head; 
+		do {
+			count++;
+			lasttoshow = walk;
+			walk = walk->next;
+		} while (walk && (count<maxcount));
+
+		if (maxminutes)  { 
+			sprintf(title, "%d notifications in the past %u minutes", 
+				count, (unsigned int)((time(NULL) - lasttoshow->eventtime) / 60));
+		}
+		else {
+			sprintf(title, "%d notifications sent.", count);
+		}
+
+		fprintf(output, "<BR><BR>\n");
+		fprintf(output, "<TABLE SUMMARY=\"Notification log\" BORDER=0>\n");
+		fprintf(output, "<TR BGCOLOR=\"#333333\">\n");
+		fprintf(output, "<TD ALIGN=CENTER COLSPAN=4><FONT SIZE=-1 COLOR=\"#33ebf4\">%s</FONT></TD></TR>\n", title);
+		fprintf(output, "<TR BGCOLOR=\"#333333\"><TH>Time</TH><TH>Host</TH><TH>Service</TH><TH>Recipient</TH></TR>\n");
+
+		for (walk=head; (walk != lasttoshow->next); walk=walk->next) {
+			char *hostname = bbh_item(walk->host, BBH_HOSTNAME);
+
+			fprintf(output, "<TR BGCOLOR=%s>\n", bgcolors[bgcolor]);
+			bgcolor = ((bgcolor + 1) % 2);
+
+			fprintf(output, "<TD ALIGN=LEFT>%s</TD>\n", ctime(&walk->eventtime));
+
+			fprintf(output, "<TD ALIGN=LEFT>%s</TD>\n", hostname);
+			fprintf(output, "<TD ALIGN=LEFT>%s</TD>\n", walk->service->name);
+			fprintf(output, "<TD ALIGN=LEFT>%s</TD>\n", walk->recipient);
+		}
+
+		fprintf(output, "</TABLE>\n");
+
+		/* Clean up */
+		walk = head;
+		do {
+			struct notification_t *tmp = walk;
+
+			walk = walk->next;
+			xfree(tmp->recipient);
+			xfree(tmp);
+		} while (walk);
+	}
+	else {
+		/* No notifications during the past maxminutes */
+		if (notifylog)
+			sprintf(title, "No notifications sent in the last %d minutes", maxminutes);
+		else
+			strcpy(title, "No notifications logged");
+
+		fprintf(output, "<CENTER><BR>\n");
+		fprintf(output, "<TABLE SUMMARY=\"%s\" BORDER=0>\n", title);
+		fprintf(output, "<TR BGCOLOR=\"#333333\">\n");
+		fprintf(output, "<TD ALIGN=CENTER COLSPAN=6><FONT SIZE=-1 COLOR=\"#33ebf4\">%s</FONT></TD>\n", title);
+		fprintf(output, "</TR>\n");
+		fprintf(output, "</TABLE>\n");
+		fprintf(output, "</CENTER>\n");
+	}
+
+	if (notifylog) fclose(notifylog);
+
+	if (pageregexp)   pcre_free(pageregexp);
+	if (expageregexp) pcre_free(expageregexp);
+	if (hostregexp)   pcre_free(hostregexp);
+	if (exhostregexp) pcre_free(exhostregexp);
+	if (testregexp)   pcre_free(testregexp);
+	if (extestregexp) pcre_free(extestregexp);
+	if (rcptregexp)   pcre_free(rcptregexp);
+	if (exrcptregexp) pcre_free(exrcptregexp);
+}
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/notifylog.h ./lib/notifylog.h
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/notifylog.h	1970-01-01 01:00:00.000000000 +0100
+++ ./lib/notifylog.h	2007-02-09 11:06:31.269796723 +0100
@@ -0,0 +1,21 @@
+/*----------------------------------------------------------------------------*/
+/* Hobbit monitor library.                                                    */
+/*                                                                            */
+/* Copyright (C) 2002-2006 Henrik Storner <henrik@storner.dk>                 */
+/*                                                                            */
+/* This program is released under the GNU General Public License (GPL),       */
+/* version 2. See the file "COPYING" for details.                             */
+/*                                                                            */
+/*----------------------------------------------------------------------------*/
+
+#ifndef __NOTIFYLOG_H_
+#define __NOTIFYLOG_H_
+
+extern void do_notifylog(FILE *output, int maxcount, int maxminutes, char *fromtime, char *totime, 
+			 char *pagematch, char *expagematch, 
+			 char *hostmatch, char *exhostmatch, 
+			 char *testmatch, char *extestmatch,
+			 char *rcptmatch, char *exrcptmatch);
+
+#endif
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/lib/timefunc.c ./lib/timefunc.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/lib/timefunc.c	2006-08-09 22:10:16.000000000 +0200
+++ ./lib/timefunc.c	2006-10-03 13:52:29.000000000 +0200
@@ -245,6 +245,7 @@
 	return found;
 }
 
+#ifndef CLIENTONLY
 char *check_downtime(char *hostname, char *testname)
 {
 	namelist_t *hinfo = hostinfo(hostname);
@@ -300,6 +301,7 @@
 
 	return NULL;
 }
+#endif
 
 int periodcoversnow(char *tag)
 {
@@ -415,20 +417,30 @@
 	 */
 
 	int result = 0;
-	char *p;
-	char modifier;
+	char *startofval;
 
-	p = dur + strspn(dur, "0123456789");
-	modifier = *p;
-	*p = '\0';
-	result = atoi(dur);
-	*p = modifier;
+	startofval = dur;
+
+	while (startofval && (isdigit((int)*startofval))) {
+		char *p;
+		char modifier;
+		int oneval = 0;
+
+		p = startofval + strspn(startofval, "0123456789");
+		modifier = *p;
+		*p = '\0';
+		oneval = atoi(startofval);
+		*p = modifier;
+
+		switch (modifier) {
+		  case 'm': break;			/* minutes */
+		  case 'h': oneval *= 60; break;	/* hours */
+		  case 'd': oneval *= 1440; break;	/* days */
+		  case 'w': oneval *= 10080; break;	/* weeks */
+		}
 
-	switch (modifier) {
-	  case 'm': break;			/* minutes */
-	  case 'h': result *= 60; break;	/* hours */
-	  case 'd': result *= 1440; break;	/* days */
-	  case 'w': result *= 10080; break;	/* weeks */
+		result += oneval;
+		startofval = ((*p) ? p+1 : NULL);
 	}
 
 	return result;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/Makefile ./web/Makefile
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/Makefile	2006-08-09 22:10:13.000000000 +0200
+++ ./web/Makefile	2007-02-09 11:06:18.666086326 +0100
@@ -1,5 +1,5 @@
-PROGRAMS = bb-hist.cgi bb-eventlog.cgi bb-rep.cgi bb-replog.cgi bb-snapshot.cgi bb-findhost.cgi bb-csvinfo.cgi bb-ack.cgi bb-webpage bb-datepage.cgi hobbitgraph.cgi hobbitsvc.cgi hobbit-enadis.cgi hobbit-confreport.cgi hobbit-nkview.cgi hobbit-nkedit.cgi hobbit-ackinfo.cgi hobbit-statusreport.cgi boilerplate.cgi hobbit-hostgraphs.cgi hobbit-ghosts.cgi
-CGISCRIPTS = bb-hist.sh bb-eventlog.sh bb-rep.sh bb-replog.sh bb-snapshot.sh bb-findhost.sh bb-csvinfo.sh hobbitcolumn.sh bb-datepage.sh hobbitgraph.sh bb-hostsvc.sh bb-histlog.sh hobbit-confreport.sh hobbit-nkview.sh hobbit-certreport.sh hobbit-nongreen.sh hobbit-hostgraphs.sh hobbit-ghosts.sh
+PROGRAMS = bb-hist.cgi bb-eventlog.cgi bb-rep.cgi bb-replog.cgi bb-snapshot.cgi bb-findhost.cgi bb-csvinfo.cgi bb-ack.cgi bb-webpage bb-datepage.cgi hobbitgraph.cgi hobbitsvc.cgi hobbit-enadis.cgi hobbit-confreport.cgi hobbit-nkview.cgi hobbit-nkedit.cgi hobbit-ackinfo.cgi hobbit-statusreport.cgi boilerplate.cgi hobbit-hostgraphs.cgi hobbit-ghosts.cgi hobbit-notifylog.cgi
+CGISCRIPTS = bb-hist.sh bb-eventlog.sh bb-rep.sh bb-replog.sh bb-snapshot.sh bb-findhost.sh bb-csvinfo.sh hobbitcolumn.sh bb-datepage.sh hobbitgraph.sh bb-hostsvc.sh bb-histlog.sh hobbit-confreport.sh hobbit-confreport-critical.sh hobbit-nkview.sh hobbit-certreport.sh hobbit-nongreen.sh hobbit-hostgraphs.sh hobbit-ghosts.sh hobbit-notifylog.sh
 SECCGISCRIPTS = bb-ack.sh hobbit-enadis.sh hobbit-nkedit.sh hobbit-ackinfo.sh
 
 LIBOBJS = ../lib/libbbgen.a
@@ -25,6 +25,7 @@
 STATUSREPOBJS   = hobbit-statusreport.o
 MAILACKOBJS     = hobbit-mailack.o
 GHOSTOBJS       = hobbit-ghosts.o
+NOTIFYOBJS      = hobbit-notifylog.o
 
 HOSTGRAPHSOBJS  = hobbit-hostgraphs.o
 BOILERPLATEOBJS = boilerplate.o
@@ -105,6 +106,9 @@
 hobbit-ghosts.cgi: $(GHOSTOBJS) $(LIBOBJS)
 	$(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(GHOSTOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS)
 
+hobbit-notifylog.cgi: $(NOTIFYOBJS) $(LIBOBJS)
+	$(CC) $(CFLAGS) -o $@ $(RPATHOPT) $(NOTIFYOBJS) $(LIBOBJS) $(PCRELIBS) $(NETLIBS)
+
 
 bb-ack.sh: bb-ack.sh.DIST
 	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
@@ -178,6 +182,10 @@
 	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
 	chmod 755 $@
 
+hobbit-confreport-critical.sh: hobbit-confreport-critical.sh.DIST
+	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
+	chmod 755 $@
+
 hobbit-certreport.sh: hobbit-certreport.sh.DIST
 	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
 	chmod 755 $@
@@ -194,6 +202,10 @@
 	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
 	chmod 755 $@
 
+hobbit-notifylog.sh: hobbit-notifylog.sh.DIST
+	cat $< | sed -e 's!@BBHOME@!$(BBHOME)!g' | sed -e 's!@RUNTIMEDEFS@!$(RUNTIMEDEFS)!g' >$@
+	chmod 755 $@
+
 
 %.o: %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-ack.c ./web/bb-ack.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-ack.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-ack.c	2006-10-03 13:52:29.000000000 +0200
@@ -133,7 +133,7 @@
 				}
 
 				if (acknum) awalk->acknum = atoi(acknum);
-				if (validity) awalk->validity = atoi(validity);
+				if (validity) awalk->validity = durationvalue(validity);
 				if (ackmsg) awalk->ackmsg = strdup(ackmsg);
 				if (hostname) awalk->hostname = strdup(hostname);
 				if (testname) awalk->testname = strdup(testname);
@@ -161,7 +161,7 @@
 
 	fprintf(output, "    <td>%s</td>\n", (hname ? hname : "&nbsp;"));
 	fprintf(output, "    <td>%s</td>\n", (tname ? tname : "&nbsp;"));
-	fprintf(output, "    <TD><INPUT TYPE=TEXT NAME=\"DELAY_%s\" SIZE=4 MAXLENGTH=4></TD>\n", numstr);
+	fprintf(output, "    <TD><INPUT TYPE=TEXT NAME=\"DELAY_%s\" SIZE=8 MAXLENGTH=20></TD>\n", numstr);
 	fprintf(output, "    <TD><INPUT TYPE=TEXT NAME=\"MESSAGE_%s\" SIZE=60 MAXLENGTH=80></TD>\n", numstr);
 
 	fprintf(output, "    <TD>\n");
@@ -224,42 +224,31 @@
 				 NULL, NULL);
 		}
 		else {
-			char cmd[1024];
+			char *cmd;
 			char *respbuf = NULL;
-                	char *cookie = NULL, *p;
+			char *hostname, *pagename;
 			int gotfilter = 0;
 
 			headfoot(stdout, "acknowledge", "", "header", COL_RED);
 
+			cmd = (char *)malloc(1024);
 			strcpy(cmd, "hobbitdboard color=red,yellow fields=hostname,testname,cookie");
 
-			p = getenv("HTTP_COOKIE"); 
-			if (p) cookie = strdup(p);
-
-			if (obeycookies && cookie && ((p = strstr(cookie, "host=")) != NULL)) {
-				char *hostname;
-				
-				hostname = p + strlen("host=");
-				p = strchr(hostname, ';'); if (p) *p = '\0';
+			if (obeycookies && !gotfilter && ((hostname = get_cookie("host")) != NULL)) {
 				if (*hostname) {
-					sprintf(cmd + strlen(cmd), " host=%s", hostname);
+					cmd = (char *)realloc(cmd, 1024 + strlen(hostname));
+					sprintf(cmd + strlen(cmd), " host=^%s$", hostname);
 					gotfilter = 1;
 				}
-				if (p) *p = ';';
 			}
 
-			if (obeycookies && cookie && !gotfilter && ((p = strstr(cookie, "pagepath=")) != NULL)) {
-				char *pagename;
-
-				pagename = p + strlen("pagepath=");
-				p = strchr(pagename, ';'); if (p) *p = '\0';
+			if (obeycookies && !gotfilter && ((pagename = get_cookie("pagepath")) != NULL)) {
 				if (*pagename) {
-					sprintf(cmd + strlen(cmd), " page=^%s$", pagename);
+					cmd = (char *)realloc(cmd, 1024 + 2*strlen(pagename));
+					sprintf(cmd + strlen(cmd), " page=^%s$|^%s/.+", pagename, pagename);
 					gotfilter = 1;
 				}
-				if (p) *p = ';';
 			}
-			xfree(cookie);
 
 			if (sendmessage(cmd, NULL, NULL, &respbuf, 1, BBTALK_TIMEOUT) == BB_OK) {
 				char *bol, *eoln;
@@ -327,7 +316,7 @@
 
 			if (reqtype == ACK_MANY) {
 				if (!awalk->ackmsg) awalk->ackmsg = ackmsgall;
-				if (!awalk->validity && validityall) awalk->validity = atoi(validityall);
+				if (!awalk->validity && validityall) awalk->validity = durationvalue(validityall);
 			}
 
 			count++;
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-datepage.c ./web/bb-datepage.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-datepage.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-datepage.c	2006-10-03 12:55:27.000000000 +0200
@@ -108,30 +108,21 @@
 	parse_query();
 
 	if (cgi_method == CGI_POST) {
-		char *cookie, *pagepath, *p;
-		char *endurl;
+		char *pagepath, *cookie, *endurl;
 
-		cookie = getenv("HTTP_COOKIE");
-		if (cookie == NULL) {
-			errormsg("Cookies must be enabled\n");
-			return 1;
+		cookie = get_cookie("pagepath");
+
+		if (cookie && *cookie) {
+			pagepath = strdup(cookie);
 		}
+		else {
+			cookie = get_cookie("host");
 
-		p = strstr(cookie, "pagepath="); if (p) p+= strlen("pagepath=");
-		if ((p == NULL) || (strlen(p) == 0) || (*p == ';')) {
-			p = strstr(cookie, "host="); if (p) p += strlen("host=");
-			if ((p == NULL) || (strlen(p) == 0) || (*p == ';')) {
-				pagepath = "";
-			}
-			else {
-				char *hname;
+			if (cookie && *cookie) {
 				namelist_t *hinfo;
 
-				hname = strdup(p);
-				p = strchr(hname, ';'); if (p) *p = '\0';
-
 				load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
-				hinfo = hostinfo(hname);
+				hinfo = hostinfo(cookie);
 				if (hinfo) {
 					pagepath = bbh_item(hinfo, BBH_PAGEPATH);
 				}
@@ -139,10 +130,9 @@
 					pagepath = "";
 				}
 			}
-		}
-		else {
-			pagepath = strdup(p);
-			p = strchr(pagepath, ';'); if (p) *p = '\0';
+			else {
+				pagepath = "";
+			}
 		}
 
 		endurl = (char *)malloc(strlen(urlprefix) + strlen(pagepath) + 1024);
@@ -201,6 +191,7 @@
 		}
 
 		sethostenv("", "", "", colorname(bgcolor), NULL);
+		fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 		showform(stdout, hffile, formfn, COL_BLUE, seltime, NULL, NULL);
 	}
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-eventlog.c ./web/bb-eventlog.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-eventlog.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-eventlog.c	2006-10-03 12:55:27.000000000 +0200
@@ -114,6 +114,8 @@
 
 	redirect_cgilog("bb-eventlog");
 
+	fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
+
 	cgidata = cgi_request();
 	if (cgidata == NULL) {
 		/* Present the query form */
@@ -126,8 +128,6 @@
 	load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
 
 	/* Now generate the webpage */
-	printf("Content-Type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
-
 	headfoot(stdout, "event", "", "header", COL_GREEN);
 	fprintf(stdout, "<center>\n");
 	do_eventlog(stdout, maxcount, maxminutes, fromtime, totime, 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-findhost.c ./web/bb-findhost.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-findhost.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-findhost.c	2006-10-03 12:55:27.000000000 +0200
@@ -152,6 +152,7 @@
 	if (cgidata == NULL) {
 		/* Present the query form */
 		sethostenv("", "", "", colorname(COL_BLUE), NULL);
+		printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 		showform(stdout, "findhost", "findhost_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL);
 		return 0;
 	}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-rep.c ./web/bb-rep.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-rep.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-rep.c	2006-10-03 12:55:27.000000000 +0200
@@ -230,6 +230,7 @@
 	if (cgidata == NULL) {
 		/* Present the query form */
 		sethostenv("", "", "", colorname(COL_BLUE), NULL);
+		printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 		showform(stdout, "report", "report_form", COL_BLUE, getcurrenttime(NULL)-86400, NULL, NULL);
 		return 0;
 	}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-snapshot.c ./web/bb-snapshot.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/bb-snapshot.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/bb-snapshot.c	2006-10-03 12:55:27.000000000 +0200
@@ -173,6 +173,7 @@
 	if (cgidata == NULL) {
 		/* Present the query form */
 		sethostenv("", "", "", colorname(COL_BLUE), NULL);
+		printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 		showform(stdout, "snapshot", "snapshot_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL);
 		return 0;
 	}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport-critical.sh.DIST ./web/hobbit-confreport-critical.sh.DIST
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport-critical.sh.DIST	1970-01-01 01:00:00.000000000 +0100
+++ ./web/hobbit-confreport-critical.sh.DIST	2006-10-03 12:55:27.000000000 +0200
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# This is the Hobbit CGI script interface to hobbit-confreport.cgi
+# It shows only the statuses on the Critical systems view
+#
+# Install this script in your webservers' cgi-bin directory
+
+. @BBHOME@/etc/hobbitcgi.cfg
+@RUNTIMEDEFS@ exec @BBHOME@/bin/hobbit-confreport.cgi $CGI_HOBBITCONFREPORT_OPTS --critical
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport.c ./web/hobbit-confreport.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/hobbit-confreport.c	2006-10-03 12:56:57.000000000 +0200
@@ -8,7 +8,7 @@
 /*                                                                            */
 /*----------------------------------------------------------------------------*/
 
-static char rcsid[] = "$Id: hobbit-confreport.c,v 1.15 2006/05/31 16:12:38 henrik Rel $";
+static char rcsid[] = "$Id: hobbit-confreport.c,v 1.18 2006/08/19 06:25:27 henrik Exp $";
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -45,6 +45,8 @@
 static char *coldelim = ";";
 static coltext_t *chead = NULL;
 static int ccount = 0;
+static int nkonly = 0;
+static int newnkconfig = 1;
 
 void errormsg(char *msg)
 {
@@ -131,6 +133,39 @@
 	closedir(d);
 }
 
+char *nkval(char *hname, char *tname, char *nkalerts)
+{
+	static char *result = NULL;
+
+	if (result) xfree(result);
+
+	if (newnkconfig) {
+		char *key;
+		nkconf_t *nkrec;
+
+		key = (char *)malloc(strlen(hname) + strlen(tname) + 2);
+		sprintf(key, "%s|%s", hname, tname);
+		nkrec = get_nkconfig(key, NKCONF_FIRSTMATCH, NULL);
+		if (!nkrec) {
+			result = strdup("No");
+		}
+		else {
+			char *tspec;
+
+			tspec = (nkrec->nktime ? timespec_text(nkrec->nktime) : "24x7");
+			result = (char *)malloc(strlen(tspec) + 30);
+			sprintf(result, "%s&nbsp;prio&nbsp;%d", tspec, nkrec->priority);
+		}
+		xfree(key);
+	}
+	else {
+		result = strdup((checkalert(nkalerts, tname) ? "Yes" : "No"));
+	}
+
+	return result;
+}
+
+
 static void print_host(hostlist_t *host, htnames_t *testnames[], int testcount)
 {
 	int testi, rowcount, netcount;
@@ -157,6 +192,7 @@
 	comment = bbh_item(hinfo, BBH_COMMENT);
 	description = bbh_item(hinfo, BBH_DESCRIPTION); 
 	net = bbh_item(hinfo, BBH_NET);
+	nkalerts = bbh_item(hinfo, BBH_NK);
 	nktime = bbh_item(hinfo, BBH_NKTIME); if (!nktime) nktime = "24x7"; else nktime = strdup(timespec_text(nktime));
 	downtime = bbh_item(hinfo, BBH_DOWNTIME); if (downtime) downtime = strdup(timespec_text(downtime));
 	reporttime = bbh_item(hinfo, BBH_REPORTTIME); if (!reporttime) reporttime = "24x7"; else reporttime = strdup(timespec_text(reporttime));
@@ -166,7 +202,7 @@
 	if (dispname || clientalias) rowcount++;
 	if (comment) rowcount++;
 	if (description) rowcount++;
-	if (nktime) rowcount++;
+	if (!newnkconfig && nktime) rowcount++;
 	if (downtime) rowcount++;
 	if (reporttime) rowcount++;
 
@@ -185,13 +221,11 @@
 	if (pagepathtitle) fprintf(stdout, "<tr><td>Monitoring location: %s</td></tr>\n", pagepathtitle);
 	if (comment) fprintf(stdout, "<tr><td>Comment: %s</td></tr>\n", comment);
 	if (description) fprintf(stdout, "<tr><td>Description: %s</td></tr>\n", description);
-	if (nktime) fprintf(stdout, "<tr><td>NK monitoring period: %s</td></tr>\n", nktime);
+	if (!newnkconfig && nktime) fprintf(stdout, "<tr><td>NK monitoring period: %s</td></tr>\n", nktime);
 	if (downtime) fprintf(stdout, "<tr><td>Planned downtime: %s</td></tr>\n", downtime);
 	if (reporttime) fprintf(stdout, "<tr><td>SLA Reporting Period: %s</td></tr>\n", reporttime);
 
 
-	nkalerts = bbh_item(hinfo, BBH_NK);
-
 	/* Build a list of the network tests */
 	itm = bbh_item_walk(hinfo);
 	while (itm) {
@@ -344,7 +378,7 @@
 		use_columndoc(testnames[testi]->name);
 		fprintf(stdout, "<tr>");
 		fprintf(stdout, "<td valign=top>%s</td>", testnames[testi]->name);
-		fprintf(stdout, "<td valign=top>%s</td>", (checkalert(nkalerts, testnames[testi]->name) ? "Yes" : "No"));
+		fprintf(stdout, "<td valign=top>%s</td>", nkval(host->hostname, testnames[testi]->name, nkalerts));
 
 		fprintf(stdout, "<td valign=top>");
 		if (twalk->b1 || twalk->b2 || twalk->b3) {
@@ -384,7 +418,7 @@
 		use_columndoc(testnames[testi]->name);
 		fprintf(stdout, "<tr>");
 		fprintf(stdout, "<td valign=top>%s</td>", testnames[testi]->name);
-		fprintf(stdout, "<td valign=top>%s</td>", (checkalert(nkalerts, testnames[testi]->name) ? "Yes" : "No"));
+		fprintf(stdout, "<td valign=top>%s</td>", nkval(host->hostname, testnames[testi]->name, nkalerts));
 		fprintf(stdout, "<td valign=top>-/-/-</td>");
 
 		/* Make up some default configuration data */
@@ -599,8 +633,8 @@
 {
 	int argi, hosti, testi;
 	char *pagepattern = NULL, *hostpattern = NULL;
-	char *envarea = NULL, *cookie = NULL, *p, *nexthost;
-	char hobbitcmd[1024], procscmd[1024], svcscmd[1024];
+	char *envarea = NULL, *cookie = NULL, *nexthost;
+	char *hobbitcmd, *procscmd, *svcscmd;
         int alertcolors, alertinterval;
 	char configfn[PATH_MAX];
 	char *respbuf = NULL, *procsbuf = NULL, *svcsbuf = NULL;
@@ -627,38 +661,50 @@
 			char *p = strchr(argv[argi], '=');
 			coldelim = strdup(p+1);
 		}
+		else if (strcmp(argv[argi], "--critical") == 0) {
+			nkonly = 1;
+		}
+		else if (strcmp(argv[argi], "--old-nk-config") == 0) {
+			newnkconfig = 0;
+		}
 	}
 
 	redirect_cgilog("hobbit-confreport");
 
-	/* Setup the filter we use for the report */
-	cookie = getenv("HTTP_COOKIE");
-	if (cookie && ((p = strstr(cookie, "pagepath=")) != NULL)) {
-		p += strlen("pagepath=");
-		pagepattern = strdup(p);
-		p = strchr(pagepattern, ';'); if (p) *p = '\0';
-		if (strlen(pagepattern) == 0) { xfree(pagepattern); pagepattern = NULL; }
-	}
+	load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
+	load_nkconfig(NULL);
 
-	if (cookie && (!pagepattern) && ((p = strstr(cookie, "host=")) != NULL)) {
-		p += strlen("host=");
-		hostpattern = strdup(p);
-		p = strchr(hostpattern, ';'); if (p) *p = '\0';
-		if (strlen(hostpattern) == 0) { xfree(hostpattern); hostpattern = NULL; }
-	}
+	/* Setup the filter we use for the report */
+	cookie = get_cookie("pagepath"); if (cookie && *cookie) pagepattern = strdup(cookie);
+	cookie = get_cookie("host");     if (cookie && *cookie) hostpattern = strdup(cookie);
 
 	/* Fetch the list of host+test statuses we currently know about */
 	if (pagepattern) {
-		sprintf(hobbitcmd, "hobbitdboard page=%s fields=hostname,testname", pagepattern);
-		sprintf(procscmd,  "hobbitdboard page=%s test=procs fields=hostname,msg", pagepattern);
-		sprintf(svcscmd,   "hobbitdboard page=%s test=svcs fields=hostname,msg", pagepattern);
+		hobbitcmd = (char *)malloc(2*strlen(pagepattern) + 1024);
+		procscmd = (char *)malloc(2*strlen(pagepattern) + 1024);
+		svcscmd = (char *)malloc(2*strlen(pagepattern) + 1024);
+
+		sprintf(hobbitcmd, "hobbitdboard page=^%s$|^%s/.+ fields=hostname,testname", 
+			pagepattern, pagepattern);
+		sprintf(procscmd,  "hobbitdboard page=^%s$|^%s/.+ test=procs fields=hostname,msg",
+			pagepattern, pagepattern);
+		sprintf(svcscmd,   "hobbitdboard page=^%s$|^%s/.+ test=svcs fields=hostname,msg",
+			pagepattern, pagepattern);
 	}
 	else if (hostpattern) {
-		sprintf(hobbitcmd, "hobbitdboard host=%s fields=hostname,testname", hostpattern);
-		sprintf(procscmd,  "hobbitdboard host=%s test=procs fields=hostname,msg", hostpattern);
-		sprintf(svcscmd,   "hobbitdboard host=%s test=svcs fields=hostname,msg", hostpattern);
+		hobbitcmd = (char *)malloc(strlen(hostpattern) + 1024);
+		procscmd = (char *)malloc(strlen(hostpattern) + 1024);
+		svcscmd = (char *)malloc(strlen(hostpattern) + 1024);
+
+		sprintf(hobbitcmd, "hobbitdboard host=^%s$ fields=hostname,testname", hostpattern);
+		sprintf(procscmd,  "hobbitdboard host=^%s$ test=procs fields=hostname,msg", hostpattern);
+		sprintf(svcscmd,   "hobbitdboard host=^%s$ test=svcs fields=hostname,msg", hostpattern);
 	}
 	else {
+		hobbitcmd = (char *)malloc(1024);
+		procscmd = (char *)malloc(1024);
+		svcscmd = (char *)malloc(1024);
+
 		sprintf(hobbitcmd, "hobbitdboard fields=hostname,testname");
 		sprintf(procscmd,  "hobbitdboard test=procs fields=hostname,msg");
 		sprintf(svcscmd,   "hobbitdboard test=svcs fields=hostname,msg");
@@ -686,12 +732,20 @@
 	nexthost = respbuf;
 	do {
 		char *hname, *tname, *eoln;
+		int wanted = 1;
 
 		eoln = strchr(nexthost, '\n'); if (eoln) *eoln = '\0';
 		hname = nexthost;
 		tname = strchr(nexthost, '|'); if (tname) { *tname = '\0'; tname++; }
 
-		if (hname && tname && strcmp(hname, "summary") && strcmp(tname, xgetenv("INFOCOLUMN")) && strcmp(tname, xgetenv("TRENDSCOLUMN"))) {
+		if (nkonly) {
+			namelist_t *hinfo = hostinfo(hname);
+			char *nkalerts = bbh_item(hinfo, BBH_NK);
+
+			if (!nkalerts || (strcmp(nkval(hname, tname, nkalerts), "No") == 0)) wanted = 0;
+		}
+
+		if (wanted && hname && tname && strcmp(hname, "summary") && strcmp(tname, xgetenv("INFOCOLUMN")) && strcmp(tname, xgetenv("TRENDSCOLUMN"))) {
 			htnames_t *newitem = (htnames_t *)malloc(sizeof(htnames_t));
 
 			for (hwalk = hosthead; (hwalk && strcmp(hwalk->hostname, hname)); hwalk = hwalk->next);
@@ -726,7 +780,6 @@
 	qsort(&allhosts[0], hostcount, sizeof(hostlist_t **), host_compare);
 
 	/* Get the static info */
-	load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
 	load_all_links();
 	init_tcp_services();
 	pingcolumn = xgetenv("PINGCOLUMN");
@@ -753,6 +806,9 @@
 		fprintf(stdout, "%s ", allhosts[hosti]->hostname);
 	}
 	fprintf(stdout, "</td></tr>\n");
+	if (nkonly) {
+		fprintf(stdout, "<tr><th valign=top align=left>Filter</th><td>Only data for the &quot;Critical Systems&quot; view reported</td></tr>\n");
+	}
 	fprintf(stdout, "</table>\n");
 
 	headfoot(stdout, "confreport", "", "front", COL_BLUE);
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport.cgi.1 ./web/hobbit-confreport.cgi.1
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-confreport.cgi.1	2006-08-09 22:10:12.000000000 +0200
+++ ./web/hobbit-confreport.cgi.1	2006-10-03 12:55:27.000000000 +0200
@@ -23,6 +23,14 @@
 the Hobbit system.
 
 .SH OPTIONS
+.IP "--critical"
+Report only on the statuses that are configured to show up on the
+\fBCritical Systems\fR view.
+
+.IP "--old-nk-config"
+Use the deprecated \fBNK\fR tag in bb-hosts to determine if tests
+appear on the Critical Systems view.
+
 .IP "--env=FILENAME"
 Loads the environment defined in FILENAME before executing the CGI script.
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-enadis.c ./web/hobbit-enadis.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-enadis.c	2006-08-09 22:10:12.000000000 +0200
+++ ./web/hobbit-enadis.c	2006-10-03 12:55:27.000000000 +0200
@@ -289,21 +289,15 @@
 
 	if (cgi_method == CGI_GET) {
 		/*
-		 * It's a GET , so the initial request.
+		 * It's a GET, so the initial request.
 		 * If we have a pagepath cookie, use that as the initial
 		 * host-name filter.
 		 */
-		char *cookie, *p;
+		char *pagepath;
 
 		action = ACT_FILTER;
-
-		cookie = getenv("HTTP_COOKIE");
-		if (obeycookies && cookie && ((p = strstr(cookie, "pagepath=")) != NULL)) {
-			p += strlen("pagepath=");
-			pagepattern = strdup(p);
-			p = strchr(pagepattern, ';'); if (p) *p = '\0';
-			if (strlen(pagepattern) == 0) { xfree(pagepattern); pagepattern = 0; }
-		}
+		pagepath = get_cookie("pagepath");
+		if (obeycookies && pagepath && *pagepath) pagepattern = strdup(pagepath);
 	}
 
 	if (action == ACT_FILTER) {
@@ -312,6 +306,7 @@
 		load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
 		sethostenv("", "", "", colorname(COL_BLUE), NULL);
 		sethostenv_filter(hostpattern, pagepattern, ippattern);
+		printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 		showform(stdout, "maint", "maint_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL);
 		return 0;
 	}
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-hostgraphs.c ./web/hobbit-hostgraphs.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-hostgraphs.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbit-hostgraphs.c	2006-10-03 12:55:27.000000000 +0200
@@ -209,24 +209,13 @@
 	fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 
 	if (action == A_SELECT) {
-                char *cookie, *p;
+                char *cookie;
 
-		cookie = getenv("HTTP_COOKIE");
-		if (cookie && !pagepattern && ((p = strstr(cookie, "pagepath=")) != NULL)) {
-			/* Match ONLY the exact pagename by using start/end of line markers */
-
-			p += strlen("pagepath=");
-			pagepattern = (char *)malloc(strlen(p) + 3);
-			sprintf(pagepattern, "^%s", p);
-			p = strchr(pagepattern, ';'); if (p) *p = '\0';
-
-			if (strlen(pagepattern) == 0) { 
-				xfree(pagepattern); 
-				pagepattern = NULL;
-			}
-			else {
-				strcat(pagepattern, "$");
-			}
+		cookie = get_cookie("pagepath");
+		if (!pagepattern && cookie && *cookie) {
+			/* Match the exact pagename and sub-pages */
+			pagepattern = (char *)malloc(10 + 2*strlen(cookie));
+			sprintf(pagepattern, "^%s$|^%s/.+", cookie, cookie);
 		}
 
 		if (hostpattern || pagepattern || ippattern)
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-nkedit.c ./web/hobbit-nkedit.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-nkedit.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbit-nkedit.c	2006-10-03 12:55:27.000000000 +0200
@@ -238,6 +238,7 @@
 	if (isaclone && isclonewarning) sprintf(warnmsg, "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('%s'); </SCRIPT>\n", isclonewarning);
 	if (hasclones && hascloneswarning) sprintf(warnmsg, "<SCRIPT LANGUAGE=\"Javascript\" type=\"text/javascript\"> alert('%s'); </SCRIPT>\n", hascloneswarning);
 
+	printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
 	showform(stdout, "nkedit", "nkedit_form", COL_BLUE, getcurrenttime(NULL), warnmsg, NULL);
 }
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-nkview.c ./web/hobbit-nkview.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-nkview.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbit-nkview.c	2006-10-03 12:55:27.000000000 +0200
@@ -46,8 +46,10 @@
 	char *bol, *eol;
 	time_t now;
 	char msg[1024];
+	int i;
 
-	sprintf(msg, "hobbitdboard color=red,yellow acklevel=%d fields=hostname,testname,color,lastchange,logtime,validtime,acklist", nkacklevel);
+	sprintf(msg, "hobbitdboard acklevel=%d fields=hostname,testname,color,lastchange,logtime,validtime,acklist color=%s", nkacklevel,colorname(mincolor));
+	for (i=mincolor+1; (i < COL_COUNT); i++) sprintf(msg+strlen(msg), ",%s", colorname(i));
 
 	hobbitdresult = sendmessage(msg, NULL, NULL, &board, 1, BBTALK_TIMEOUT);
 	if (hobbitdresult != BB_OK) {
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-notifylog.c ./web/hobbit-notifylog.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-notifylog.c	1970-01-01 01:00:00.000000000 +0100
+++ ./web/hobbit-notifylog.c	2007-02-09 11:07:18.206269768 +0100
@@ -0,0 +1,138 @@
+/*----------------------------------------------------------------------------*/
+/* Hobbit notification log viewer                                             */
+/*                                                                            */
+/* Copyright (C) 2007 Henrik Storner <henrik@storner.dk>                      */
+/*                                                                            */
+/* This program is released under the GNU General Public License (GPL),       */
+/* version 2. See the file "COPYING" for details.                             */
+/*                                                                            */
+/*----------------------------------------------------------------------------*/
+
+static char rcsid[] = "$Id: hobbit-notifylog.c,v 1.2 2007/02/07 21:49:47 henrik Exp $";
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+
+#include "libbbgen.h"
+
+int	maxcount = 100;		/* Default: Include last 100 events */
+int	maxminutes = 1440;	/* Default: for the past 24 hours */
+char	*totime = NULL;
+char	*fromtime = NULL;
+char	*hostregex = NULL;
+char	*exhostregex = NULL;
+char	*testregex = NULL;
+char	*extestregex = NULL;
+char	*pageregex = NULL;
+char	*expageregex = NULL;
+char	*rcptregex = NULL;
+char	*exrcptregex = NULL;
+cgidata_t *cgidata = NULL;
+
+static void parse_query(void)
+{
+	cgidata_t *cwalk;
+
+	cwalk = cgidata;
+	while (cwalk) {
+		/*
+		 * cwalk->name points to the name of the setting.
+		 * cwalk->value points to the value (may be an empty string).
+		 */
+
+		if (strcasecmp(cwalk->name, "MAXCOUNT") == 0) {
+			maxcount = atoi(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "MAXTIME") == 0) {
+			maxminutes = atoi(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "FROMTIME") == 0) {
+			if (*(cwalk->value)) fromtime = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "TOTIME") == 0) {
+			if (*(cwalk->value)) totime = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "HOSTMATCH") == 0) {
+			if (*(cwalk->value)) hostregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "EXHOSTMATCH") == 0) {
+			if (*(cwalk->value)) exhostregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "TESTMATCH") == 0) {
+			if (*(cwalk->value)) testregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "EXTESTMATCH") == 0) {
+			if (*(cwalk->value)) extestregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "PAGEMATCH") == 0) {
+			if (*(cwalk->value)) pageregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "EXPAGEMATCH") == 0) {
+			if (*(cwalk->value)) expageregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "RCPTMATCH") == 0) {
+			if (*(cwalk->value)) rcptregex = strdup(cwalk->value);
+		}
+		else if (strcasecmp(cwalk->name, "EXRCPTMATCH") == 0) {
+			if (*(cwalk->value)) exrcptregex = strdup(cwalk->value);
+		}
+
+		cwalk = cwalk->next;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int argi;
+	char *envarea = NULL;
+
+	for (argi=1; (argi < argc); argi++) {
+		if (argnmatch(argv[argi], "--env=")) {
+			char *p = strchr(argv[argi], '=');
+			loadenv(p+1, envarea);
+		}
+		else if (argnmatch(argv[argi], "--area=")) {
+			char *p = strchr(argv[argi], '=');
+			envarea = strdup(p+1);
+		}
+	}
+
+	redirect_cgilog("hobbit-notifylog");
+
+	fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
+
+	cgidata = cgi_request();
+	if (cgidata == NULL) {
+		/* Present the query form */
+		sethostenv("", "", "", colorname(COL_BLUE), NULL);
+		showform(stdout, "notify", "notify_form", COL_BLUE, getcurrenttime(NULL), NULL, NULL);
+		return 0;
+	}
+
+	parse_query();
+	load_hostnames(xgetenv("BBHOSTS"), NULL, get_fqdn());
+
+	/* Now generate the webpage */
+	headfoot(stdout, "notify", "", "header", COL_GREEN);
+	fprintf(stdout, "<center>\n");
+	do_notifylog(stdout, maxcount, maxminutes, fromtime, totime, 
+			pageregex, expageregex, 
+			hostregex, exhostregex, 
+			testregex, extestregex,
+			rcptregex, exrcptregex);
+	fprintf(stdout, "</center>\n");
+	headfoot(stdout, "notify", "", "footer", COL_GREEN);
+
+	return 0;
+}
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-notifylog.sh.DIST ./web/hobbit-notifylog.sh.DIST
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-notifylog.sh.DIST	1970-01-01 01:00:00.000000000 +0100
+++ ./web/hobbit-notifylog.sh.DIST	2007-02-09 11:07:23.802253102 +0100
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# This is the Hobbit wrapper for the notifylog CGI
+
+. @BBHOME@/etc/hobbitcgi.cfg
+@RUNTIMEDEFS@ exec @BBHOME@/bin/hobbit-notifylog.cgi $CGI_NOTIFYLOG_OPTS
+
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-statusreport.c ./web/hobbit-statusreport.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbit-statusreport.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbit-statusreport.c	2006-10-03 12:55:27.000000000 +0200
@@ -26,7 +26,7 @@
 {
 	char *envarea = NULL;
 	char *server = NULL;
-	char *cookie, *p, *pagefilter = "";
+	char *cookie, *pagefilter = "";
 	char *filter = NULL;
 	char *heading = NULL;
 	int  showcolors = 1;
@@ -108,13 +108,10 @@
 
 	if (!allhosts) {
       		/* Setup the filter we use for the report */
-		cookie = getenv("HTTP_COOKIE");
-		if (cookie && ((p = strstr(cookie, "pagepath=")) != NULL)) {
-			p += strlen("pagepath=");
-			pagefilter = malloc(strlen(p) + 6);
-			sprintf(pagefilter, "page=%s", p);
-			p = strchr(pagefilter, ';'); if (p) *p = '\0';
-			if (strlen(pagefilter) == 0) { xfree(pagefilter); pagefilter = ""; }
+		cookie = get_cookie("pagepath");
+		if (cookie && *cookie) {
+			pagefilter = malloc(10 + 2*strlen(cookie));
+			sprintf(pagefilter, "page=^%s$|^%s/.+", cookie, cookie);
 		}
 	}
 
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitgraph.c ./web/hobbitgraph.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitgraph.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbitgraph.c	2006-10-03 12:55:27.000000000 +0200
@@ -63,6 +63,7 @@
 int graphwidth = 0;
 int graphheight = 0;
 int ignorestalerrds = 0;
+int bgcolor = COL_GREEN;
 
 int coloridx = 0;
 char *colorlist[] = { 
@@ -216,6 +217,10 @@
 		else if (strcmp(cwalk->name, "nostale") == 0) {
 			ignorestalerrds = 1;
 		}
+		else if (strcmp(cwalk->name, "color") == 0) {
+			int color = parse_color(cwalk->value);
+			if (color != -1) bgcolor = color;
+		}
 
 		cwalk = cwalk->next;
 	}
@@ -440,8 +445,8 @@
 	  case ACT_MENU:
 		fprintf(output, "  <td align=\"left\"><img src=\"%s&amp;action=view&amp;graph=%s\" alt=\"%s graph\"></td>\n",
 			uri, grtype, grtype);
-		fprintf(output, "  <td align=\"left\" valign=\"top\"> <a href=\"%s&amp;graph=%s&amp;action=selzoom\"> <img src=\"%s/zoom.gif\" border=0 alt=\"Zoom graph\" style='padding: 3px'> </a> </td>\n",
-			uri, grtype, getenv("BBSKIN"));
+		fprintf(output, "  <td align=\"left\" valign=\"top\"> <a href=\"%s&amp;graph=%s&amp;action=selzoom&amp;color=%s\"> <img src=\"%s/zoom.gif\" border=0 alt=\"Zoom graph\" style='padding: 3px'> </a> </td>\n",
+			uri, grtype, colorname(bgcolor), getenv("BBSKIN"));
 		break;
 
 	  case ACT_SELZOOM:
@@ -538,8 +543,8 @@
 	else
 		p += sprintf(p, "?host=%s", hostname);
 
-	p += sprintf(p, "&amp;service=%s&amp;graph_height=%d&amp;graph_width=%d", 
-		     service, graphheight, graphwidth);
+	p += sprintf(p, "&amp;service=%s&amp;graph_height=%d&amp;graph_width=%d&amp;color=%s", 
+		     service, graphheight, graphwidth, colorname(bgcolor));
 	if (displayname != hostname) p += sprintf(p, "&amp;disp=%s", displayname);
 	if (firstidx != -1) p += sprintf(p, "&amp;first=%d", firstidx+1);
 	if (idxcount != -1) p += sprintf(p, "&amp;count=%d", idxcount);
@@ -548,8 +553,8 @@
 	switch (action) {
 	  case ACT_MENU:
 		fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
-		sethostenv(displayname, "", service, colorname(COL_GREEN), hostname);
-		headfoot(stdout, "graphs", "", "header", COL_GREEN);
+		sethostenv(displayname, "", service, colorname(bgcolor), hostname);
+		headfoot(stdout, "graphs", "", "header", bgcolor);
 
 		fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
 
@@ -560,13 +565,13 @@
 
 		fprintf(stdout, "</table>\n");
 
-		headfoot(stdout, "graphs", "", "footer", COL_GREEN);
+		headfoot(stdout, "graphs", "", "footer", bgcolor);
 		return 0;
 
 	  case ACT_SELZOOM:
 		fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
-		sethostenv(displayname, "", service, colorname(COL_GREEN), hostname);
-		headfoot(stdout, "graphs", "", "header", COL_GREEN);
+		sethostenv(displayname, "", service, colorname(bgcolor), hostname);
+		headfoot(stdout, "graphs", "", "header", bgcolor);
 
 
 		fprintf(stdout, "  <div id='zoomBox' style='position:absolute; overflow:none; left:0px; top:0px; width:0px; height:0px; visibility:visible; background:red; filter:alpha(opacity=50); -moz-opacity:0.5; -khtml-opacity:0.5'></div>\n");
@@ -608,13 +613,13 @@
 		}
 
 
-		headfoot(stdout, "graphs", "", "footer", COL_GREEN);
+		headfoot(stdout, "graphs", "", "footer", bgcolor);
 		return 0;
 
 	  case ACT_SHOWZOOM:
 		fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
-		sethostenv(displayname, "", service, colorname(COL_GREEN), hostname);
-		headfoot(stdout, "graphs", "", "header", COL_GREEN);
+		sethostenv(displayname, "", service, colorname(bgcolor), hostname);
+		headfoot(stdout, "graphs", "", "header", bgcolor);
 
 		fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
 
@@ -622,7 +627,7 @@
 
 		fprintf(stdout, "</table>\n");
 
-		headfoot(stdout, "graphs", "", "footer", COL_GREEN);
+		headfoot(stdout, "graphs", "", "footer", bgcolor);
 		return 0;
 
 	  case ACT_VIEW:
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitsvc-info.c ./web/hobbitsvc-info.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitsvc-info.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbitsvc-info.c	2006-10-03 13:52:29.000000000 +0200
@@ -40,7 +40,7 @@
 	char *name;
 	int color;
 	char *dismsg;
-	time_t distime;
+	time_t distime, lastchange;
 	struct hinf_t *next;
 } hinf_t;
 hinf_t *tnames = NULL;
@@ -73,7 +73,7 @@
 	int testsz;
 	int haveuname = 0;
 
-	sprintf(hobbitcmd, "hobbitdboard fields=testname,color,disabletime,dismsg,client host=%s", hostname);
+	sprintf(hobbitcmd, "hobbitdboard fields=testname,color,disabletime,dismsg,client,lastchange host=^%s$", hostname);
 	if (sendmessage(hobbitcmd, NULL, NULL, &statuslist, 1, BBTALK_TIMEOUT) != BB_OK) {
 		return 1;
 	}
@@ -94,7 +94,8 @@
 			if (tok) { tnames[testcount].color = parse_color(tok); tok = gettok(NULL, "|"); }
 			if (tok) { tnames[testcount].distime = atol(tok); tok = gettok(NULL, "|"); }
 			if (tok) { tnames[testcount].dismsg = strdup(tok); tok = gettok(NULL, "|"); }
-			if (tok) { haveuname |= (*tok == 'Y'); }
+			if (tok) { haveuname |= (*tok == 'Y'); tok = gettok(NULL, "|"); }
+			if (tok) { tnames[testcount].lastchange = atol(tok); }
 			tnames[testcount].next = NULL;
 			testcount++;
 			if (testcount == testsz) {
@@ -113,7 +114,7 @@
 
 	/* Sort them so the display looks prettier */
 	qsort(&tnames[0], testcount, sizeof(hinf_t), test_name_compare);
-	xfree(statuslist); statuslist = NULL;
+	if (statuslist) xfree(statuslist); statuslist = NULL;
 
 
 	sprintf(hobbitcmd, "schedule");
@@ -241,6 +242,108 @@
 }
 
 
+static void generate_hobbit_statuslist(char *hostname, strbuffer_t *buf)
+{
+	char msgline[4096];
+	char datestr[100];
+	int i, btncount;
+	char *bbdatefmt;
+	strbuffer_t *servRed, *servYellow, *servPurple, *servBlue;
+	time_t logage;
+
+	bbdatefmt = xgetenv("BBDATEFORMAT");
+
+	servRed = newstrbuffer(0);
+	servYellow = newstrbuffer(0);
+	servPurple = newstrbuffer(0);
+	servBlue = newstrbuffer(0);
+
+	addtobuffer(buf, "<tr><th align=left valign=top>Status summary</th><td align=left>\n");
+	addtobuffer(buf, "<form name=\"colorsel\" action=\"nosubmit\" method=\"GET\">\n");
+	addtobuffer(buf, "<table summary=\"Status summary\" border=1>\n");
+	addtobuffer(buf, "<tr><th>Service</th><th>Since</th><th>Duration</th></tr>\n");
+
+	for (i = 0; i < testcount; i++) {
+		strftime(datestr, sizeof(datestr), bbdatefmt, localtime(&tnames[i].lastchange));
+		logage = time(NULL) - tnames[i].lastchange;
+
+		addtobuffer(buf, "<tr>");
+
+		sprintf(msgline, "<td><img src=\"%s/%s\" height=\"%s\" width=\"%s\" border=0 alt=\"%s status\"> %s</td>",
+			xgetenv("BBSKIN"), dotgiffilename(tnames[i].color, 0, 1),
+			xgetenv("DOTHEIGHT"), xgetenv("DOTWIDTH"),
+			colorname(tnames[i].color), tnames[i].name);
+		addtobuffer(buf, msgline);
+
+		sprintf(msgline, "<td>%s</td>", datestr);
+		addtobuffer(buf, msgline);
+
+		sprintf(msgline, "<td align=right>%d days, %02d hours, %02d minutes</td>",
+			(int)(logage / 86400),(int) ((logage % 86400) / 3600),(int) ((logage % 3600) / 60));
+		addtobuffer(buf, msgline);
+
+		addtobuffer(buf, "</tr>\n");
+
+		sprintf(msgline, ",%s", tnames[i].name);
+		switch (tnames[i].color) {
+		  case COL_BLUE   : addtobuffer(servBlue, msgline);   break;
+		  case COL_RED    : addtobuffer(servRed, msgline);    break;
+		  case COL_YELLOW : addtobuffer(servYellow, msgline); break;
+		  case COL_PURPLE : addtobuffer(servPurple, msgline); break;
+		}
+	}
+
+	btncount = 0;
+	if (STRBUFLEN(servRed) > 0)    btncount++;
+	if (STRBUFLEN(servYellow) > 0) btncount++;
+	if (STRBUFLEN(servPurple) > 0) btncount++;
+	if (STRBUFLEN(servBlue) > 0)   btncount++;
+	if (btncount > 0) {
+		addtobuffer(buf, "<tr><td colspan=3>\n");
+
+		addtobuffer(buf, "<table width=\"100%\">\n");
+		sprintf(msgline, "<tr><th colspan=%d><center><i>Toggle tests to disable</i></center></th></tr>\n", btncount);
+		addtobuffer(buf, msgline);
+
+		addtobuffer(buf, "<tr>\n");
+		if (STRBUFLEN(servRed) > 0) {
+			addtobuffer(buf, "<td align=center><input type=button value=\"Toggle red\" onClick=\"mark4Disable('");
+			addtostrbuffer(buf, servRed);
+			addtobuffer(buf, ",');\"></td>\n");
+		} 
+		if (STRBUFLEN(servYellow) > 0) {
+			addtobuffer(buf, "<td align=center><input type=button value=\"Toggle yellow\" onClick=\"mark4Disable('");
+			addtostrbuffer(buf, servYellow);
+			addtobuffer(buf, ",');\"></td>\n");
+		} 
+		if (STRBUFLEN(servPurple) > 0) {
+			addtobuffer(buf, "<td align=center><input type=button value=\"Toggle purple\" onClick=\"mark4Disable('");
+			addtostrbuffer(buf, servPurple);
+			addtobuffer(buf, ",');\"></td>\n");
+		} 
+		if (STRBUFLEN(servBlue) > 0) {
+			addtobuffer(buf, "<td align=center><input type=button value=\"Toggle blue\" onClick=\"mark4Disable('");
+			addtostrbuffer(buf, servBlue);
+			addtobuffer(buf, ",');\"></td>\n");
+		} 
+
+		addtobuffer(buf, "</tr>\n");
+		addtobuffer(buf, "</table>\n");
+
+		addtobuffer(buf,"</td></tr>\n");
+
+	}
+
+	addtobuffer(buf,"</table></form>\n");
+	addtobuffer(buf, "</td></tr>\n");
+	addtobuffer(buf, "<tr><td colspan=2>&nbsp;</td></tr>\n");
+
+	freestrbuffer(servRed);
+	freestrbuffer(servYellow);
+	freestrbuffer(servPurple);
+	freestrbuffer(servBlue);
+}
+
 static void generate_hobbit_disable(char *hostname, strbuffer_t *buf)
 {
 	int i;
@@ -256,7 +359,7 @@
 	beginyear = nowtm->tm_year + 1900;
 	endyear = nowtm->tm_year + 1900 + 5;
 
-	sprintf(l, "<form method=\"post\" action=\"%s/hobbit-enadis.sh\">\n", xgetenv("SECURECGIBINURL"));
+	sprintf(l, "<form name=\"disableform\" method=\"post\" action=\"%s/hobbit-enadis.sh\">\n", xgetenv("SECURECGIBINURL"));
 	addtobuffer(buf, l);
 	sprintf(l, "<table summary=\"%s disable\" border=1>\n", hostname);
 	addtobuffer(buf, l);
@@ -266,7 +369,18 @@
 	addtobuffer(buf, "<td rowspan=2><select multiple size=\"15\" name=\"disabletest\">\n");
 	addtobuffer(buf, "<option value=\"*\">ALL</option>\n");
 	for (i=0; (i < testcount); i++) {
-		sprintf(l, "<option value=\"%s\">%s</option>\n", tnames[i].name, tnames[i].name);
+		char *colstyle;
+		switch (tnames[i].color) {
+		  case COL_RED:	   colstyle = "color: red"; break;
+		  case COL_YELLOW: colstyle = "color: #FFDE0F"; break;
+		  case COL_GREEN:  colstyle = "color: green"; break;
+		  case COL_BLUE:   colstyle = "color: blue;"; break;
+		  case COL_PURPLE: colstyle = "color: fuchsia;"; break;
+		  default:         colstyle = "color: black;"; break;
+		}
+
+		sprintf(l, "<option value=\"%s\" style=\"%s\">%s</option>\n", 
+			tnames[i].name, colstyle, tnames[i].name);
 		addtobuffer(buf, l);
 	}
 	addtobuffer(buf, "</select></td>\n");
@@ -544,7 +658,7 @@
 	if (strcmp(val, "0.0.0.0") == 0) {
 		struct in_addr addr;
 		struct hostent *hent;
-		static char hostip[IP_ADDR_STRLEN];
+		static char hostip[IP_ADDR_STRLEN + 20];
 
 		hent = gethostbyname(hostname);
 		if (hent) {
@@ -809,6 +923,7 @@
 	if (gotstatus && showenadis) {
 		int i, anydisabled = 0;
 
+		generate_hobbit_statuslist(hostname, infobuf);
 		addtobuffer(infobuf, "<tr><th align=left valign=top>Disable tests</th><td align=left>\n");
 		generate_hobbit_disable(hostname, infobuf);
 		addtobuffer(infobuf, "</td></tr>\n");
diff -urN /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitsvc-trends.c ./web/hobbitsvc-trends.c
--- /home/henrik/hobbit/release/hobbit-4.2.0/web/hobbitsvc-trends.c	2006-08-09 22:10:13.000000000 +0200
+++ ./web/hobbitsvc-trends.c	2006-10-03 12:55:27.000000000 +0200
@@ -125,7 +125,7 @@
 	/* If no rrdgraphs definition, include all with default links */
 	if (hostrrdgraphs == NULL) {
 		dbgprintf("rrdlink_text: Standard URL (no rrdgraphs)\n");
-		return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, rrd->gdef, rrd->count, 
+		return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, -1, rrd->gdef, rrd->count, 
 					 HG_WITH_STALE_RRDS, wantmeta);
 	}
 
@@ -141,7 +141,7 @@
 			dbgprintf("rrdlink_text: Default URL included\n");
 
 			/* Yes, return default link for this RRD */
-			return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, rrd->gdef, rrd->count, 
+			return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, -1, rrd->gdef, rrd->count, 
 						 HG_WITH_STALE_RRDS, wantmeta);
 		}
 		else {
@@ -193,7 +193,7 @@
 			myrrd->gdef->maxgraphs = 0;
 			myrrd->count = rrd->count;
 			myrrd->next = NULL;
-			partlink = hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, myrrd->gdef, myrrd->count, 
+			partlink = hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, -1, myrrd->gdef, myrrd->count, 
 						     HG_WITH_STALE_RRDS, wantmeta);
 			if ((strlen(rrdlink) + strlen(partlink) + 1) >= rrdlinksize) {
 				rrdlinksize += strlen(partlink) + 4096;
@@ -215,7 +215,7 @@
 	}
 	else {
 		/* It is included with the default graph */
-		return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, rrd->gdef, rrd->count, 
+		return hobbit_graph_data(host->bbhostname, hostdisplayname, NULL, -1, rrd->gdef, rrd->count, 
 					 HG_WITH_STALE_RRDS, wantmeta);
 	}