1 #undef DEBUG
2 
3 #include "config.h"
4 #include "copyright.h"
5 
6 #ifdef META
7 #include INC_LIMITS
8 #include INC_FCNTL
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <ctype.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <time.h>
15 #include <stdlib.h>
16 #include INC_SYS_SELECT
17 #include INC_STRINGS
18 #include <sys/select.h>
19 #include <sys/socket.h>
20 #include INC_NETINET_IN
21 #include <arpa/inet.h>
22 #include <netdb.h>
23 #include <errno.h>
24 #include <signal.h>
25 
26 #include "Wlib.h"
27 #include "defs.h"
28 #include "struct.h"
29 #include "data.h"
30 #include "version.h"
31 #include "patchlevel.h"
32 
33 #include "badversion.h"
34 #include "defaults.h"
35 #include "newwin.h"
36 
37 #include "parsemeta.h"
38 
39 #ifdef WIN32 /* socket garbage in case the client is not running on NT */
40 #define read(f,b,l) recv(f,b,l,0)
41 #define write(f,b,l) send(f,b,l,0)
42 #define close(s) closesocket(s)
43 #endif
44 
45 /* Constants */
46 
47 #define BUF     6144
48 #define LINE    100             /* Width of a meta-server line           */
49 #define MAXMETABYTES 2048       /* maximum metaserver UDP packet size    */
50 static int msock = -1;          /* the socket to talk to the metaservers */
51 static int sent = 0;            /* number of solicitations sent          */
52 static int seen = 0;            /* number of replies seen                */
53 static int verbose = 0;         /* whether to talk a lot about it all    */
54 static time_t last;             /* time of last refresh                  */
55 
56 /* Local Types */
57 
58 struct servers {
59   char    address[LINE];        /* host name or ip address of server    */
60   int     port;
61   int     age;                  /* age in seconds as received           */
62   time_t  when;                 /* date time this record received       */
63   int     refresh;              /* data needs redisplaying              */
64   int     lifetime;             /* remaining cache life of entry        */
65   int     players;
66   int     status;
67   char    typeflag;
68   char    comment[LINE];
69   pid_t   pid;                  /* our last known child playing here    */
70   int     exitstatus;           /* exit status of last known child here */
71   int     observer;             /* set if child is an observer          */
72 };
73 
74 struct servers *serverlist = NULL;      /* The record for each server.  */
75 static int num_servers = 0;             /* The number of servers.       */
76 static int chosen = -1;                 /* Arrow key chosen server.     */
77 static int metaHeight = 0;              /* The number of list lines.    */
78 static char *metaWindowName;            /* The window's name.           */
79 static int statusLevel;
80 static W_Window metaWin, metaList, metaHelpWin = NULL;
81 void *logo;
82 
83 /* button offsets from end of list */
84 #define B_ADD 4
85 #define B_REFRESH 3
86 #define B_HELP 2
87 #define B_QUIT 1
88 
89 #define N_TITLES 1
90 #define N_BUTTONS 4
91 #define N_GAP 1
92 #define N_OVERHEAD N_TITLES+N_BUTTONS+N_GAP
93 
94 /* The status strings:  The order of the strings up until statusNull is
95  * important because the meta-client will display all the strings up to a
96  * particular point.
97  *
98  * The strings after statusNull are internal status types and are formatted
99  * separatly from the other strings.
100  *
101  * The string corresponding to "statusNull" is assigned to thoes servers which
102  * have "statusNobody" or earlier strings in old, cached, meta-server data. */
103 
104 char   *statusStrings[] =
105 {"", "Wait queue:", "-", "Timed out", "No connection",
106  "Active", "CANNOT CONNECT", "DEFAULT SERVER"};
107 
108 enum statusTypes {
109   statusOpen = 0, statusWait, statusNobody, statusTout, statusNoConnect,
110   statusNull, statusCantConnect, statusDefault
111 };
112 
113 static const int defaultStatLevel = statusTout;
114 
115 /* Functions */
116 extern void terminate(int error);
117 
118 char *metahelp_message[] =
119   {
120     "Netrek Server List - Help",
121     "",
122     "This is a list of Netrek servers, from the Metaserver, or on your",
123     "local network.  You can do this:",
124     "",
125     "  -  click on a server to join the game there,",
126     "",
127     "  -  middle-click on a server to immediately join as guest,",
128     "",
129     "  -  right-click on a server to observe the game there,",
130     "",
131     "  -  click refresh to reload the list of servers,",
132     "",
133     "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --",
134     "",
135     "Tips for new Netrek players:",
136     "",
137     "  1.  when you enter the game, press the number 4 to start moving,",
138     "  other numbers are slower or faster,",
139     "",
140     "  2.  you are the white ship in the centre of the left-hand screen,",
141     "",
142     "  3.  use right-click on the mouse to steer toward a point in space,",
143     "  you will find it easier to turn at lower speeds,",
144     "",
145     "  4.  use left-click on the mouse to fire a torpedo, they travel over",
146     "  time so you have to point ahead of your target, they hurt enemy",
147     "  ships but usually pass right through your team,",
148     "",
149     "  5.  use middle-click on your mouse to fire a phaser, they are",
150     "  instant so point at your target, but only work well close up,",
151     "",
152     "  6.  the aim of the game is to capture planets, killing enemies is",
153     "  only a means to an end, and dying is a good thing, because you get a",
154     "  new ship,",
155     "",
156     "  7.  but if someone kills you, they can begin to capture planets, so",
157     "  it is best to not die in vain,",
158     "",
159     "  8.  people in your team will try to communicate so that you can all",
160     "  do things together, because when you cooperate better than the other",
161     "  team, you win.",
162     "",
163     "  9.  use the 'h' key to see a list of commands for your ship.",
164     "",
165     "Observing is a good way to learn.  When you join as an observer, you",
166     "won't see much until you use 'l' (lower-case L) to lock on to a",
167     "player.  Choose a good player and watch what they do.",
168     "",
169     "For more information on Netrek, visit http://netrek.org/beginner",
170     NULL
171   };
172 
make_help()173 static void make_help()
174 {
175   int i, l, h, w;
176 
177   /* calculate width and height required */
178   h = (sizeof(metahelp_message)/sizeof(char *));
179   w = 0;
180   for (i=0; i<h; i++) {
181     char *line = metahelp_message[i];
182     if (line == NULL) break;
183     l = strlen(line);
184     if (l > w) w = l;
185   }
186   metaHelpWin = W_MakeWindow("Netrek Server List - Help", 500, 500,
187                              w * W_Textwidth + 40, h * W_Textheight + 40,
188                              0, 2, foreColor);
189 }
190 
expo_help()191 static void expo_help()
192 {
193   int i, h;
194   h = (sizeof(metahelp_message)/sizeof(char *));
195   for (i=0; i<h; i++) {
196     char *line = metahelp_message[i];
197     if (line == NULL) break;
198     W_WriteText(metaHelpWin, 20, i * W_Textheight + 20, textColor, line, -1,
199                 W_RegularFont);
200   }
201 }
202 
show_help()203 static void show_help()
204 {
205   W_MapWindow(metaHelpWin);
206 }
207 
hide_help()208 static void hide_help()
209 {
210   W_UnmapWindow(metaHelpWin);
211 }
212 
toggle_help()213 static void toggle_help()
214 {
215   if (W_IsMapped(metaHelpWin)) {
216     hide_help();
217   } else {
218     show_help();
219   }
220 }
221 
ReadMetasSend()222 static int ReadMetasSend()
223 {
224   char *metaservers;            /* our copy of the metaserver host names */
225   char *token;                  /* current metaserver host name          */
226   struct sockaddr_in address;   /* the address of the metaservers        */
227   static char *req;             /* the request packet for the metaserver */
228   static int reqlen;            /* the length of the request packet      */
229 
230   last = time(NULL);
231 
232   /* host names of metaservers, default in data.c, comma delimited */
233   if ((getdefault("metaserver")) != NULL)
234     metaserver = getdefault("metaserver");
235 
236   /* port number of metaservers, unlikely to change, not a list */
237   metaport = intDefault("metaport", metaport);
238 
239   /* whether to report everything that happens */
240   verbose = booleanDefault("metaverbose", verbose);
241 
242   /* create the socket */
243   if (msock < 0) {
244     msock = socket(AF_INET, SOCK_DGRAM, 0);
245     if (msock < 0) { perror("ReadMetasSend: socket"); return 0; }
246 
247     /* bind the socket to any address */
248     address.sin_addr.s_addr = INADDR_ANY;
249     address.sin_family      = AF_INET;
250     address.sin_port        = 0;
251     if (bind(msock,(struct sockaddr *)&address, sizeof(address)) < 0) {
252       perror("ReadMetasSend: bind");
253       close(msock);
254       return 0;
255     }
256     req = malloc(80);
257     snprintf(req, 80, "?version=netrek-client-cow-%s.%d",
258              mvers, PATCHLEVEL);
259     reqlen = strlen(req);
260   }
261 
262   /* send request to a multicast metaserver on local area network */
263   address.sin_family = AF_INET;
264   address.sin_port = htons(metaport);
265   address.sin_addr.s_addr = inet_addr("224.0.0.1");
266   if (verbose) fprintf(stderr,
267                        "requesting player list from nearby servers on %s\n",
268                        inet_ntoa(address.sin_addr));
269 
270   if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
271              sizeof(address)) < 0) {
272     perror("ReadMetasSend: sendto");
273   } else {
274     sent++;
275   }
276 
277   /* try each of the comma delimited metaserver host names */
278   metaservers = strdup(metaserver);
279   token = strtok(metaservers,",");
280 
281   while (token != NULL) {
282     /* compose the address structure */
283     address.sin_family = AF_INET;
284     address.sin_port = htons(metaport);
285 
286     /* skip any blanks */
287     while (*token == ' ') token++;
288 
289     /* attempt numeric translation first */
290     if ((address.sin_addr.s_addr = inet_addr(token)) == -1) {
291       struct hostent *hp;
292 
293       /* then translation by name */
294       if ((hp = gethostbyname(token)) == NULL) {
295         /* if it didn't work, return failure and warning */
296         fprintf(stderr,
297           "cannot resolve host %s, check for DNS problems?\n",
298           token);
299       } else {
300         int i;
301 
302         /* resolution worked, send a query to every metaserver listed */
303         for(i=0;;i++) {
304 
305           /* check for end of list of addresses */
306           if (hp->h_addr_list[i] == NULL) break;
307           address.sin_addr.s_addr = *(long *) hp->h_addr_list[i];
308           if (verbose) fprintf(stderr,
309                 "requesting player list from metaserver %s at %s\n",
310                 token, inet_ntoa(address.sin_addr));
311           if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
312                      sizeof(address)) < 0) {
313             perror("ReadMetasSend: sendto");
314           } else {
315             sent++;
316           }
317         }
318       }
319     } else {
320       /* call to inet_addr() worked, host name is in IP address form */
321       if (verbose) fprintf(stderr,
322                            "requesting player list from metaserver %s\n",
323                            inet_ntoa(address.sin_addr));
324       if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
325                  sizeof(address)) < 0) {
326         perror("ReadMetasSend: sendto");
327       } else {
328         sent++;
329       }
330     }
331 
332     /* look for next host name in list */
333     token = strtok(NULL,",");
334   } /* while (token != NULL) */
335 
336   metaWindowName = "Netrek Server List";
337   return sent;
338 }
339 
340 /* allocate or extend the server list */
grow(int servers)341 static void grow(int servers)
342 {
343   int size;
344   if (serverlist == NULL) {
345     size = sizeof(struct servers) * servers;
346     serverlist = (struct servers *) malloc(size);
347   } else {
348     size = sizeof(struct servers) * (servers + num_servers);
349     serverlist = (struct servers *) realloc(serverlist, size);
350   }
351 }
352 
server_find(char * address,int port)353 static struct servers *server_find(char *address, int port)
354 {
355   int j;
356 
357   for(j=0;j<num_servers;j++) {
358     struct servers *sp = serverlist + j;
359     if ((!strcmp(sp->address, address)) && (sp->port == port)) {
360       return sp;
361     }
362   }
363   return NULL;
364 }
365 
version_r(struct sockaddr_in * address)366 static void version_r(struct sockaddr_in *address) {
367   char *p;
368   int servers, i;
369   time_t now = time(NULL);
370 
371   /* number of servers */
372   p = strtok(NULL,"\n");
373   if (p == NULL) return;
374   servers = atoi(p);
375 
376   /* sanity check on number of servers */
377   if (servers > 2048) return;
378   if (servers < 0) return;
379 
380   if (verbose) fprintf(stderr,
381                        "metaserver at %s responded with %d server%s\n",
382                        inet_ntoa(address->sin_addr),
383                        servers,
384                        servers == 1 ? "" : "s" );
385 
386   if (servers == 0) return;
387 
388   /* for each server listed by this metaserver packet */
389   for(i=0;i<servers;i++) {
390     struct servers *sp = NULL;
391     char *host, type;
392     int port, status, age, players, queue, throwaway;
393 
394     throwaway = 0;
395 
396     host = strtok(NULL,",");            /* hostname */
397     if (host == NULL) continue;
398 
399     p = strtok(NULL,",");               /* port */
400     if (p == NULL) continue;
401     port = atoi(p);
402 
403     p = strtok(NULL,",");               /* status */
404     if (p == NULL) continue;
405     status = atoi(p);
406 
407     /* ignore servers based on status */
408     if (status > statusLevel)
409       throwaway++;
410     /* the sp->why_dead workaround */
411     if (status == 6)
412       if ((status - 3) <= statusLevel)
413         throwaway--;
414 
415     p = strtok(NULL,",");               /* age (of data in seconds) */
416     if (p == NULL) continue;
417     age = atoi(p);
418 
419     p = strtok(NULL,",");               /* players */
420     if (p == NULL) continue;
421     players = atoi(p);
422 
423     p = strtok(NULL,",");               /* queue size */
424     if (p == NULL) continue;
425     queue = atoi(p);
426 
427     p = strtok(NULL,"\n");              /* server type */
428     if (p == NULL) continue;
429     type = p[0];
430 
431     /* ignore paradise servers */
432     if (type == 'P') throwaway++;
433 
434     /* if it's to be thrown away, do not add this server, skip to next */
435     if (throwaway) continue;
436 
437     /* find in current server list? */
438     sp = server_find(host, port);
439 
440     /* if it was found, check age */
441     if (sp != NULL) {
442       if ((now-age) < (sp->when-sp->age)) {
443         sp->age = now - (sp->when-sp->age);
444         sp->when = now;
445         sp->refresh = 1;
446         sp->lifetime = 20;
447         continue;
448       } else {
449         sp->age = age;
450         sp->when = now;
451         sp->lifetime = 20;
452       }
453     } else {
454       /* not found, store it at the end of the list */
455       grow(1);
456       sp = serverlist + num_servers;
457       num_servers++;
458       strncpy(sp->address,host,LINE);
459       sp->port = port;
460       sp->age = age;
461       sp->when = now;
462       sp->lifetime = 4;
463     }
464     sp->refresh = 1;
465 
466     /* from meta.h of metaserver code */
467 #define SS_WORKING 0
468 #define SS_QUEUE 1
469 #define SS_OPEN 2
470 #define SS_EMPTY 3
471 #define SS_NOCONN 4
472 #define SS_INIT 5
473     /* not a real metaserver number, but overcomes a limitation of dropping text */
474     /* description of sp->why_dead */
475 #define SS_TOUT 6
476     switch (status) {
477     case SS_QUEUE:
478       sp->status = statusWait;
479       sp->players = queue;
480       break;
481     case SS_OPEN:
482       sp->status = statusOpen;
483       sp->players = players;
484       break;
485     case SS_EMPTY:
486       sp->status = statusNobody;
487       sp->players = 0;
488       break;
489     case SS_TOUT:
490       sp->status = statusTout;
491       sp->players = 0;
492       break;
493     case SS_NOCONN:                     /* no connection */
494     case SS_WORKING:            /* metaserver should not return this */
495     case SS_INIT:                       /* nor this */
496     default:
497       sp->status = statusNoConnect;
498       sp->players = 0;
499       break;
500     }
501     sp->typeflag = type;
502     strcpy(sp->comment, "");
503     sp->pid = -1;
504     sp->exitstatus = 0;
505     sp->observer = 0;
506   }
507 }
508 
version_s(struct sockaddr_in * address)509 static void version_s(struct sockaddr_in *address)
510 {
511   char *p;
512   time_t now = time(NULL);
513 
514   /* use return address on packet as host address for this server,
515   since it isn't practical for the server to know it's own address; as
516   is the case with multihomed machines */
517   char *host = inet_ntoa(address->sin_addr);
518 
519   if (verbose) fprintf(stderr, "server at %s responded\n", host);
520 
521   p = strtok(NULL,","); /* server type */
522   if (p == NULL) return;
523   char type = p[0];
524 
525   /* ignore paradise servers */
526   if (type == 'P') return;
527 
528   p = strtok(NULL,",");         /* comment */
529   if (p == NULL) return;
530   char *comment = strdup(p);
531   if (strlen(comment) > LINE) comment[LINE] = '\0';
532 
533   p = strtok(NULL,",");         /* number of ports */
534   if (p == NULL) return;
535   // int ports = atoi(p);               /* not currently used */
536 
537   // TODO: accept more than one port reply
538 
539   p = strtok(NULL,",");         /* port */
540   if (p == NULL) return;
541   int port = atoi(p);
542 
543   p = strtok(NULL,",");         /* players */
544   if (p == NULL) return;
545   int players = atoi(p);
546 
547   p = strtok(NULL,",");         /* queue size */
548   if (p == NULL) return;
549   // int queue = atoi(p);               /* not currently used */
550 
551   /* find in current server list? */
552   struct servers *sp = server_find(host, port);
553 
554   /* if it was not found, add it to the end of the list */
555   if (sp == NULL) {
556     grow(1);
557     sp = serverlist + num_servers;
558     num_servers++;
559   }
560 
561   /* add or update the entry */
562   strncpy(sp->address, host, LINE);
563   sp->port = port;
564   sp->age = 0;
565   sp->when = now;
566   sp->refresh = 1;
567   sp->lifetime = 20;
568   sp->players = players;
569   if (type == 'u' && players == 0) {
570     sp->status = statusNobody;
571   } else {
572     sp->status = statusOpen;
573   }
574   sp->typeflag = type;
575   strncpy(sp->comment, comment, LINE);
576   sp->pid = -1;
577   sp->exitstatus = 0;
578   sp->observer = 0;
579   free(comment);
580 }
581 
ReadMetasRecv(int x)582 static int ReadMetasRecv(int x)
583 {
584   struct sockaddr_in address;   /* the address of the metaservers        */
585   socklen_t length;             /* length of the address                 */
586   int bytes;                    /* number of bytes received from meta'   */
587   fd_set readfds;               /* the file descriptor set for select()  */
588   struct timeval timeout;       /* timeout for select() call             */
589   char packet[MAXMETABYTES];    /* buffer for packet returned by meta'   */
590   char *p;
591 
592   /* now await and process replies */
593 
594   FD_ZERO(&readfds);
595   if (msock >= 0) FD_SET(msock, &readfds);
596   timeout.tv_sec = 10;
597   timeout.tv_usec = 0;
598 
599   if (x != -1) FD_SET(x, &readfds);
600   if (select(FD_SETSIZE, &readfds, NULL, NULL, &timeout) < 0) {
601     if (errno == EINTR) return 0;
602     perror("ReadMetasRecv: select");
603     return 0;
604   }
605 
606   /* if x activity, return immediately */
607   if (x != -1 && FD_ISSET(x, &readfds)) return 0;
608   if (msock < 0) return 0;
609 
610   /* if the wait timed out, then we give up */
611   if (!FD_ISSET(msock, &readfds)) return 0;
612 
613   /* so we have data back from a metaserver or server */
614   length = sizeof(address);
615   bytes = recvfrom(msock, packet, MAXMETABYTES, 0,
616                    (struct sockaddr *)&address, &length );
617   if (bytes < 0) {
618     perror("ReadMetasRecv: recvfrom");
619     return 0;
620   }
621 
622   /* terminate the packet received */
623   packet[bytes] = 0;
624 #ifdef DEBUG
625   fprintf(stderr, "%s", packet);
626 #endif /* DEBUG */
627 
628   /* process the packet, updating our server list */
629 
630   /* version identifier */
631   p = strtok(packet,",");
632   if (p == NULL) return 0;
633 
634   switch (p[0]) {
635   case 'r': version_r(&address); seen++; break;
636   case 's': version_s(&address); seen++; break;
637   }
638 
639   return 1;
640 }
641 
SaveMetasCache()642 static void SaveMetasCache()
643 {
644   FILE *cache;
645   char cacheFileName[PATH_MAX];
646   char tmpFileName[PATH_MAX];
647   char *cacheName;
648   int len;
649 
650   cacheName = getdefault("metaUDPCache");
651   /* overwrite existing file if possible */
652   if (cacheName && !findfile(cacheName, cacheFileName))
653    strcpy(cacheFileName, cacheName);
654 
655   if (cacheName)
656     {
657       len = strlen(cacheFileName);
658       strcpy(tmpFileName, cacheFileName);
659 
660       /* create a temporary file with roughly the same name */
661       if ((cacheFileName[len - 1] == 'T') || (cacheFileName[len - 1] == 't'))
662         tmpFileName[len-1] = 'R';
663       else
664         tmpFileName[len-1] = 'T';
665 
666       cache = fopen(tmpFileName, "w");
667 
668       if (cache == NULL)
669         {
670           fprintf(stderr,
671                    "Cannot create a metaUDPCache temporary file `%s'\n",
672               tmpFileName);
673           fprintf(stderr, "Meta-server read will not be cached.\n");
674         }
675     }
676   else
677     {
678       cache = NULL;
679     }
680 
681 
682   if (cache != NULL)
683     {
684 
685       if (fwrite(&statusLevel, sizeof(statusLevel), 1, cache) != 1 ||
686           fwrite(&num_servers, sizeof(num_servers), 1, cache) != 1 ||
687           fwrite(serverlist, sizeof(struct servers), num_servers, cache) != num_servers) {
688 	int xerrno = errno;
689 	fclose(cache);
690 #ifdef _MSC_VER
691   	_unlink(tmpFileName);
692 #else
693     	unlink(tmpFileName);
694 #endif
695 	errno = xerrno;
696 	perror("Could not write to the new cache file");
697       }
698 
699       fclose(cache);
700 
701 #ifdef WIN32
702       /* Can't rename file to existing name under NT */
703 #ifdef _MSC_VER
704       _unlink(cacheName);
705 #else
706       unlink(cacheName);
707 #endif
708 #endif
709       if (rename(tmpFileName, cacheName) == -1)
710         perror("Could not rename new cache file");
711     }
712 
713 }
714 
LoadMetasCache()715 static void LoadMetasCache()
716 {
717   FILE *cache;
718   char *cacheName;
719   char cacheFileName[PATH_MAX];
720   int  i;
721 
722   cacheName = getdefault("metaUDPCache");
723 
724   if(!cacheName)
725     {
726       num_servers = 0;
727       return;
728     }
729 
730   findfile(cacheName, cacheFileName);
731 
732   cache = fopen(cacheFileName, "r");
733   if (cache == NULL)
734     {
735       num_servers = 0;
736       return;
737     }
738 
739   /* ignore the cache if user changed statusLevel */
740   if (fread(&i, sizeof(i), 1, cache) != 1 ||
741       i != statusLevel)
742     {
743       num_servers = 0;
744       fclose(cache);
745       return;
746     }
747 
748   /* read the server list into memory from the file */
749   if (fread(&num_servers, sizeof(num_servers), 1, cache) != 1) {
750     num_servers = 0;
751     fclose(cache);
752     return;
753   }
754   serverlist = (struct servers *) malloc(sizeof(struct servers)*num_servers);
755   if (fread(serverlist, sizeof(struct servers), num_servers, cache) != num_servers) {
756     free(serverlist);
757     serverlist = NULL;
758     num_servers = 0;
759     fclose(cache);
760     return;
761   }
762   fclose(cache);
763 
764   /* hunt and kill old server lines from cache */
765   for(i=0;i<num_servers;i++)
766     {
767       int j;
768 
769       /* mark each server as needing to be refreshed */
770       serverlist[i].refresh = 1;
771 
772       /* skip the deletion below if the entry was received recently */
773       if (serverlist[i].lifetime-- > 0) continue;
774 
775       /* delete this entry by moving the ones above down */
776       for(j=i;j<num_servers-1;j++)
777         {
778           memcpy(&serverlist[j],&serverlist[j+1],sizeof(struct servers));
779         }
780 
781       /* adjust the current entry pointer, limit, and resize the memory */
782       i--;
783       num_servers--;
784       serverlist =
785         (struct servers *) realloc(serverlist,
786                                    sizeof(struct servers) * (num_servers));
787     }
788 }
789 
790 
parsemeta()791 void    parsemeta()
792 /* Read and Parse the metaserver information, either from the metaservers
793  * by UDP (1), from a single metaserver by TCP (3), or from the cache (2).
794  */
795 {
796   statusLevel = intDefault("metaStatusLevel", defaultStatLevel);
797 
798   if (statusLevel < 0)
799     statusLevel = 0;
800   else if (statusLevel >= statusNull)
801     statusLevel = statusNull - 1;
802 
803   ReadMetasSend();
804   LoadMetasCache();
805   metaHeight = 2 + N_OVERHEAD;
806 }
807 
808 
redraw(int i)809 static void redraw(int i)
810 /* Redraw line i in the list */
811 {
812   char    buf[LINE + 1];
813   struct servers *sp;
814 
815   /* can't say a thing if line is beyond server list */
816   if (i >= num_servers) {
817     /* but we can at least blank the line shown */
818     if (i < metaHeight-3) W_WriteText(metaList, 0, i+1, W_White, "", 0, 0);
819     return;
820   }
821 
822   sp = serverlist + i;
823 
824   snprintf(buf, LINE, "%-40s          ",
825            strlen(sp->comment) > 0 ? sp->comment : sp->address);
826   buf[80] = '\0';
827 
828   switch(sp->status) {
829   case statusOpen:
830     snprintf(buf + strlen(buf), sp->players,      "################");
831     snprintf(buf + strlen(buf), 17 - sp->players, "                ");
832     strcat(buf, "    ");
833     break;
834   case statusWait:
835     snprintf(buf + strlen(buf), 16, "################ Q%2d", sp->players);
836     break;
837   default:
838     snprintf(buf + strlen(buf), 16, "%-16s", statusStrings[sp->status]);
839     strcat(buf, "    ");
840     break;
841   }
842 
843   switch (sp->typeflag) {
844     case 'P':
845       strcat(buf, "Paradise");
846       break;
847     case 'B':
848       strcat(buf, "Bronco  ");
849       break;
850     case 'C':
851       strcat(buf, "Chaos   ");
852       break;
853     case 'I':
854       strcat(buf, "INL     ");
855       break;
856     case 'S':
857       strcat(buf, "Sturgeon");
858       break;
859     case 'H':
860       strcat(buf, "Hockey  ");
861       break;
862     case 'F':
863       strcat(buf, "Dogfight");
864       break;
865     default:
866       strcat(buf, "Unknown ");
867       break;
868     }
869 
870   {
871     int age = sp->age;
872     char *units;
873 
874     if (age > 86400) {
875       age = age / 86400;
876       units = "d";
877     } else if (age > 3600) {
878       age = age / 3600;
879       units = "h";
880     } else if (age > 90) {
881       age = age / 60;
882       units = "m";
883     } else {
884       units = "s";
885     }
886     sprintf(buf + strlen(buf), " %4d%s", age, units);
887   }
888 
889   strcat(buf, "    ");
890   if (sp->pid != -1) {
891     strcat(buf, sp->observer ? "Observing" : "Playing" );
892   } else {
893     switch (sp->exitstatus) {
894     case EXIT_FORK_FAILURE:
895       strcat(buf, "Cannot Start");
896       break;
897     case EXIT_UNKNOWN:
898     case EXIT_OK:
899       break;
900     case EXIT_CONNECT_FAILURE:
901       strcat(buf, "Connect Fail");
902       break;
903     case EXIT_LOGIN_FAILURE:
904       strcat(buf, "Login Fail");
905       break;
906     default:
907       {
908         int badversion = (sp->exitstatus - EXIT_BADVERSION_BASE);
909         if (badversion >= 0 && badversion <= MAXBADVERSION) {
910           strcat(buf, badversion_short_strings[badversion]);
911         } else {
912           strcat(buf, "Unknown Response");
913         }
914       }
915       break;
916     }
917   }
918 
919   W_Color color = W_White;
920   if (i == chosen) color = W_Green;
921   if (sp->status == statusCantConnect) color = W_Red;
922   if (sp->pid != -1) color = W_Cyan;
923 
924   W_WriteText(metaList, 0, i+1, color, buf, -1, 0);
925   sp->refresh = 0;
926 }
927 
928 
929 static char add_buffer[LINE];
930 static int add_offset;
931 
add_init()932 static void add_init()
933 {
934   add_buffer[0] = '\0';
935   add_offset = 0;
936 }
937 
add_redraw()938 static void add_redraw()
939 {
940   char buf[LINE + 1];
941 
942   snprintf(buf, LINE, "Add a server: %s_", add_buffer);
943   W_WriteText(metaList, 0, metaHeight-B_ADD, W_Yellow, buf, -1, 0);
944 }
945 
add_commit()946 static void add_commit()
947 {
948   struct servers *sp;
949 
950   grow(1);
951   sp = serverlist + num_servers;
952   num_servers++;
953   strncpy(sp->address, add_buffer, LINE);
954   sp->port = 2592;
955   sp->age = 0;
956   sp->when = time(NULL);
957   sp->refresh = 1;
958   sp->lifetime = 20;
959   sp->players = 0;
960   sp->status = statusNobody;
961   sp->typeflag = 'U';
962   strncpy(sp->comment, add_buffer, LINE);
963   sp->pid = -1;
964   sp->exitstatus = 0;
965   sp->observer = 0;
966   metawindow();
967 }
968 
add_key(W_Event * data)969 static int add_key(W_Event *data)
970 {
971   if (data->key == '\r') {
972     add_commit();
973     add_init();
974     add_redraw();
975   } else if (data->key == 21) {
976     add_init();
977     add_redraw();
978   } else if (data->key == 8 || data->key == '\177') {
979     if (add_offset > 0) {
980       add_buffer[add_offset-1] = '\0';
981       add_offset--;
982       add_redraw();
983     }
984   } else if (add_offset < (LINE-1)) {
985     add_buffer[add_offset+1] = '\0';
986     add_buffer[add_offset] = data->key;
987     add_offset++;
988     add_redraw();
989   }
990   return 0;
991 }
992 
metawindow()993 void    metawindow()
994 /* Show the meta server menu window */
995 {
996   int i, height;
997   char *header;
998   static int lastHeight = 0;
999 
1000   if (!metaWin) {
1001     height = 350 + metaHeight * (W_Textheight + 8) + 4 * (metaHeight - 1);
1002     metaWin = W_MakeWindow("Netrek Server List", 0, 0, 716, height, NULL, 2,
1003                            foreColor);
1004     W_SetBackgroundImage(metaWin, "Misc/map_back.png");
1005     logo = W_ReadImage(metaWin, "netrek-green-white-300px.png");
1006     metaList = W_MakeMenu("metalist", 50, 200, LINE, metaHeight, metaWin, 1);
1007     lastHeight = metaHeight;
1008     make_help();
1009   } else {
1010     if (metaHeight > lastHeight) {
1011       W_ReinitMenu(metaList, LINE, metaHeight);
1012       W_ResizeMenu(metaList, LINE, metaHeight);
1013       lastHeight = metaHeight;
1014     }
1015     // FIXME: handle metaList growing beyond metaWin
1016   }
1017 
1018   header = "Server                                            Players            Type       Age";
1019   W_WriteText(metaList, 0, 0, W_Cyan, header, -1, 0);
1020 
1021   for (i = 0; i < metaHeight; i++) redraw(i);
1022 
1023   /* Give the window the right name */
1024   W_RenameWindow(metaWin, metaWindowName);
1025 
1026   /* Add additional options */
1027   W_WriteText(metaList, 0, metaHeight-B_REFRESH, W_Yellow,
1028               "Refresh                                           (r)",
1029               -1, 0);
1030   add_redraw();
1031   W_WriteText(metaList, 0, metaHeight-B_HELP, W_Yellow,
1032                 "Help & Tips                                       (h)",
1033               -1, 0);
1034   W_WriteText(metaList, 0, metaHeight-B_QUIT, W_Yellow,
1035                 "Quit                                              (q)",
1036               -1, 0);
1037 
1038   /* Map window */
1039   W_MapWindow(metaList);
1040   W_MapWindow(metaWin);
1041 }
1042 
1043 
metadone(void)1044 static void metadone(void)
1045 {
1046   W_UnmapWindow(metaList);
1047   W_DropImage(logo);
1048   W_UnmapWindow(metaWin);
1049   SaveMetasCache();
1050   free(serverlist);
1051 }
1052 
1053 
refresh()1054 static void refresh()
1055 {
1056   W_WriteText(metaList, 0, metaHeight-B_REFRESH, W_Red,
1057               "Refresh (in progress)", -1, 0);
1058   W_NextScreenShot(metaWin, 0, 0);
1059   W_Flush();
1060   ReadMetasSend();
1061 }
1062 
1063 
refresh_cyclic()1064 static void refresh_cyclic()
1065 {
1066   struct servers *sp;
1067   int i, interval = 30;
1068 
1069   /* while we have a local player, chances are they want their network
1070   link for play, and their eyes are on the tactical, they don't need
1071   to know about other servers */
1072   for (i=0;i<num_servers;i++) {
1073     sp = serverlist + i;
1074     if (sp->pid != -1) {
1075       if (!sp->observer) return;
1076       interval = 90;
1077     }
1078   }
1079 
1080   /* don't do until sufficient time has elapsed */
1081   if ((time(NULL) - last) > interval) {
1082     W_NextScreenShot(metaWin, 0, 0);
1083     ReadMetasSend();
1084   }
1085 }
1086 
1087 
choose(int way)1088 static void choose(int way)
1089 {
1090   int was;
1091   int lo = 0;
1092   int hi = num_servers - 1;
1093 
1094   if (hi < 0) {
1095     return;
1096   }
1097 
1098   was = chosen;
1099   if (chosen == -1) {
1100     chosen = lo;
1101     if (way > 0) chosen = lo;
1102     if (way < 0) chosen = hi;
1103   } else {
1104     if (way > 0) { chosen++; if (chosen > hi) chosen = hi; }
1105     if (way < 0) { chosen--; if (chosen < lo) chosen = lo; }
1106   }
1107   if (was != chosen) {
1108     if (was != -1) redraw(was);
1109     redraw(chosen);
1110   }
1111 }
1112 
1113 
chose(int which,unsigned char key)1114 static int chose(int which, unsigned char key)
1115 {
1116   struct servers *sp;
1117   pid_t pid;
1118 
1119   if (which != chosen) {
1120     int was;
1121     was = chosen;
1122     chosen = which;
1123     if (was != -1) redraw(was);
1124     redraw(chosen);
1125   }
1126 
1127   sp = serverlist + which;
1128   xtrekPort = sp->port;
1129   if (key == W_RBUTTON) { /* Guess at an observer port */
1130     xtrekPort++;
1131     fprintf(stderr,
1132             "you chose to observe on %s, guessing port %d\n",
1133             sp->address, xtrekPort);
1134   }
1135   serverName = strdup(sp->address);
1136 
1137   sp->pid = -1;
1138   sp->exitstatus = EXIT_UNKNOWN;
1139   fprintf(stderr, "you chose server %s port %d\n", serverName, xtrekPort);
1140 
1141   if ((pid = fork()) == 0) {
1142     char *args[16];
1143     int argc = 0;
1144 
1145     setpgid(0, 0);
1146     args[argc++] = program;
1147     args[argc++] = "--server";
1148     args[argc++] = serverName;
1149     if (xtrekPort != DEFAULT_PORT) {
1150       char port[32];
1151       sprintf(port, "%d", xtrekPort);
1152       args[argc++] = "--port";
1153       args[argc++] = strdup(port);
1154     }
1155 
1156     if (key == W_MBUTTON)
1157       args[argc++] = "--fast-guest";
1158 
1159     args[argc++] = NULL;
1160 
1161     execvp(program, args);
1162     perror("execvp");
1163     _exit(1);
1164   }
1165 
1166   /* we are the parent, did the fork fail? */
1167   if (pid < 0) {
1168     perror("fork");
1169     sp->exitstatus = EXIT_FORK_FAILURE;
1170   } else {
1171     sp->pid = pid;
1172     sp->observer = (key == W_RBUTTON);
1173   }
1174 
1175   redraw(which);
1176   W_Flush();
1177   return 0;
1178 }
1179 
1180 
button(W_Event * data)1181 static int button(W_Event *data)
1182 {
1183   if ((data->y > 0) && (data->y <= num_servers)) { /* click on server */
1184     return chose(data->y - 1, data->key);
1185   }
1186   if (data->y == (metaHeight-B_REFRESH)) { /* refresh */
1187     refresh();
1188   } else if (data->y == metaHeight-B_HELP) { /* help */
1189     toggle_help();
1190   } else if (data->y == metaHeight-B_QUIT) { /* quit */
1191     metadone();
1192     terminate(0);
1193   }
1194   return 0;
1195 }
1196 
1197 
key(W_Event * data)1198 static int key(W_Event *data)
1199 {
1200   if (data->y == (metaHeight-B_ADD)) return add_key(data);
1201   if (data->key == 113 || data->key == 196) { /* q or ^d */
1202     metadone();
1203     terminate(0);
1204   } else if (data->key == 114 || data->key == 210) { /* r or ^r */
1205     refresh();
1206   } else if (data->key == W_Key_Up) {
1207     choose(-1);
1208   } else if (data->key == W_Key_Down) {
1209     choose(1);
1210   } else if (data->key == '\r' || data->key == ' ') { /* enter or space */
1211     if (chosen != -1) return chose(chosen, W_LBUTTON);
1212   } else if (data->key == 'g') { /* g, for guest */
1213     if (chosen != -1) return chose(chosen, W_MBUTTON);
1214   } else if (data->key == 'o') { /* o, for observe */
1215     if (chosen != -1) return chose(chosen, W_RBUTTON);
1216   } else if (data->key == 'h') {
1217     toggle_help();
1218   } else {
1219     return button(data);
1220   }
1221   return 0;
1222 }
1223 
1224 
1225 static int metareap_needed = 0;
1226 
1227 /*! @brief Check if a child process, a playing client, has terminated.
1228     @details Attempts a no hang wait on each active client process in
1229     the server list and clears the pid entry if a child has
1230     terminated.
1231     @return activity count, number of processes seen to have terminated. */
metareap(void)1232 static int metareap(void)
1233 {
1234   struct servers *sp;
1235   int i, status, activity;
1236   pid_t pid;
1237 
1238   metareap_needed = 0;
1239   activity = 0;
1240   for (i=0;i<num_servers;i++) {
1241     sp = serverlist + i;
1242     if (sp->pid != -1) {
1243       pid = waitpid(sp->pid, &status, WNOHANG);
1244       if (pid == sp->pid) {
1245         sp->pid = -1;
1246         if (WIFEXITED(status)) {
1247           activity++;
1248           sp->exitstatus = WEXITSTATUS(status);
1249         }
1250         redraw(i);
1251         W_Flush();
1252       }
1253     }
1254   }
1255   return activity;
1256 }
1257 
1258 
1259 /*! @brief child death signal handler
1260     @details does nothing much, but needs to exist to ensure that
1261     select(2) is given EINTR when a child terminates, otherwise we
1262     will not note a termination until after the select times out.
1263 */
sigchld(int ignored)1264 void sigchld(int ignored)
1265 {
1266   metareap_needed++;
1267 }
1268 
1269 
metainput(void)1270 void    metainput(void)
1271 /* Wait for actions in the meta-server window.
1272  *
1273  * This is really the meta-server window's own little input() function. It is
1274  * needed so we don't have to use all the bull in the main input(). Plus to
1275  * use it I'd have to call mapAll() first and the client would read in the
1276  * default server and then call it up before I can select a server. */
1277 {
1278   W_Event data;
1279 
1280   (void) SIGNAL(SIGCHLD, sigchld);
1281   while (W_IsMapped(metaWin)) {
1282     while (1) {
1283       W_Flush();
1284       if (W_EventsPending()) break;
1285       if (ReadMetasRecv(W_Socket()) || metareap_needed) {
1286         metareap();
1287         metaHeight = num_servers + N_OVERHEAD;
1288         metawindow();
1289         W_Flush();
1290       }
1291       refresh_cyclic();
1292     }
1293     W_NextEvent(&data);
1294     switch ((int) data.type) {
1295     case W_EV_KEY:
1296       if (data.Window == metaList || data.Window == metaWin)
1297         if (key(&data)) return;
1298       if (data.Window == metaHelpWin) hide_help();
1299       break;
1300     case W_EV_BUTTON:
1301       if (data.Window == metaList)
1302         if (button(&data)) return;
1303       if (data.Window == metaHelpWin) hide_help();
1304       if (data.Window == metaWin && data.y < 200)
1305         W_NextScreenShot(metaWin, 0, 0);
1306       break;
1307     case W_EV_EXPOSE:
1308       if (data.Window == metaHelpWin) expo_help();
1309       if (data.Window == metaWin) {
1310         W_DrawScreenShot(metaWin, 0, 0);
1311         W_DrawImage(200, 9, logo);
1312       }
1313       break;
1314     case W_EV_CLOSED:
1315       if (data.Window == metaWin) {
1316         fprintf(stderr, "you quit, by closing the server list window\n");
1317         terminate(0);
1318       }
1319       break;
1320     default:
1321       break;
1322     }
1323   }
1324 }
1325 
1326 #endif
1327