Fix crash after recursive aliasing
[capablanca.git] / lasker-2.2.3 / src / command.c
1 /*
2    Copyright (c) 1993 Richard V. Nash.
3    Copyright (c) 2000 Dan Papasian
4    Copyright (C) Andrew Tridgell 2002
5    
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "includes.h"
22 #include "command_list.h"
23
24 const char *usage_dir[NUM_LANGS] = {USAGE_DIR, USAGE_SPANISH, 
25                                            USAGE_FRENCH, USAGE_DANISH};
26
27 static int lastCommandFound = -1;
28
29 static char *guest_name(void);
30 static int check_user(char *user);
31
32 /* Copies command into comm, and returns pointer to parameters in
33  * parameters
34  */
35 static int parse_command(char *com_string,
36                            char **comm,
37                            char **parameters)
38 {
39         *comm = com_string;
40         *parameters = eatword(com_string);
41         if (**parameters != '\0') {
42                 **parameters = '\0';
43                 (*parameters)++;
44                 *parameters = eatwhite(*parameters);
45         }
46         if (strlen(*comm) >= MAX_COM_LENGTH) {
47                 return COM_BADCOMMAND;
48         }
49         return COM_OK;
50 }
51
52 /* numalias is the maximum number to search through */
53 int alias_lookup(char *tmp, struct alias_type *alias_list, int numalias)
54 {
55         int i;
56         
57         for (i = 0; i < numalias && alias_list[i].comm_name; i++) {
58                 if (!strcasecmp(tmp, alias_list[i].comm_name))
59                         return i;
60         }
61         return -1;                      /* not found */
62 }
63
64 /* Puts alias substitution into alias_string */
65 static void alias_substitute(struct alias_type *alias_list, int num_alias,
66                              char *com_str, char outalias[])
67 {
68         char *name, *atpos;
69         int i;
70         int at_offset=0;
71
72         /* handle punctuation commands like '+' */
73         if (ispunct(*com_str)) {
74                 int n;
75                 for (n=0;ispunct(com_str[n]);n++) ;
76                 name = strndup(com_str, n);
77         } else {
78                 name = strndup(com_str, strcspn(com_str, " \t"));
79         }
80         com_str += strlen(name);
81
82         while (isspace(*com_str)) com_str++;
83
84         i = alias_lookup(name, alias_list, num_alias);
85
86         if (i >= 0) {
87                 free(name);
88                 name = strdup(alias_list[i].alias);
89         }
90
91         /* now substitute '@' values in name */
92         while ((atpos = strchr(name+at_offset, '@'))) {
93                 char *name2 = NULL;
94                 asprintf(&name2, "%*.*s%s%s", 
95                          atpos-name, atpos-name, name,
96                          com_str,
97                          atpos+1);
98                 if (!name2) break;
99
100                 /* try to prevent loops */
101                 at_offset = (atpos - name) + strlen(com_str);
102
103                 free(name);
104                 name = name2;
105         }
106
107         /* there is an implicit @ after each alias */
108         if (at_offset == 0 && *com_str) {
109           sprintf(outalias, "%s %s", name, com_str);
110         } else {
111           strcpy(outalias, name);
112         }
113
114         free(name);
115 }
116
117 /* Returns pointer to command that matches */
118 static int match_command(char *comm, int p)
119 {
120         int i = 0;
121         int gotIt = -1;
122         int len = strlen(comm);
123
124         while (command_list[i].comm_name) {
125                 if (strncmp(command_list[i].comm_name, comm, len) == 0 &&
126                     check_admin(p, command_list[i].adminLevel)) {
127                         if (gotIt >= 0)
128                                 return -COM_AMBIGUOUS;
129                         gotIt = i;
130                 }
131                 i++;
132         }
133
134         if (gotIt == -1) {
135                 return -COM_FAILED;
136         }
137
138         if (in_list(p, L_REMOVEDCOM, command_list[gotIt].comm_name)) {
139                 pprintf(p, "Due to a bug - this command has been temporarily removed.\n");
140                 return -COM_FAILED;
141         }
142         lastCommandFound = gotIt;
143         return gotIt;
144 }
145
146 /* Gets the parameters for this command */
147 static int get_parameters(int command, char *parameters, param_list params)
148 {
149   int i, parlen;
150   int paramLower;
151   char c;
152   static char punc[2];
153
154   punc[1] = '\0';               /* Holds punc parameters */
155   for (i = 0; i < MAXNUMPARAMS; i++)
156     (params)[i].type = TYPE_NULL;       /* Set all parameters to NULL */
157   parlen = strlen(command_list[command].param_string);
158   for (i = 0; i < parlen; i++) {
159     c = command_list[command].param_string[i];
160     if (isupper(c)) {
161       paramLower = 0;
162       c = tolower(c);
163     } else {
164       paramLower = 1;
165     }
166     switch (c) {
167     case 'w':
168     case 'o':                   /* word or optional word */
169       parameters = eatwhite(parameters);
170       if (!*parameters)
171         return (c == 'o' ? COM_OK : COM_BADPARAMETERS);
172       (params)[i].val.word = parameters;
173       (params)[i].type = TYPE_WORD;
174       if (ispunct(*parameters)) {
175         punc[0] = *parameters;
176         (params)[i].val.word = punc;
177         parameters++;
178         if (*parameters && isspace(*parameters))
179           parameters++;
180       } else {
181         parameters = eatword(parameters);
182         if (*parameters != '\0') {
183           *parameters = '\0';
184           parameters++;
185         }
186       }
187       if (paramLower)
188         stolower((params)[i].val.word);
189       break;
190
191     case 'd':
192     case 'p':                   /* optional or required integer */
193       parameters = eatwhite(parameters);
194       if (!*parameters)
195         return (c == 'p' ? COM_OK : COM_BADPARAMETERS);
196       if (sscanf(parameters, "%d", &(params)[i].val.integer) != 1)
197         return COM_BADPARAMETERS;
198       (params)[i].type = TYPE_INT;
199       parameters = eatword(parameters);
200       if (*parameters != '\0') {
201         *parameters = '\0';
202         parameters++;
203       }
204       break;
205
206     case 'i':
207     case 'n':                   /* optional or required word or integer */
208       parameters = eatwhite(parameters);
209       if (!*parameters)
210         return (c == 'n' ? COM_OK : COM_BADPARAMETERS);
211       if (sscanf(parameters, "%d", &(params)[i].val.integer) != 1) {
212         (params)[i].val.word = parameters;
213         (params)[i].type = TYPE_WORD;
214       } else {
215         (params)[i].type = TYPE_INT;
216       }
217       if (ispunct(*parameters)) {
218         punc[0] = *parameters;
219         (params)[i].val.word = punc;
220         (params)[i].type = TYPE_WORD;
221         parameters++;
222         if (*parameters && isspace(*parameters))
223           parameters++;
224       } else {
225         parameters = eatword(parameters);
226         if (*parameters != '\0') {
227           *parameters = '\0';
228           parameters++;
229         }
230       }
231       if ((params)[i].type == TYPE_WORD)
232         if (paramLower)
233           stolower((params)[i].val.word);
234       break;
235
236     case 's':
237     case 't':                   /* optional or required string to end */
238       if (!*parameters)
239         return (c == 't' ? COM_OK : COM_BADPARAMETERS);
240       (params)[i].val.string = parameters;
241       (params)[i].type = TYPE_STRING;
242       while (*parameters)
243         parameters = nextword(parameters);
244       if (paramLower)
245         stolower((params)[i].val.string);
246       break;
247     }
248   }
249   if (*parameters)
250     return COM_BADPARAMETERS;
251   else
252     return COM_OK;
253 }
254
255 static void printusage(int p, char *command_str)
256 {
257   struct player *pp = &player_globals.parray[p];
258   int i, parlen, UseLang = pp->language;
259   int command;
260   char c;
261
262   char *filenames[1000];        /* enough for all usage names */
263
264   if ((command = match_command(command_str, p)) < 0) {
265     pprintf(p, "  UNKNOWN COMMAND\n");
266     return;
267   }
268
269 /*Usage added by DAV 11/19/95 */
270   /* First lets check if we have a text usage file for it */
271
272   i = search_directory(usage_dir[UseLang], command_str, filenames, 1000);
273   if (i == 0) { /* nope none in current Lang */
274     if (UseLang != LANG_DEFAULT) {
275       i += search_directory(usage_dir[LANG_DEFAULT], command_str, filenames, 1000);
276       if (i > 0) {
277         pprintf(p, "No usage available in %s; using %s instead.\n",
278                 Language(UseLang), Language(LANG_DEFAULT));
279         UseLang = LANG_DEFAULT;
280       }
281     }
282   }
283
284  if (i != 0) {
285   if ((i == 1) || (!strcmp(*filenames, command_str))) { /* found it? then send */
286     if (psend_file(p, usage_dir[UseLang], *filenames)) {
287       /* we should never reach this unless the file was just deleted */
288       pprintf(p, "Usage file %s could not be found! ", *filenames);
289       pprintf(p, "Please inform an admin of this. Thank you.\n");
290       /* no need to print 'system' usage - should never happen */
291     }
292     pprintf(p, "\nSee '%s %s' for a complete description.\n", 
293             ((command_list[lastCommandFound].adminLevel > ADMIN_USER) ? "ahelp" :
294 "help"),
295             command_list[lastCommandFound].comm_name);
296     return;
297   } 
298  } 
299
300   /* print the default 'system' usage files (which aren't much help!) */
301
302   pprintf(p, "Usage: %s", command_list[lastCommandFound].comm_name);
303
304   parlen = strlen(command_list[command].param_string);
305   for (i = 0; i < parlen; i++) {
306     c = command_list[command].param_string[i];
307     if (isupper(c))
308       c = tolower(c);
309     switch (c) {
310     case 'w':                   /* word */
311       pprintf(p, " word");
312       break;
313     case 'o':                   /* optional word */
314       pprintf(p, " [word]");
315       break;
316     case 'd':                   /* integer */
317       pprintf(p, " integer");
318       break;
319     case 'p':                   /* optional integer */
320       pprintf(p, " [integer]");
321       break;
322     case 'i':                   /* word or integer */
323       pprintf(p, " {word, integer}");
324       break;
325     case 'n':                   /* optional word or integer */
326       pprintf(p, " [{word, integer}]");
327       break;
328     case 's':                   /* string to end */
329       pprintf(p, " string");
330       break;
331     case 't':                   /* optional string to end */
332       pprintf(p, " [string]");
333       break;
334     }
335   }
336   pprintf(p, "\nSee '%s %s' for a complete description.\n", 
337           ((command_list[lastCommandFound].adminLevel > ADMIN_USER) ? "ahelp" :
338 "help"),
339           command_list[lastCommandFound].comm_name);
340 }
341
342 static int one_command(int p, char *command, char **cmd)
343 {
344         struct player *pp = &player_globals.parray[p];
345         int which_command, retval;
346         char *comm=NULL, *parameters=NULL;
347         param_list params;
348
349         if ((retval = parse_command(command, &comm, &parameters)))
350                 return retval;
351         if (pp->game >= 0) {
352                 if ((game_globals.garray[pp->game].status == GAME_SETUP) && (is_drop(comm)))
353                         return COM_ISMOVE;
354         }
355         if (is_move(comm)) {
356                 if (pp->game == -1) {
357                         pprintf(p, "You are not playing or examining a game.\n");
358                         return COM_OK;
359                 }
360
361                 if (game_globals.garray[pp->game].status == GAME_SETUP)
362                         return COM_ISMOVE_INSETUP;
363                 else 
364                         return COM_ISMOVE;
365         }
366         stolower(comm);         /* All commands are case-insensitive */
367         *cmd = comm;
368         if ((which_command = match_command(comm, p)) < 0)
369                 return -which_command;
370         if (!check_admin(p, command_list[which_command].adminLevel)) {
371                 return COM_RIGHTS;
372         }
373         if ((retval = get_parameters(which_command, parameters, params)))
374                 return retval;
375
376         if (command_list[which_command].adminLevel >= ADMIN_ADMIN) {
377                 admin_log(pp, command, params);
378         }
379
380         return command_list[which_command].comm_func(p, params);
381 }
382
383 static int process_command(int p, char *com_string, char **cmd)
384 {
385         struct player *pp = &player_globals.parray[p];
386         char *tok;
387         char *ptr = NULL;
388         char astring1[MAX_STRING_LENGTH * 4];
389         char astring2[MAX_STRING_LENGTH * 4];
390
391 #ifdef DEBUG
392         if (strcasecmp(pp->name, pp->login)) {
393                 d_printf( "CHESSD: PROBLEM Name=%s, Login=%s\n", 
394                         pp->name, pp->login);
395         }
396 #endif
397         if (!com_string)
398                 return COM_FAILED;
399 #ifdef DEBUG
400         d_printf( "%s, %s, %d: >%s<\n", 
401                 pp->name, pp->login, pp->socket, com_string);
402 #endif
403
404         /* don't expand the alias command */
405         if (strncmp(com_string, "alias ", 6) == 0) {
406                 return one_command(p, com_string, cmd);
407         }
408
409         /* don't expand the alias command */
410         if (com_string[0] == '$') {
411                 return one_command(p, eatwhite(com_string+1), cmd);
412         }
413
414         alias_substitute(pp->alias_list, pp->numAlias,
415                          com_string, astring1);
416         alias_substitute(g_alias_list, 999,
417                          astring1, astring2);
418
419 #ifdef DEBUG
420         if (strcmp(com_string, astring2) != 0) {
421                 d_printf( "%s -alias-: >%s< >%s<\n", 
422                         pp->name, com_string, astring2);
423         }
424 #endif
425
426         for (tok=strtok_r(astring2, ";", &ptr); tok; tok=strtok_r(NULL, ";", &ptr)) {
427                 char alias_string1[MAX_STRING_LENGTH * 4];
428                 char alias_string2[MAX_STRING_LENGTH * 4];
429                 int retval;
430                 while (isspace(*tok)) tok++;
431
432                 alias_substitute(pp->alias_list, pp->numAlias,
433                                  tok, alias_string1);
434                 alias_substitute(g_alias_list, 999,
435                                  alias_string1, alias_string2);
436
437 #ifdef DEBUG
438                 if (strcmp(tok, alias_string2) != 0) {
439                         d_printf( "%s -alias2-: >%s<\n", 
440                                 pp->name, alias_string2);
441                 }
442 #endif
443
444                 retval = one_command(p, alias_string2, cmd);
445
446                 /* stop on first error */
447                 if (retval != COM_OK) return retval;
448         }
449
450         return COM_OK;
451 }
452
453 static int process_login(int p, char *loginname)
454 {
455         struct player *pp = &player_globals.parray[p];
456         char loginnameii[80];
457         int is_guest = 0;
458
459         loginname = eatwhite(loginname);
460
461         if (!*loginname) {
462                 goto new_login;
463         }
464
465         /* if 'guest' was specified then pick a random guest name */
466         if (strcasecmp(loginname, config_get_tmp("GUEST_LOGIN")) == 0) {
467                 loginname = guest_name();
468                 is_guest = 1;
469                 pprintf(p,"\nCreated temporary login '%s'\n", loginname);
470         }
471
472         strlcpy(loginnameii, loginname, sizeof(loginnameii));
473
474         if (!alphastring(loginnameii)) {
475                 pprintf(p, "\nSorry, names can only consist of lower and upper case letters.  Try again.\n");
476                 goto new_login;
477         } 
478         if (strlen(loginnameii) < 3) {
479                 pprintf(p, "\nA name should be at least three characters long!  Try again.\n");
480                 goto new_login;
481         }
482
483         if (strlen(loginnameii) > (MAX_LOGIN_NAME - 1)) {
484                 pprintf(p, "\nSorry, names may be at most %d characters long.  Try again.\n",
485                         MAX_LOGIN_NAME - 1);
486                 goto new_login;
487         } 
488
489         if (in_list(p, L_BAN, loginnameii)) {
490                 pprintf(p, "\nPlayer \"%s\" is banned.\n", loginnameii);
491                 return COM_LOGOUT;
492         }
493
494         if (!in_list(p, L_ADMIN, loginnameii) &&
495             player_count(0) >= config_get_int("MAX_PLAYER", DEFAULT_MAX_PLAYER)) {
496                 psend_raw_file(p, MESS_DIR, MESS_FULL);
497                 return COM_LOGOUT;
498         } 
499
500         if (player_read(p, loginnameii) != 0) {
501                 if (!is_guest && 
502                     config_get_int("GUEST_PREFIX_ONLY", DEFAULT_GUEST_PREFIX_ONLY)) {
503                         goto new_login;
504                 }
505                 /* they are not registered */
506                 strcpy(pp->name, loginnameii);
507                 if (in_list(p, L_FILTER, dotQuad(pp->thisHost))) {
508                         pprintf(p, "\nDue to abusive behavior, nobody from your site may login.\n");
509                         pprintf(p, "If you wish to use this server please email %s\n", 
510                                 config_get_tmp("REGISTRATION_ADDRESS"));
511                         pprintf(p, "Include details of a nick-name to be called here, e-mail address and your real name.\n");
512                         pprintf(p, "We will send a password to you. Thanks.\n");
513                         return COM_LOGOUT;
514                 }
515
516                 if (player_count(0) >= config_get_int("MAX_PLAYER", DEFAULT_MAX_PLAYER) - 100) {
517                         psend_raw_file(p, MESS_DIR, MESS_FULL_UNREG);
518                         return COM_LOGOUT;
519                 }
520
521                 pprintf_noformat(p, "\n\"%s\" is not a registered name.  You may play unrated games as a guest.\n(After logging in, do \"help register\" for more info on how to register.)\n\nPress return to enter the FICS as \"%s\":", 
522                                  loginnameii, loginnameii);
523                 pp->status = PLAYER_PASSWORD;
524                 turn_echo_off(pp->socket);
525                 return COM_OK;
526         }
527
528         pprintf_noformat(p, "\n\"%s\" is a registered name.  If it is yours, type the password.\nIf not, just hit return to try another name.\n\npassword: ", 
529                          pp->name);
530         pp->status = PLAYER_PASSWORD;
531         turn_echo_off(pp->socket);
532         if (strcasecmp(loginnameii, pp->name)) {
533                 pprintf(p, "\nYou've got a bad name field in your playerfile -- please report this to an admin!\n");
534                 return COM_LOGOUT;
535         }
536
537         if (CheckPFlag(p, PFLAG_REG)
538             && (pp->fullName == NULL)) {
539                 pprintf(p, "\nYou've got a bad playerfile -- please report this to an admin!\n");
540                 pprintf(p, "Your FullName is missing!");
541                 pprintf(p, "Please log on as an unreg until an admin can correct this.\n");
542                 return COM_LOGOUT;
543         }
544         if (CheckPFlag(p, PFLAG_REG)
545             && (pp->emailAddress == NULL)) {
546                 pprintf(p, "\nYou've got a bad playerfile -- please report this to an admin!\n");
547                 pprintf(p, "Your Email address is missing\n");
548                 pprintf(p, "Please log on as an unreg until an admin can correct this.\n");
549                 return COM_LOGOUT;
550         }
551   
552   
553         return COM_OK;
554
555 new_login:
556         /* give them a new prompt */
557         psend_raw_file(p, MESS_DIR, MESS_LOGIN);
558         pprintf(p, "login: ");
559         return COM_OK;
560 }
561
562 static void boot_out(int p, int p1)
563 {
564         struct player *pp = &player_globals.parray[p];
565         int fd;
566         pprintf(p, "\n **** %s is already logged in - kicking them out. ****\n", pp->name);
567         pprintf(p1, "**** %s has arrived - you can't both be logged in. ****\n", pp->name);
568         fd = player_globals.parray[p1].socket;
569         process_disconnection(fd);
570         net_close_connection(fd);
571 }
572
573 static int process_password(int p, char *password)
574 {
575   struct player *pp = &player_globals.parray[p];
576   static int Current_ad;
577   int p1;
578   char salt[3];
579   int fd;
580   struct in_addr fromHost;
581   int messnum;
582   char fname[10];
583   int dummy; /* (to hold a return value) */ 
584
585   turn_echo_on(pp->socket);
586
587   if (pp->passwd && CheckPFlag(p, PFLAG_REG)) {
588     salt[0] = pp->passwd[3];
589     salt[1] = pp->passwd[4];
590     salt[2] = '\0';
591     if (strcmp(chessd_crypt(password,salt), pp->passwd)) {
592       fd = pp->socket;
593       fromHost = pp->thisHost;
594       if (*password) {
595         pprintf(p, "\n\n**** Invalid password! ****\n\n");
596         d_printf("FICS (process_password): Bad password for %s [%s] [%s] [%s]\n",
597                 pp->login, 
598                 password, 
599                 salt,
600                 pp->passwd);
601       }
602       player_clear(p);
603       pp->logon_time = pp->last_command_time = time(0);
604       pp->status = PLAYER_LOGIN;
605       pp->socket = fd;
606       if (fd >= net_globals.no_file)
607         d_printf("FICS (process_password): Out of range fd!\n");
608
609       pp->thisHost = fromHost;
610
611       psend_raw_file(p, MESS_DIR, MESS_LOGIN);
612       pprintf(p, "login: ");
613       return COM_OK;
614     }
615   }
616   for (p1 = 0; p1 < player_globals.p_num; p1++) {
617     if (player_globals.parray[p1].name != NULL) {
618       if ((!strcasecmp(pp->name, player_globals.parray[p1].name)) && (p != p1)) {
619         if (!CheckPFlag(p, PFLAG_REG)) {
620           pprintf(p, "\n*** Sorry %s is already logged in ***\n", pp->name);
621           return COM_LOGOUT;
622         }
623         boot_out(p, p1);
624       }
625     }
626   }
627   
628   if (player_ishead(p)) {
629           pprintf(p,"\n  ** LOGGED IN AS HEAD ADMIN **\n");
630           pp->adminLevel = ADMIN_GOD;
631   }
632
633   news_login(p);
634
635   if (pp->adminLevel > 0) {
636     psend_raw_file(p, MESS_DIR, MESS_ADMOTD);
637   } else {
638     psend_raw_file(p, MESS_DIR, MESS_MOTD);
639   }
640   if (MAX_ADVERTS >=0) {
641     pprintf (p, "\n");
642     sprintf (fname,"%d",Current_ad);
643     Current_ad = (Current_ad + 1) % MAX_ADVERTS;
644     psend_raw_file(p, ADVERT_DIR, fname);
645   }
646   if (!pp->passwd && CheckPFlag(p, PFLAG_REG))
647     pprintf(p, "\n*** You have no password. Please set one with the password command.");
648   if (!CheckPFlag(p, PFLAG_REG))
649     psend_raw_file(p, MESS_DIR, MESS_UNREGISTERED);
650   pp->status = PLAYER_PROMPT;
651   player_write_login(p);
652   for (p1 = 0; p1 < player_globals.p_num; p1++) {
653     if (p1 == p)
654       continue;
655     if (player_globals.parray[p1].status != PLAYER_PROMPT)
656       continue;
657     if (!CheckPFlag(p1, PFLAG_PIN))
658       continue;
659     if (player_globals.parray[p1].adminLevel > 0) {
660       pprintf_prompt(p1, "\n[%s (%s: %s) has connected.]\n", pp->name,
661                      (CheckPFlag(p, PFLAG_REG) ? "R" : "U"),
662                      dotQuad(pp->thisHost));
663     } else {
664       pprintf_prompt(p1, "\n[%s has connected.]\n", pp->name);
665     }
666   }
667   pp->num_comments = player_num_comments(p);
668   messnum = player_num_messages(p);
669
670   if (messnum) {
671     if (messnum == 1)
672       pprintf(p, "\nYou have 1 message.\nUse \"messages\" to display it, or \"clearmessages\" to remove it.\n");
673     else
674       pprintf(p, "\nYou have %d messages.\nUse \"messages\" to display them, or \"clearmessages\" to remove them.\n", messnum);
675   }
676
677   player_notify_present(p);
678   player_notify(p, "arrived", "arrival");
679   showstored(p);
680
681   if (CheckPFlag(p, PFLAG_REG) && (pp->lastHost.s_addr != 0) &&
682       (pp->lastHost.s_addr != pp->thisHost.s_addr)) {
683     pprintf(p, "\nPlayer %s: Last login: %s ", pp->name,
684             dotQuad(pp->lastHost));
685     pprintf(p, "This login: %s", dotQuad(pp->thisHost));
686   }
687   pp->lastHost = pp->thisHost;
688   if (CheckPFlag(p, PFLAG_REG) && !pp->timeOfReg)
689     pp->timeOfReg = time(0);
690   pp->logon_time = pp->last_command_time = time(0);
691   dummy = check_and_print_shutdown(p);
692   pprintf_prompt(p, "\n");
693   if (CheckPFlag(p, PFLAG_REG))
694     announce_avail(p);
695   return 0;
696 }
697
698 FILE *comlog = NULL;
699
700 static int process_prompt(int p, char *command)
701 {
702   struct player *pp = &player_globals.parray[p];
703   int retval;
704   char *cmd = "";
705
706         if(comlog == NULL) comlog = fopen("command.log", "a");
707         if(comlog) fprintf(comlog, "p%d: '%s'\n", p, command), fflush(comlog);
708
709   command = eattailwhite(eatwhite(command));
710   if (!*command) {
711           send_prompt(p);
712           return COM_OK;
713   }
714   retval = process_command(p, command, &cmd);
715   switch (retval) {
716   case COM_OK:
717     retval = COM_OK;
718     send_prompt(p);
719     break;
720   case COM_OK_NOPROMPT:
721     retval = COM_OK;
722     break;
723   case COM_ISMOVE:
724     retval = COM_OK;
725
726     if (pp->game >= 0 && game_globals.garray[pp->game].status == GAME_ACTIVE
727         && pp->side == game_globals.garray[pp->game].game_state.onMove
728         && game_globals.garray[pp->game].flag_pending != FLAG_NONE) {
729             ExecuteFlagCmd(p, net_globals.con[pp->socket]);
730     }
731
732     process_move(p, command);
733     send_prompt(p);
734
735     break;
736   case COM_ISMOVE_INSETUP:
737     pprintf(p, "You are still setting up the position.\n");
738     pprintf_prompt(p, "Type: 'setup done' when you are finished editing.\n");
739     retval = COM_OK;
740     break;
741   case COM_RIGHTS:
742     pprintf_prompt(p, "%s: Insufficient rights.\n", cmd);
743     retval = COM_OK;
744     break;
745   case COM_AMBIGUOUS:
746 /*    pprintf(p, "%s: Ambiguous command.\n", cmd); */
747     {
748       int len = strlen(cmd);
749       int i = 0;
750       pprintf(p, "Ambiguous command. Matches:");
751       while (command_list[i].comm_name) {
752         if (strncmp(command_list[i].comm_name, cmd, len) == 0 &&
753             check_admin(p, command_list[i].adminLevel)) {
754                 pprintf(p, " %s", command_list[i].comm_name);
755         }
756         i++;
757       }
758     }
759     pprintf_prompt(p, "\n");
760     retval = COM_OK;
761     break;
762   case COM_BADPARAMETERS:
763     printusage(p, command_list[lastCommandFound].comm_name);
764     send_prompt(p);
765     retval = COM_OK;
766     break;
767   case COM_FAILED:
768   case COM_BADCOMMAND:
769     pprintf_prompt(p, "%s: Command not found.\n", cmd);
770     d_printf("Command not found [%s]\n", cmd);
771     retval = COM_OK;
772     break;
773   case COM_LOGOUT:
774     retval = COM_LOGOUT;
775     break;
776   }
777   return retval;
778 }
779
780 /* Return 1 to disconnect */
781 int process_input(int fd, char *com_string)
782 {
783   int p = player_find(fd);
784   int retval = 0;
785   struct player *pp;
786
787   if (p < 0) {
788     d_printf( "CHESSD: Input from a player not in array!\n");
789     return -1;
790   }
791
792   pp = &player_globals.parray[p];
793
794   command_globals.commanding_player = p;
795   pp->last_command_time = time(0);
796
797   switch (pp->status) {
798   case PLAYER_EMPTY:
799     d_printf( "CHESSD: Command from an empty player!\n");
800     break;
801   case PLAYER_NEW:
802     d_printf( "CHESSD: Command from a new player!\n");
803     break;
804   case PLAYER_INQUEUE:
805     /* Ignore input from player in queue */
806     break;
807   case PLAYER_LOGIN:
808     retval = process_login(p, com_string);
809     if (retval == COM_LOGOUT && com_string != NULL)
810       d_printf("%s tried to log in and failed.\n", com_string);
811     break;
812   case PLAYER_PASSWORD:
813     retval = process_password(p, com_string);
814     break;
815   case PLAYER_PROMPT:
816           FREE(pp->busy);
817           retval = process_prompt(p, com_string);
818           break;
819   }
820
821   command_globals.commanding_player = -1;
822   return retval;
823 }
824
825 int process_new_connection(int fd, struct in_addr fromHost)
826 {
827         struct player *pp;
828         int p = player_new();
829
830         pp = &player_globals.parray[p];
831
832         pp->status = PLAYER_LOGIN;
833         if (fd >= net_globals.no_file)
834                 d_printf("FICS (process_new_connection): Out of range fd!\n");
835         
836         pp->socket = fd;
837         pp->thisHost = fromHost;
838         pp->logon_time = time(0);
839         psend_raw_file(p, MESS_DIR, MESS_WELCOME);
840         pprintf(p, "Head admin : %s", config_get_tmp("HEAD_ADMIN"));
841         pprintf(p, "    Complaints to : %s\n", config_get_tmp("HEAD_ADMIN_EMAIL"));
842         pprintf(p, "Server location: %s", config_get_tmp("SERVER_LOCATION"));
843         pprintf(p, "    Server version : %s\n", VERS_NUM);
844         pprintf(p, "Server name : %s\n", config_get_tmp("SERVER_HOSTNAME"));
845         psend_raw_file(p, MESS_DIR, MESS_LOGIN);
846         pprintf(p, "login: ");
847         
848         return 0;
849 }
850
851 int process_disconnection(int fd)
852 {
853   int p = player_find(fd);
854   int p1;
855   char command[1024];
856   struct player *pp;
857
858   if (p < 0) {
859     d_printf( "CHESSD: Disconnect from a player not in array!\n");
860     return -1;
861   }
862
863   pp = &player_globals.parray[p];
864
865   if (CheckPFlag(p, PFLAG_REG) && CheckPFlag(p, PFLAG_OPEN) &&
866       pp->game < 0)
867     announce_notavail(p);
868   if ((pp->game >=0) && ((game_globals.garray[pp->game].status == GAME_EXAMINE) || (game_globals.garray[pp->game].status == GAME_SETUP))) {
869     pcommand(p, "unexamine");
870   }
871   if ((pp->game >=0) && (in_list(p, L_ABUSER, pp->name)
872         || (game_globals.garray[pp->game].link >= 0)))
873
874      pcommand(p, "resign");
875
876   if (pp->ftell != -1)
877     pcommand (p,"tell 0 I am logging out now - conversation forwarding stopped.");
878
879   withdraw_seeks(p);
880
881   if (pp->status == PLAYER_PROMPT) {
882     for (p1 = 0; p1 < player_globals.p_num; p1++) {
883       if (p1 == p)
884         continue;
885       if (player_globals.parray[p1].status != PLAYER_PROMPT)
886         continue;
887
888       if (player_globals.parray[p1].ftell == p) {
889         player_globals.parray[p1].ftell = -1;
890         sprintf (command,"tell 0 *%s* has logged out - conversation forwarding stopped.",pp->name);
891         pcommand (p1,command);
892         pprintf_prompt (p1,"%s, whose tells you were forwarding has logged out.\n",
893                  pp->name);
894       }
895
896       if (!CheckPFlag(p1, PFLAG_PIN))
897         continue;
898       pprintf_prompt(p1, "\n[%s has disconnected.]\n", pp->name);
899     }
900     player_notify(p, "departed", "departure");
901     player_notify_departure(p);
902     player_write_logout(p);
903     if (CheckPFlag(p, PFLAG_REG)) {
904       pp->totalTime += time(0) - pp->logon_time;
905       player_save(p);
906     } else {                    /* delete unreg history file */
907       char fname[MAX_FILENAME_SIZE];
908       sprintf(fname, "%s/player_data/%c/%s.games", STATS_DIR, pp->login[0], pp->login);
909       unlink(fname);
910     }
911   }
912   player_remove(p);
913   return 0;
914 }
915
916 /* Called every few seconds */
917 int process_heartbeat(int *fd)
918 {
919         struct tm *nowtm;
920         int p;
921         time_t now = time(0); 
922         unsigned idle_timeout = config_get_int("IDLE_TIMEOUT", DEFAULT_IDLE_TIMEOUT);
923         unsigned login_timeout = config_get_int("LOGIN_TIMEOUT", DEFAULT_LOGIN_TIMEOUT);
924
925         /* Check for timed out connections */
926         for (p = 0; p < player_globals.p_num; p++) {
927                 struct player *pp = &player_globals.parray[p];
928
929                 if ((pp->status == PLAYER_LOGIN ||
930                      pp->status == PLAYER_PASSWORD) &&
931                     player_idle(p) > login_timeout) {
932                         pprintf(p, "\n**** LOGIN TIMEOUT ****\n");
933                         *fd = pp->socket;
934                         return COM_LOGOUT;
935                 }
936                 if (pp->status == PLAYER_PROMPT &&
937                     player_idle(p) > idle_timeout &&
938                     !check_admin(p, ADMIN_ADMIN) &&
939                     !in_list(p, L_TD, pp->name)) {
940                         pprintf(p, "\n**** Auto-logout - you were idle more than %u minutes. ****\n", 
941                                 idle_timeout/60);
942                         *fd = pp->socket;
943                         return COM_LOGOUT;
944                 }
945         }
946         nowtm=localtime((time_t *)&now);
947         if (nowtm->tm_min==0) {
948                 gics_globals.userstat.users[nowtm->tm_hour*2]=player_count(1);
949                 save_userstat();
950         }
951         if (nowtm->tm_min==30) {
952                 gics_globals.userstat.users[nowtm->tm_hour*2+1]=player_count(1);
953                 save_userstat();
954         }
955         if (command_globals.player_high > gics_globals.userstat.usermax) {
956                 gics_globals.userstat.usermax=command_globals.player_high;
957                 gics_globals.userstat.usermaxtime=now;
958                 save_userstat();
959         }
960         if (command_globals.game_high > gics_globals.userstat.gamemax) {
961                 gics_globals.userstat.gamemax=command_globals.game_high;
962                 gics_globals.userstat.gamemaxtime=now;
963                 save_userstat();
964         }
965         
966         ShutHeartBeat();
967         return COM_OK;
968 }
969
970 /* helper function for sorting command list */
971 static int command_compare(struct command_type *c1, struct command_type *c2)
972 {
973         return strcasecmp(c1->comm_name, c2->comm_name);
974 }
975
976 void commands_init(void)
977 {
978         FILE *fp, *afp;
979         int i = 0;
980         int count=0, acount=0;
981
982         /* sort the command list */
983         qsort(command_list, 
984               (sizeof(command_list)/sizeof(command_list[0])) - 1,
985               sizeof(command_list[0]),
986               (COMPAR_FN_T)command_compare);
987
988         command_globals.commanding_player = -1;
989         
990         fp = fopen_s(HELP_DIR "/commands", "w");
991         if (!fp) {
992                 d_printf( "CHESSD: Could not write commands help file.\n");
993                 return;
994         }
995         afp = fopen_s(ADHELP_DIR "/commands", "w");
996         if (!afp) {
997                 d_printf( "CHESSD: Could not write admin commands help file.\n");
998                 fclose(fp);
999                 return;
1000         }
1001
1002         while (command_list[i].comm_name) {
1003                 if (command_list[i].adminLevel >= ADMIN_ADMIN) {
1004                         fprintf(afp, "%-19s", command_list[i].comm_name);
1005                         acount++;
1006                         if (acount % 4 == 0) {
1007                                 fprintf(afp,"\n");
1008                         }
1009                 } else {
1010                         fprintf(fp, "%-19s", command_list[i].comm_name);
1011                         count++;
1012                         if (count % 4 == 0) {
1013                                 fprintf(fp,"\n");
1014                         }
1015                 }
1016                 i++;
1017         }
1018
1019         fprintf(afp,"\n");
1020         fprintf(fp,"\n");
1021
1022         fclose(fp);
1023         fclose(afp);
1024
1025         d_printf("CHESSD: Loaded %d commands (admin=%d normal=%d)\n", i, acount, count);
1026 }
1027
1028 /* Need to save rated games */
1029 void TerminateCleanup(void)
1030 {
1031         int p1;
1032         int g;
1033   
1034         save_userstat();
1035
1036         for (g = 0; g < game_globals.g_num; g++) {
1037                 if (game_globals.garray[g].status != GAME_ACTIVE)
1038                         continue;
1039                 if (game_globals.garray[g].rated) {
1040                         game_ended(g, WHITE, END_ADJOURN);
1041                 }
1042         }
1043         for (p1 = 0; p1 < player_globals.p_num; p1++) {
1044                 if (player_globals.parray[p1].status == PLAYER_EMPTY)
1045                         continue;
1046                 pprintf(p1, "\n    **** Server shutting down immediately. ****\n\n");
1047                 if (player_globals.parray[p1].status != PLAYER_PROMPT) {
1048                         close(player_globals.parray[p1].socket);
1049                 } else {
1050                         pprintf(p1, "Logging you out.\n");
1051                         psend_raw_file(p1, MESS_DIR, MESS_LOGOUT);
1052                         player_write_logout(p1);
1053                         if (CheckPFlag(p1, PFLAG_REG))
1054                                 player_globals.parray[p1].totalTime += time(0) - player_globals.parray[p1].logon_time;
1055                         player_save(p1);
1056                 }
1057         }
1058         destruct_pending();
1059 }
1060
1061 static char *guest_name(void)
1062 {
1063        static char name[20];
1064        int found;
1065
1066 #define RANDLET ((char)('A' + (random() % 26)))
1067        srandom(time(0));
1068
1069        found = 0;
1070        while(!found) {
1071                snprintf(name,sizeof(name), "Guest%c%c%c%c", RANDLET, RANDLET, RANDLET, RANDLET);
1072                found = check_user(name);
1073        }
1074 #undef RANDLET
1075        return name;
1076 }
1077
1078 static int check_user(char *user)
1079 {
1080        int i;
1081        
1082        for (i = 0; i < player_globals.p_num; i++) {
1083                if (player_globals.parray[i].name != NULL) {
1084                        if (!strcasecmp(user, player_globals.parray[i].name))
1085                                return 0;
1086                }
1087        }
1088        return 1;
1089 }
1090
1091
1092 /* return the global alias list */
1093 const struct alias_type *alias_list_global(void)
1094 {
1095         return g_alias_list;
1096 }
1097
1098 /* return a personal alias list */
1099 const struct alias_type *alias_list_personal(int p, int *n)
1100 {
1101         struct player *pp = &player_globals.parray[p];
1102
1103         *n = pp->numAlias;
1104         return pp->alias_list;
1105 }
1106
1107 /*
1108   report on any missing help pages
1109 */
1110 int com_acheckhelp(int p, param_list param)
1111 {
1112         int i;
1113         int count;
1114
1115         for (count=i=0; command_list[i].comm_name; i++) {
1116                 char *fname;
1117                 asprintf(&fname, "%s/%s", 
1118                          command_list[i].adminLevel?ADHELP_DIR:HELP_DIR, 
1119                          command_list[i].comm_name);
1120                 if (!file_exists(fname)) {
1121                         pprintf(p, "Help for command '%s' is missing%s\n",
1122                                 command_list[i].comm_name,
1123                                 command_list[i].adminLevel?" (admin)":"");
1124                         count++;
1125                 }
1126                 free(fname);
1127         }
1128
1129         pprintf(p, "%d commands are missing help files\n", count);
1130
1131         for (count=i=0; command_list[i].comm_name; i++) {
1132                 char *fname;
1133                 asprintf(&fname, "%s/%s", USAGE_DIR, command_list[i].comm_name);
1134                 if (!file_exists(fname)) {
1135                         pprintf(p, "Usage for command '%s' is missing%s\n",
1136                                 command_list[i].comm_name,
1137                                 command_list[i].adminLevel?" (admin)":"");
1138                         count++;
1139                 }
1140                 free(fname);
1141         }
1142
1143         pprintf(p, "%d commands are missing usage files\n", count);
1144
1145         return COM_OK;
1146 }