Imported Debian patch 5.1-7
[debian/gcpegg] / basket.c
1 /* PROGRAM:     basket
2  * FILE:        $Header: /home/egg/src/RCS/basket.c,v 1.8 1999/02/28 19:58:49 ghn Exp $
3  * PURPOSE:     Main program for basket software
4  * AUTHOR:      Greg Nelson
5  * DATE:        98-06-28
6  *
7  * REVISED:
8  * $Log: basket.c,v $
9  * Revision 1.8  1999/02/28 19:58:49  ghn
10  * Version 5.1: Support for ip address masks to allow us to limit egg
11  * spoofing to users on a limited subnet (cf. EGG_DYNAMIC).  Added
12  * flexible interface/port cmdline arguments and config file arguments.
13  * Report the eggs we are accepting.
14  *
15  * Revision 1.7  1999/01/01 23:52:31  ghn
16  * Allow basket to start with no net connection and handle connection
17  * going up and down.
18  *
19  * Revision 1.6  1998/12/31 22:07:56  ghn
20  * Rev 5 code: includes multi-reg support, HTML, etc.
21  *
22  * Revision 1.5  1998/08/03 20:40:13  kelvin
23  * PACKETDUMP, SIGHUP reset when signal caught.
24  * 
25  * Revision 1.4  1998/08/01  18:52:49  ghn
26  * Added John's byte-order-independence changes.
27  *
28  * Revision 1.3  1998/08/01 17:02:05  ghn
29  * Incorporate John's Solaris fixes and terminate STAT lines properly.
30  *
31  * Revision 1.2  1998/07/21 11:44:04  ghn
32  * Added headers.
33  *
34  * Copyright 1998 - Greg Nelson
35  */
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <signal.h>
40 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <netdb.h>
44 #include <netinet/in.h>
45 #include <time.h>
46 #include "global.h"
47 #include "genlib.h"
48 #include "storage.h"
49 #include "network.h"
50 #include "errnos.h"
51 #include "version.h"
52
53 #define EGGSTATS        "egg.status"
54
55 static int32 BasketReceiveDataPacket(char *pktbuf, int16 isegg, int16 thisegg,
56                               struct sockaddr_in *rhost);
57 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence);
58 static void MakeSettings(SettingsPacket *pkt, uint16 eggid);
59 static void LoadRCFile(void);
60 static void LoadEggStats(void);
61 static void SaveEggStats(void);
62 static void handle_sighup(int arg);
63
64 EggEntry eggtable[MAX_EGGS];
65 BasketEntry baskettable[MAX_BASKETS];
66 EggHeader protocol;
67
68 short numeggs, numbaskets;
69 char    *pgmname;                     /* Program name from argv[0] */
70 char    *myaddr;                      /* Interface to bind */
71 int16   myport;                       /* Service port to bind */
72
73 static int htmlInterval = 0;          /* HTML status file update interval in seconds */
74 static char htmlFile[256] = "";       /* HTML status file name */
75 static uint32 upsince = 0;            /* Basket start time */
76 #define htmlReport (htmlFile[0] != 0) /* Make HTML report ? */
77
78 struct {
79     uint32 firstPacket;               /* Time of "first contact" with egg */
80     uint32 trials;                    /* Number of trials received */
81     uint32 missing;                   /* Number of missing samples */
82     double sum;                       /* Sum of trials to date */
83 } eggStatistics[MAX_EGGS];
84
85 /*  updateHTML  --  Update HTML status file if a decent interval
86                     has passed since the last update.  */
87
88 static void updateHTML(void)
89 {
90     if (htmlReport) {
91         int i;
92         static uint32 lastHtml = 0;
93         uint32 now = getzulutime(NULL);
94         char udate[256], ustime[256];
95         static char timeFormat[] = "%Y-%m-%d %T";
96
97         /* When first called, set the last HTML update interval to
98            the present moment.  This defers generation of the first
99            HTML report until htmlInterval elapses.  This prevents
100            issuing a potentially-misleading HTML report based on
101            incomplete information. */
102
103         if (lastHtml == 0) {
104             lastHtml = now;
105         }
106
107         if ((now - lastHtml) >= htmlInterval) {
108             FILE *hf;
109
110             lastHtml = now;
111             hf = fopen(htmlFile, "w");
112             if (hf == NULL) {
113                 fprintf(stderr, "Cannot open HTML status file %s.  Updates discarded.\n", htmlFile);
114                 htmlFile[0] = 0;      /* Suppress further updates */
115                 return;
116             }
117
118             strftime(udate, sizeof udate, timeFormat, gmtime((time_t *) &now));
119             strftime(ustime, sizeof ustime, timeFormat, gmtime((time_t *) &upsince));
120
121             fprintf(hf, "\
122 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n\
123 <html version=\"-//W3C//DTD HTML 3.2 Final//EN\">\n\
124 <head>\n\
125 <title>Basket %s Status Report</title>\n\
126 <meta http-equiv=\"Refresh\" content=\"%d\">\n\
127 </head>\n\
128 <body>\n\
129 <center>\n\
130 <h1>Basket %s Status Report</h1>\n\
131 <h3>Last update: %s UTC</h3>\n\
132 <h4>Basket started at %s UTC<br>\n\
133 %s</h4>\n\
134 </center>\n\
135 <center>\n\
136 <h2>Egg Status</h2>\n\
137 <table border cellpadding=4>\n\
138 <tr><th>Name<th>Number<th>Up Since<th>Last Packet<th>Active\n\
139 <th>Trials<th>Missed<th>Coverage<th>Mean\n\
140 ",
141             baskettable[0].name, htmlInterval, baskettable[0].name, udate, ustime, Version);
142
143             for (i = 0; i < numeggs; i++) {
144               char url1[256], url2[256];
145
146               if ((eggtable[i].lastupd == 0) || (eggStatistics[i].firstPacket == 0)) {
147                   strcpy(udate, "<td colspan=2 align=center><em>Never contacted</em>");
148                   strcpy(ustime, "");
149               } else {
150                   strftime(udate, sizeof udate, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggtable[i].lastupd));
151                   strftime(ustime, sizeof ustime, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggStatistics[i].firstPacket));
152               }
153               if (eggtable[i].url == NULL) {
154                   url1[0] = url2[0] = 0;
155               } else {
156                   sprintf(url1, "<a href=\"%s\">", eggtable[i].url);
157                   strcpy(url2, "</a>");
158               }
159               fprintf(hf, "<tr><td>%s%s%s<td align=center>%d%s%s<td align=center>%s\n\
160 ",
161                       url1,
162                       eggtable[i].name,
163                       url2,
164                       eggtable[i].id,
165                       ustime,
166                       udate,
167                       eggtable[i].setup ? "Yes" : "No"
168               );
169
170               if (eggStatistics[i].trials == 0) {
171                   fprintf(hf, "<td align=center>&nbsp;<td align=center>&nbsp;<td align=center>&nbsp;<td align=center>&nbsp;\n");
172               } else {
173                   uint32 coverage;
174
175                   /* The following expression computes "coverage" for
176                      the egg--the percentage of samples received
177                      to the number prescribed by the protocol
178                      for the time in which the egg has been in contact. */
179                   coverage = (100L * eggStatistics[i].trials * protocol.samp_rec) /
180                       (protocol.sec_rec * (now - eggStatistics[i].firstPacket));
181                   if (coverage > 100) {
182                       coverage = 100;
183                   }
184
185                   fprintf(hf, "<td align=center>%ld<td align=center>%ld<td align=center>%ld%%<td align=center>%.3f\n",
186                           eggStatistics[i].trials,
187                           eggStatistics[i].missing,
188                           coverage,
189                           eggStatistics[i].sum / eggStatistics[i].trials
190                   );
191               }
192             }
193
194
195             fprintf(hf, "\
196 </table>\n\
197 </center>\n\
198 <center>\n\
199 <h4>Egg Status Table Fields</h4>\n\
200 <table width=\"75%%\" cellpadding=3>\n\
201 <tr><td valign=top><b>Name</b> <td>Egg name<p>\n\
202 <tr><td valign=top><b>Number</b> <td>Egg number.  Thousands digit indicates type of random event generator.<p>\n\
203 <tr><td valign=top><b>Up Since</b> <td>Time and date in first packet received from egg since basket started.<p>\n\
204 <tr><td valign=top><b>Last Packet</b> <td>Time and date of most recently received packet from egg.<p>\n\
205 <tr><td valign=top><b>Active</b> <td>Has egg ever communicated with this basket since it was started?<p>\n\
206 <tr><td valign=top><b>Trials</b> <td>Number of trials received from this egg.<p>\n\
207 <tr><td valign=top><b>Missed</b> <td>Number of scheduled trials missed by egg due to synchronisation problems.<p>\n\
208 <tr><td valign=top><b>Coverage</b> <td>Percent of trials collected from this egg since basket\n\
209     started compared to the number scheduled by the protocol.<p>\n\
210 <tr><td valign=top><b>Mean</b> <td>Arithmetic mean of trials performed by this egg.\n\
211 </table>\n\
212 </center>\n\
213 </body>\n\
214 </html>\n");
215             fclose(hf);
216 #ifdef DEBUG
217             fprintf(stderr, "Updating HTML file %s at %s",
218                 htmlFile, asctime(gmtime((time_t *) &now)));
219 #endif
220         }
221     }
222 }
223
224 static void Usage(void) {
225   printf("Usage: %s [-i interface] [-p port]\n", pgmname);
226   exit(-1);
227 }
228
229 /*  Main program.  */
230
231 int main(int argc, char *argv[]) {
232   char                  *pktbuf;
233   uint16                pkttype, pktsize, pkt_eggid;
234   int                   sdlisten, res;
235   struct sockaddr_in    rhost;
236   ReqPacket             rpkt;
237   SettingsPacket        spkt;
238   int                   i, isbasket, isegg, thisegg;
239   char                  *remname;
240 #ifdef SIGACTION_WORKING
241   struct sigaction      sigact;
242 #endif
243 #ifdef EGG_DYNAMIC 
244   uint32                ipvalidate, ipmatch, ipmask;
245 #endif
246
247   pgmname = argv[0];
248   argv++; argc--;
249
250   /* Defaults correspond to original usage */
251   myport = BASKETPORT;
252   myaddr = NULL;
253
254   while (argc) {
255     if (argv[0][0] == '-') {
256       switch(argv[0][1]) {
257       case 'i': /* Specify interface */
258         if (argc < 2) Usage();
259         myaddr = argv[1];
260         argv++; argc--;
261         break;
262       case 'p': /* Specify port */
263         if (argc < 2) Usage();
264         myport = atoi(argv[1]);
265         if (myport < 0) Usage();
266         argv++; argc--;
267         break;
268       default:  /* Oops. */
269         Usage();
270       }
271     } else {
272       Usage();
273     }
274     argv++; argc--;
275   }
276
277   LoadRCFile();
278   LoadEggStats();
279
280 #ifdef Solaris
281   /* Solaris has an eccentric definition of sigaction() which
282      doesn't seem to even work according to Sun's own
283      documentation.  (sa_flags is a 4 element array of
284      unsigned longs, with no mention of how one stores the
285      flags into it.)  Let's just use plain old signal for
286      the moment. */
287   signal(SIGHUP, handle_sighup);
288 #else
289 #ifdef SIGACTION_WORKING
290   sigact.sa_handler = handle_sighup;
291   sigact.sa_mask = 0;
292   sigact.sa_flags = 0;
293   sigact.sa_restorer = NULL;
294   sigaction(SIGHUP, &sigact, NULL);
295 #else
296   signal(SIGHUP, handle_sighup);
297 #endif
298 #endif
299
300   /* No connection at outset */
301   sdlisten = -1;
302
303   /* Initialize storage system. */
304   InitStorage("%Y-%m/%Y-%m-%d-$b");
305
306   upsince = getzulutime(NULL);
307
308   fprintf(stderr, "Starting basket %s.\n", Version);
309
310   /* Inner loop is currently single thread:
311      1A. Wait for connection.  When a connection comes in,
312          packet has been decrypted and verified.
313      1B. Validate connection.
314      1C. Reply to connection. */
315   while (1) {
316     /* If no current listening connection, try to get one,
317        and if that fails, sleep and try later. */
318
319     if (sdlisten < 0) {
320       /* Get a listening socket at BASKETPORT.
321          Only allow our */
322       sdlisten = InitNetwork(myaddr, myport);
323       if (sdlisten < 0) {
324         sleep(1);
325         continue;
326       }
327     }
328
329     /* 1A. Wait for connection. */
330     res = NetListen(sdlisten, &pktbuf, &rhost, TRUE);
331     if (res < 0) {
332       fprintf(stderr, "NetListen error: %d\n", res);
333       continue;
334     }
335     
336     /* 1B. Validate connection. 
337        Remote host address is in rhost.  Make sure this is either an
338        egg or a basket, and set the flag appropriately. */
339 #ifdef EGG_DYNAMIC
340     ipvalidate = ntohl(rhost.sin_addr.s_addr);
341 #endif
342
343     isegg = isbasket = 0;
344     for (i = 0; i < numeggs; i++) {
345 #ifdef EGG_DYNAMIC
346       /* When mask == 32:       ipmask = 0xFFFFFFFF (all significant)
347               mask == 16:       ipmask = 0xFFFF0000 (only top half sig)
348               mask ==  0:       ipmask = 0x00000000 (none significant) */
349       ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
350       ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
351       if ((ipvalidate & ipmask) == (ipmatch & ipmask)) {
352         isegg = eggtable[i].id;
353         thisegg = i;
354         remname = eggtable[i].name;
355       }      
356 #else
357       if (!memcmp(&(rhost.sin_addr),
358                   &(eggtable[i].ipaddr.sin_addr),
359                   sizeof(rhost.sin_addr))) {
360         isegg = eggtable[i].id;
361         thisegg = i;
362         remname = eggtable[i].name;
363       }
364 #endif
365     }
366     for (i = 0; i < numbaskets; i++) {
367       if (!memcmp(&(rhost.sin_addr),
368                   &(baskettable[i].ipaddr.sin_addr),
369                   sizeof(rhost.sin_addr))) {
370         isbasket = 1;
371         remname = baskettable[i].name;
372       }
373     }
374
375     if (!isegg && !isbasket) {
376       fprintf(stderr, "Attempt to connect from unknown source: %s\n",
377               sockaddr2dquad(&rhost));
378       continue;
379     }
380
381     memcpy(&pkttype, pktbuf, sizeof(uint16));
382     memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
383     pkttype = ntohs(pkttype);
384     pktsize = ntohs(pktsize);
385     if (pktsize < 6) {
386       fprintf(stderr, "Packet doesn't contain an egg id, why?\n");
387       continue;
388     }
389     pkt_eggid = ntohs(*((uint16 *) (pktbuf + 4)));
390
391     /* Am I hearing from who I think I am? */
392 #if EGG_DYNAMIC
393     /* Have to assume the best case before testing: that the
394        pkt_eggid is actually correct. */
395     for (i = 0; i < numeggs; i++) if (eggtable[i].id == pkt_eggid) break;
396
397     if (i == numeggs) {
398       /* Must be a spoof, and possibly an attempt to crash software too. */
399       fprintf(stderr, "%s: Egg spoofing?  %s reporting itself as %d\n",
400               pgmname, hl2dquad(ipvalidate), pkt_eggid);
401       continue;
402     }
403
404     ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
405     ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
406     if ((ipvalidate & ipmask) != (ipmatch & ipmask)) {
407       fprintf(stderr, "%s: Egg spoofing?  %s reporting itself as %d\n",
408               pgmname, hl2dquad(ipvalidate), pkt_eggid);
409       continue;
410     } else {
411       isegg = eggtable[i].id;
412       thisegg = i;
413       remname = eggtable[i].name;
414     }      
415 #else
416     if (isegg != pkt_eggid) {
417       fprintf(stderr, "%s: Egg spoofing?  %d reporting itself as %d\n",
418               pgmname, isegg, pkt_eggid);
419       continue;
420     }
421 #endif
422
423     /* 1D. Reply to connection. */
424     switch(pkttype) {
425     case DATA_PACKET:
426       /* Put it away. */
427       BasketReceiveDataPacket(pktbuf, isegg, thisegg, &rhost);
428
429       break;
430     case REQ_PACKET:
431       /* Is this how baskets communicate? */
432       break;
433     case AWAKE_PACKET:
434 #ifdef PACKETDUMP
435       {
436         uint32 egg_now_time;
437
438         memcpy(&egg_now_time, pktbuf + 6, sizeof egg_now_time);
439         egg_now_time = ntohl(egg_now_time);
440         fprintf(stderr, "Awake egg %d (%s) at its %lu: %s",
441                 pkt_eggid, eggtable[thisegg].name, egg_now_time,
442                 asctime(gmtime((time_t *) &egg_now_time)));
443       }
444 #endif
445
446       /* Calculate last data received from this egg, decide what
447          to ask for. */
448       MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
449       rhost.sin_port = htons(EGGPORT);
450       res = NetTalk(&rhost, (char *)&rpkt, TRUE);
451       if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
452
453       /* If setup looks like it's out of date, correct it. */
454       if (!eggtable[thisegg].setup) {
455         MakeSettings(&spkt, thisegg);
456         rhost.sin_port = htons(EGGPORT);
457         res = NetTalk(&rhost, (char *)&spkt, TRUE);
458         if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
459       }
460       break;
461
462     case SETTINGS_PACKET:
463       /* I don't have settings, Bozo. */
464       break;
465     }
466     free(pktbuf);
467   }
468 }
469
470 /*  handle_sighup  --  Catch SIGHUP and reload configuraton files.  */
471
472 static void handle_sighup(int arg) {
473   /* Reread the RC file */
474   LoadRCFile();
475   LoadEggStats();
476 #ifdef DEBUG
477   fprintf(stderr, "Received SIGHUP: reloading configuration files.\n");
478 #endif
479 #ifndef SIGACTION_WORKING
480   signal(SIGHUP, handle_sighup);      /* Reset signal handler for bottom-feeder signal() */
481 #endif
482 }
483
484 /*  LoadRCFile  --  Load basket configuration file.  */
485
486 static void LoadRCFile(void) {
487   FILE *fp;
488   char linebuf[200];
489   char *myargv[MAX_PARSE], *tp;
490   int myargc, lcount, p, i;
491
492   if ((fp = fopen(".basketrc", "r")) == NULL) {
493     if ((fp = fopen("~/.basketrc", "r")) == NULL) {
494       if ((fp = fopen("/etc/basketrc", "r")) == NULL) {
495         fprintf(stderr, "%s: Couldn't find a basket RC file.\n", pgmname);
496         exit(-1);
497       }
498     }
499   }
500
501   numeggs = numbaskets = 0;
502   lcount = 0;
503
504   while(fgets(linebuf, 200, fp) != NULL) {
505     /* Comments and blank lines ignored. */
506     lcount++;
507     if (*linebuf == '#' || *linebuf == '\n') continue;
508     Parse(linebuf, &myargc, myargv);
509     if (myargc == 0) continue;
510     if (!strcmp(myargv[0], "EGG")) {
511       if (myargc < 7 || myargc > 8) {
512         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
513                 pgmname, lcount, myargv[0]);
514         exit(-1);
515       }
516         
517       eggtable[numeggs].name = mallocpy(myargv[1]);
518       eggtable[numeggs].id = atoi(myargv[2]);
519       dquad2sockaddr(&(eggtable[numeggs].ipaddr), 
520                      &(eggtable[numeggs].netmask),
521                      myargv[3]);
522       eggtable[numeggs].primbasket = mallocpy(myargv[4]);
523       eggtable[numeggs].conntype = (!strcmp(myargv[5], "PERM"))?CONN_PERM:CONN_DND;
524       eggtable[numeggs].connival = atoi(myargv[6]);
525       eggtable[numeggs].url = NULL;
526       if ((myargc > 7) && (strcmp(myargv[7], ".") != 0)) {
527         eggtable[numeggs].url = mallocpy(myargv[7]);
528       }
529       numeggs++;
530     } else if (!strcmp(myargv[0], "BASKET")) {
531       if (myargc != 3) {
532         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
533                 pgmname, lcount, myargv[0]);
534         exit(-1);
535       }
536       baskettable[numbaskets].name = mallocpy(myargv[1]);
537       dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
538       numbaskets++;
539     } else if (!strcmp(myargv[0], "PROTOCOL")) {
540       if (myargc != 5) {
541         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
542                 pgmname, lcount, myargv[0]);
543         exit(-1);
544       }
545       protocol.samp_rec = atoi(myargv[1]);
546       protocol.sec_rec = atoi(myargv[2]);
547       protocol.rec_pkt = atoi(myargv[3]);
548       protocol.trialsz = atoi(myargv[4]);
549     } else if (!strcmp(myargv[0], "HTML")) {
550       if (myargc != 3) {
551         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
552                 pgmname, lcount, myargv[0]);
553         exit(-1);
554       }
555       htmlInterval = atoi(myargv[1]);
556       strcpy(htmlFile, myargv[2]);
557 #ifdef DEBUG
558       fprintf(stderr, "Updating HTML file %s every %d seconds.\n", htmlFile, htmlInterval);
559 #endif
560     } else if (!strcmp(myargv[0], "PORT") ||
561                !strcmp(myargv[0], "INTERFACE")) {
562       if (myargc != 2) {
563         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
564                 pgmname, lcount, myargv[0]);
565         exit(-1);
566       }
567       tp = (char *) malloc(200);
568       for (*tp = 0, p = 1; p < myargc; p++) {
569         strcat(tp, myargv[p]);
570         if (p < myargc-1) strcat(tp, " ");
571       }
572
573       if (!strcmp(myargv[0], "PORT")) {
574         if (atoi(tp) <= 0) {
575           fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
576                   pgmname, atoi(tp), myport);
577         } else {
578           myport = atoi(tp);
579         }
580         free(tp);
581       } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
582     } else {
583       fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[0]);
584       exit(-1);
585     }
586   }
587   fclose(fp);
588
589 #if REPORT > 0
590   /* Report a list of the eggs that we are accepting. */
591   for (i = 0; i < numeggs; i++) {
592     fprintf(stderr, "Egg %2d:\tid = %4d\tip=%s/%d\n", 
593             i, eggtable[i].id, 
594             sockaddr2dquad(&(eggtable[i].ipaddr)),
595             eggtable[i].netmask);
596   }
597 #endif
598 }
599
600 /*  BasketReceiveDataPacket  --  Process a data packet received frm an egg.  */
601
602 static int32 BasketReceiveDataPacket(char *pkt, int16 isegg, int16 thisegg,
603                               struct sockaddr_in *rhost) {
604   EggHeader             *dpktp;
605   EggCarton             result;
606   ReqPacket             rpkt;
607   uint16                offset, rec;
608   uint32                latest;
609   int32                 res;
610 #ifdef DEBUG
611   uint16                i;
612 #endif
613
614   EggHeader dpk;
615   char *pktP = pkt;
616   
617   /* Unpack the portable header into a host-order and aligned
618      EggHeader packet. */
619   
620   dpktp = &dpk;
621   unpackShort(dpk.type);
622   unpackShort(dpk.pktsize);
623   unpackShort(dpk.eggid);
624   unpackShort(dpk.samp_rec);
625   unpackShort(dpk.sec_rec);
626   unpackShort(dpk.rec_pkt);
627   unpackByte(dpk.trialsz);
628   unpackShort(dpk.numrec);
629
630   /* This spoofing test is probably completely redundant now. */
631 #ifndef EGG_DYNAMIC      
632   /* Am I hearing from an egg, and is it who I think it is? */
633   if (isegg && isegg != dpktp->eggid) {
634     fprintf(stderr, "%s: Egg spoofing?  %d reporting itself as %d\n",
635             pgmname, isegg, dpktp->eggid);
636     return -1;
637   }
638 #endif
639
640   /* Save the packet header in the result EggCarton buffer. */
641
642   memcpy(&(result.hdr), dpktp, sizeof(EggHeader));
643   offset = sizeof(EggHeader);
644
645 #ifdef DEBUG
646   {
647     uint32 stamp = 0;
648     char *spktp = pktP;
649
650     if (result.hdr.numrec > 0) {
651       unpackLong(stamp);
652     }
653     pktP = spktp;
654     fprintf(stderr, "Received packet: %d records from egg %d (%s).\n",
655       result.hdr.numrec, dpktp->eggid, eggtable[thisegg].name);
656   }
657 #endif
658
659   /* Unpack the individual data records from the packet and
660      transcribe to the result EggCarton.  */
661   
662   for (latest = 0, rec = 0; rec < result.hdr.numrec; rec++) {
663     unpackLong(result.records[rec].timestamp);   /* Record timestamp */
664     if (result.records[rec].timestamp > latest)
665       latest = result.records[rec].timestamp;
666 #ifdef DEBUG
667     fprintf(stderr, "  %10lu  ", result.records[rec].timestamp);
668 #endif
669     /* Assumes sizeof(trial) = 1 */
670     unpackBytes(&(result.records[rec].trials), result.hdr.samp_rec);
671 #ifdef DEBUG
672     for (i = 0; i < result.hdr.samp_rec; i++) {
673       fprintf(stderr, "%3d ", result.records[rec].trials[i]);
674     }
675     fprintf(stderr, "%s", asctime(gmtime((time_t *) &result.records[rec].timestamp)));
676 #endif
677   }
678
679   /* Save the packet in the basket database file. */
680   
681   if ((res = SavePacket(&result)) < 0) return res;
682
683   /* If we're generating an HTML report, update the per-basket
684      statistics based on the content of this packet. */
685
686   if (htmlReport) {
687       int r, t;
688       uint32 ptrials = 0, psum = 0, dropped = 0;
689
690       /* Update the egg status for HTML report. */
691
692       for (r = 0; r < result.hdr.rec_pkt; r++) {
693           for (t = 0; t < result.hdr.samp_rec; t++) {
694               if (result.records[r].trials[t] != EGG_MISSING_DATA) {
695                   ptrials++;
696                   psum += result.records[r].trials[t];
697               } else {
698                   dropped++;
699               }
700           }
701       }
702       eggStatistics[thisegg].trials += ptrials;
703       eggStatistics[thisegg].sum += psum;
704
705       /* If this is the first packet received from the egg, and
706          the first sample in it is missing, don't count missing
707          samples against this egg, since it's probable this is
708          the first packet collected since the egg started, which
709          will contain initial missing samples for time before the
710          egg started. */
711
712       if (eggStatistics[thisegg].firstPacket != 0) {
713           eggStatistics[thisegg].missing += dropped;
714       } else {
715           eggStatistics[thisegg].firstPacket = result.records[0].timestamp;
716       }
717   }
718
719   if (latest > eggtable[thisegg].lastupd) {
720     eggtable[thisegg].lastupd = latest;
721     SaveEggStats();
722     updateHTML();                     /* Update HTML status file, if warranted */
723     if (latest < getzulutime(NULL)) {
724       /* Get more data if it looks like there should be more. */
725       MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
726       rhost->sin_port = htons(EGGPORT);
727       res = NetTalk(rhost, (char *)&rpkt, TRUE);
728       if (res < 0) fprintf(stderr, "NetTalk error %d.\n", (int)res);
729     }
730   }
731
732   /* If the protocol now in effect differs from that of the
733      packet just received from the egg, mark the egg as not
734      set-up, and thus in need of a SETTINGS_PACKET to reset
735      its protocol. */
736
737   eggtable[thisegg].setup = (protocol.samp_rec == dpktp->samp_rec &&
738                              protocol.sec_rec == dpktp->sec_rec &&
739                              protocol.rec_pkt == dpktp->rec_pkt &&
740                              protocol.trialsz == dpktp->trialsz);
741
742   return ERR_NONE;
743 }
744
745 /*  MakeRequest  --  Assemble a request packet for a given egg,
746                      specifying, in whence, the time and date
747                      of the last sample received from that egg.  */
748
749 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence) {
750   char *pktP = (char *) pkt;
751
752   packShort(REQ_PACKET);
753   packShort((4 * sizeof(uint16)) + sizeof(uint32));
754   packShort(eggid);
755   packLong(whence);
756 }
757
758 /*  MakeSettings  --  Create a settings packet.  This packet,
759                       sent periodically to connected eggs,
760                       informs them of any change in protocol.  */
761
762 static void MakeSettings(SettingsPacket *pkt, uint16 eggid) {
763   char *pktP = (char *) pkt;
764
765   packShort(SETTINGS_PACKET);
766   packShort((7 * sizeof(uint16)) + sizeof(uint32) + sizeof(trial));
767   packShort(eggid);
768   packLong(getzulutime(NULL));
769   packShort(protocol.samp_rec);
770   packShort(protocol.sec_rec);
771   packShort(protocol.rec_pkt);
772   packByte(protocol.trialsz);
773 }
774
775 /*  LoadEggStats  --  Initialise in-memory egg status table
776                       from the EGGSTATS file.  */
777
778 static void LoadEggStats(void) {
779   FILE *fp;
780   char linebuf[200], *myargv[MAX_PARSE], *name;
781   int i, f, myargc, id, setup;
782   int32 lastupd;
783
784   /* Default stats */
785   for (i = 0; i < numeggs; i++) {
786     eggtable[i].lastupd = 0;
787     eggtable[i].setup = 0;
788   }
789   
790   if ((fp = fopen(EGGSTATS, "r")) == NULL) return;
791   
792   while(fgets(linebuf, 200, fp) != NULL) {
793     /* Comments and blank lines ignored. */
794     if (*linebuf == '#' || *linebuf == '\n') continue;
795     Parse(linebuf, &myargc, myargv);
796     if (myargc == 0) continue;
797     if (!strcmp(myargv[0], "STAT") && myargc == 5) {
798       name = myargv[1];
799       id = atoi(myargv[2]);
800       lastupd = atol(myargv[3]);
801       setup = atoi(myargv[4]);
802       for (f = 0, i = 0; i < numeggs; i++) {
803         if (eggtable[i].id == id &&
804             !strcmp(eggtable[i].name, name)) {
805           eggtable[i].lastupd = lastupd;
806           eggtable[i].setup = setup;
807           f = 1;
808         }
809       }
810       if (!f) fprintf(stderr, "%s: Egg '%s' no longer hosted.\n", pgmname, name);
811     }
812   }
813   fclose(fp);
814 }
815
816 /*  SaveEggStats  --  Dump in-memory egg status to the EGGSTATS file.  */
817
818 static void SaveEggStats(void) {
819   FILE *fp;
820   int i;
821
822   if ((fp = fopen(EGGSTATS, "w")) == NULL) {
823     /* Couldn't write stats! */  
824     fprintf(stderr, "%s: Couldn't save egg stats!\n", pgmname);
825     return;
826   }
827
828   fprintf(fp, "# Status lines of form:\n");
829   fprintf(fp, "#   STAT <eggname> <eggid> <lastupdzulu> <setupvalid>\n");
830   for (i = 0; i < numeggs; i++) {
831     fprintf(fp, "STAT %s %d %ld %d\n",
832             eggtable[i].name,
833             eggtable[i].id,
834             eggtable[i].lastupd,
835             eggtable[i].setup);
836   }
837   fclose(fp);
838 }