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