7d440c52b5680ab15219680fc1292f7b4e410531
[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 static int process_prompt(int p, char *command)
699 {
700   struct player *pp = &player_globals.parray[p];
701   int retval;
702   char *cmd = "";
703
704   command = eattailwhite(eatwhite(command));
705   if (!*command) {
706           send_prompt(p);
707           return COM_OK;
708   }
709   retval = process_command(p, command, &cmd);
710   switch (retval) {
711   case COM_OK:
712     retval = COM_OK;
713     send_prompt(p);
714     break;
715   case COM_OK_NOPROMPT:
716     retval = COM_OK;
717     break;
718   case COM_ISMOVE:
719     retval = COM_OK;
720
721     if (pp->game >= 0 && game_globals.garray[pp->game].status == GAME_ACTIVE
722         && pp->side == game_globals.garray[pp->game].game_state.onMove
723         && game_globals.garray[pp->game].flag_pending != FLAG_NONE) {
724             ExecuteFlagCmd(p, net_globals.con[pp->socket]);
725     }
726
727     process_move(p, command);
728     send_prompt(p);
729
730     break;
731   case COM_ISMOVE_INSETUP:
732     pprintf(p, "You are still setting up the position.\n");
733     pprintf_prompt(p, "Type: 'setup done' when you are finished editing.\n");
734     retval = COM_OK;
735     break;
736   case COM_RIGHTS:
737     pprintf_prompt(p, "%s: Insufficient rights.\n", cmd);
738     retval = COM_OK;
739     break;
740   case COM_AMBIGUOUS:
741 /*    pprintf(p, "%s: Ambiguous command.\n", cmd); */
742     {
743       int len = strlen(cmd);
744       int i = 0;
745       pprintf(p, "Ambiguous command. Matches:");
746       while (command_list[i].comm_name) {
747         if (strncmp(command_list[i].comm_name, cmd, len) == 0 &&
748             check_admin(p, command_list[i].adminLevel)) {
749                 pprintf(p, " %s", command_list[i].comm_name);
750         }
751         i++;
752       }
753     }
754     pprintf_prompt(p, "\n");
755     retval = COM_OK;
756     break;
757   case COM_BADPARAMETERS:
758     printusage(p, command_list[lastCommandFound].comm_name);
759     send_prompt(p);
760     retval = COM_OK;
761     break;
762   case COM_FAILED:
763   case COM_BADCOMMAND:
764     pprintf_prompt(p, "%s: Command not found.\n", cmd);
765     d_printf("Command not found [%s]\n", cmd);
766     retval = COM_OK;
767     break;
768   case COM_LOGOUT:
769     retval = COM_LOGOUT;
770     break;
771   }
772   return retval;
773 }
774
775 /* Return 1 to disconnect */
776 int process_input(int fd, char *com_string)
777 {
778   int p = player_find(fd);
779   int retval = 0;
780   struct player *pp;
781
782   if (p < 0) {
783     d_printf( "CHESSD: Input from a player not in array!\n");
784     return -1;
785   }
786
787   pp = &player_globals.parray[p];
788
789   command_globals.commanding_player = p;
790   pp->last_command_time = time(0);
791
792   switch (pp->status) {
793   case PLAYER_EMPTY:
794     d_printf( "CHESSD: Command from an empty player!\n");
795     break;
796   case PLAYER_NEW:
797     d_printf( "CHESSD: Command from a new player!\n");
798     break;
799   case PLAYER_INQUEUE:
800     /* Ignore input from player in queue */
801     break;
802   case PLAYER_LOGIN:
803     retval = process_login(p, com_string);
804     if (retval == COM_LOGOUT && com_string != NULL)
805       d_printf("%s tried to log in and failed.\n", com_string);
806     break;
807   case PLAYER_PASSWORD:
808     retval = process_password(p, com_string);
809     break;
810   case PLAYER_PROMPT:
811           FREE(pp->busy);
812           retval = process_prompt(p, com_string);
813           break;
814   }
815
816   command_globals.commanding_player = -1;
817   return retval;
818 }
819
820 int process_new_connection(int fd, struct in_addr fromHost)
821 {
822         struct player *pp;
823         int p = player_new();
824
825         pp = &player_globals.parray[p];
826
827         pp->status = PLAYER_LOGIN;
828         if (fd >= net_globals.no_file)
829                 d_printf("FICS (process_new_connection): Out of range fd!\n");
830         
831         pp->socket = fd;
832         pp->thisHost = fromHost;
833         pp->logon_time = time(0);
834         psend_raw_file(p, MESS_DIR, MESS_WELCOME);
835         pprintf(p, "Head admin : %s", config_get_tmp("HEAD_ADMIN"));
836         pprintf(p, "    Complaints to : %s\n", config_get_tmp("HEAD_ADMIN_EMAIL"));
837         pprintf(p, "Server location: %s", config_get_tmp("SERVER_LOCATION"));
838         pprintf(p, "    Server version : %s\n", VERS_NUM);
839         pprintf(p, "Server name : %s\n", config_get_tmp("SERVER_HOSTNAME"));
840         psend_raw_file(p, MESS_DIR, MESS_LOGIN);
841         pprintf(p, "login: ");
842         
843         return 0;
844 }
845
846 int process_disconnection(int fd)
847 {
848   int p = player_find(fd);
849   int p1;
850   char command[1024];
851   struct player *pp;
852
853   if (p < 0) {
854     d_printf( "CHESSD: Disconnect from a player not in array!\n");
855     return -1;
856   }
857
858   pp = &player_globals.parray[p];
859
860   if (CheckPFlag(p, PFLAG_REG) && CheckPFlag(p, PFLAG_OPEN) &&
861       pp->game < 0)
862     announce_notavail(p);
863   if ((pp->game >=0) && ((game_globals.garray[pp->game].status == GAME_EXAMINE) || (game_globals.garray[pp->game].status == GAME_SETUP))) {
864     pcommand(p, "unexamine");
865   }
866   if ((pp->game >=0) && (in_list(p, L_ABUSER, pp->name)
867         || (game_globals.garray[pp->game].link >= 0)))
868
869      pcommand(p, "resign");
870
871   if (pp->ftell != -1)
872     pcommand (p,"tell 0 I am logging out now - conversation forwarding stopped.");
873
874   withdraw_seeks(p);
875
876   if (pp->status == PLAYER_PROMPT) {
877     for (p1 = 0; p1 < player_globals.p_num; p1++) {
878       if (p1 == p)
879         continue;
880       if (player_globals.parray[p1].status != PLAYER_PROMPT)
881         continue;
882
883       if (player_globals.parray[p1].ftell == p) {
884         player_globals.parray[p1].ftell = -1;
885         sprintf (command,"tell 0 *%s* has logged out - conversation forwarding stopped.",pp->name);
886         pcommand (p1,command);
887         pprintf_prompt (p1,"%s, whose tells you were forwarding has logged out.\n",
888                  pp->name);
889       }
890
891       if (!CheckPFlag(p1, PFLAG_PIN))
892         continue;
893       pprintf_prompt(p1, "\n[%s has disconnected.]\n", pp->name);
894     }
895     player_notify(p, "departed", "departure");
896     player_notify_departure(p);
897     player_write_logout(p);
898     if (CheckPFlag(p, PFLAG_REG)) {
899       pp->totalTime += time(0) - pp->logon_time;
900       player_save(p);
901     } else {                    /* delete unreg history file */
902       char fname[MAX_FILENAME_SIZE];
903       sprintf(fname, "%s/player_data/%c/%s.games", STATS_DIR, pp->login[0], pp->login);
904       unlink(fname);
905     }
906   }
907   player_remove(p);
908   return 0;
909 }
910
911 /* Called every few seconds */
912 int process_heartbeat(int *fd)
913 {
914         struct tm *nowtm;
915         int p;
916         time_t now = time(0); 
917         unsigned idle_timeout = config_get_int("IDLE_TIMEOUT", DEFAULT_IDLE_TIMEOUT);
918         unsigned login_timeout = config_get_int("LOGIN_TIMEOUT", DEFAULT_LOGIN_TIMEOUT);
919
920         /* Check for timed out connections */
921         for (p = 0; p < player_globals.p_num; p++) {
922                 struct player *pp = &player_globals.parray[p];
923
924                 if ((pp->status == PLAYER_LOGIN ||
925                      pp->status == PLAYER_PASSWORD) &&
926                     player_idle(p) > login_timeout) {
927                         pprintf(p, "\n**** LOGIN TIMEOUT ****\n");
928                         *fd = pp->socket;
929                         return COM_LOGOUT;
930                 }
931                 if (pp->status == PLAYER_PROMPT &&
932                     player_idle(p) > idle_timeout &&
933                     !check_admin(p, ADMIN_ADMIN) &&
934                     !in_list(p, L_TD, pp->name)) {
935                         pprintf(p, "\n**** Auto-logout - you were idle more than %u minutes. ****\n", 
936                                 idle_timeout/60);
937                         *fd = pp->socket;
938                         return COM_LOGOUT;
939                 }
940         }
941         nowtm=localtime((time_t *)&now);
942         if (nowtm->tm_min==0) {
943                 gics_globals.userstat.users[nowtm->tm_hour*2]=player_count(1);
944                 save_userstat();
945         }
946         if (nowtm->tm_min==30) {
947                 gics_globals.userstat.users[nowtm->tm_hour*2+1]=player_count(1);
948                 save_userstat();
949         }
950         if (command_globals.player_high > gics_globals.userstat.usermax) {
951                 gics_globals.userstat.usermax=command_globals.player_high;
952                 gics_globals.userstat.usermaxtime=now;
953                 save_userstat();
954         }
955         if (command_globals.game_high > gics_globals.userstat.gamemax) {
956                 gics_globals.userstat.gamemax=command_globals.game_high;
957                 gics_globals.userstat.gamemaxtime=now;
958                 save_userstat();
959         }
960         
961         ShutHeartBeat();
962         return COM_OK;
963 }
964
965 /* helper function for sorting command list */
966 static int command_compare(struct command_type *c1, struct command_type *c2)
967 {
968         return strcasecmp(c1->comm_name, c2->comm_name);
969 }
970
971 void commands_init(void)
972 {
973         FILE *fp, *afp;
974         int i = 0;
975         int count=0, acount=0;
976
977         /* sort the command list */
978         qsort(command_list, 
979               (sizeof(command_list)/sizeof(command_list[0])) - 1,
980               sizeof(command_list[0]),
981               (COMPAR_FN_T)command_compare);
982
983         command_globals.commanding_player = -1;
984         
985         fp = fopen_s(HELP_DIR "/commands", "w");
986         if (!fp) {
987                 d_printf( "CHESSD: Could not write commands help file.\n");
988                 return;
989         }
990         afp = fopen_s(ADHELP_DIR "/commands", "w");
991         if (!afp) {
992                 d_printf( "CHESSD: Could not write admin commands help file.\n");
993                 fclose(fp);
994                 return;
995         }
996
997         while (command_list[i].comm_name) {
998                 if (command_list[i].adminLevel >= ADMIN_ADMIN) {
999                         fprintf(afp, "%-19s", command_list[i].comm_name);
1000                         acount++;
1001                         if (acount % 4 == 0) {
1002                                 fprintf(afp,"\n");
1003                         }
1004                 } else {
1005                         fprintf(fp, "%-19s", command_list[i].comm_name);
1006                         count++;
1007                         if (count % 4 == 0) {
1008                                 fprintf(fp,"\n");
1009                         }
1010                 }
1011                 i++;
1012         }
1013
1014         fprintf(afp,"\n");
1015         fprintf(fp,"\n");
1016
1017         fclose(fp);
1018         fclose(afp);
1019
1020         d_printf("CHESSD: Loaded %d commands (admin=%d normal=%d)\n", i, acount, count);
1021 }
1022
1023 /* Need to save rated games */
1024 void TerminateCleanup(void)
1025 {
1026         int p1;
1027         int g;
1028   
1029         save_userstat();
1030
1031         for (g = 0; g < game_globals.g_num; g++) {
1032                 if (game_globals.garray[g].status != GAME_ACTIVE)
1033                         continue;
1034                 if (game_globals.garray[g].rated) {
1035                         game_ended(g, WHITE, END_ADJOURN);
1036                 }
1037         }
1038         for (p1 = 0; p1 < player_globals.p_num; p1++) {
1039                 if (player_globals.parray[p1].status == PLAYER_EMPTY)
1040                         continue;
1041                 pprintf(p1, "\n    **** Server shutting down immediately. ****\n\n");
1042                 if (player_globals.parray[p1].status != PLAYER_PROMPT) {
1043                         close(player_globals.parray[p1].socket);
1044                 } else {
1045                         pprintf(p1, "Logging you out.\n");
1046                         psend_raw_file(p1, MESS_DIR, MESS_LOGOUT);
1047                         player_write_logout(p1);
1048                         if (CheckPFlag(p1, PFLAG_REG))
1049                                 player_globals.parray[p1].totalTime += time(0) - player_globals.parray[p1].logon_time;
1050                         player_save(p1);
1051                 }
1052         }
1053         destruct_pending();
1054 }
1055
1056 static char *guest_name(void)
1057 {
1058        static char name[20];
1059        int found;
1060
1061 #define RANDLET ((char)('A' + (random() % 26)))
1062        srandom(time(0));
1063
1064        found = 0;
1065        while(!found) {
1066                snprintf(name,sizeof(name), "Guest%c%c%c%c", RANDLET, RANDLET, RANDLET, RANDLET);
1067                found = check_user(name);
1068        }
1069 #undef RANDLET
1070        return name;
1071 }
1072
1073 static int check_user(char *user)
1074 {
1075        int i;
1076        
1077        for (i = 0; i < player_globals.p_num; i++) {
1078                if (player_globals.parray[i].name != NULL) {
1079                        if (!strcasecmp(user, player_globals.parray[i].name))
1080                                return 0;
1081                }
1082        }
1083        return 1;
1084 }
1085
1086
1087 /* return the global alias list */
1088 const struct alias_type *alias_list_global(void)
1089 {
1090         return g_alias_list;
1091 }
1092
1093 /* return a personal alias list */
1094 const struct alias_type *alias_list_personal(int p, int *n)
1095 {
1096         struct player *pp = &player_globals.parray[p];
1097
1098         *n = pp->numAlias;
1099         return pp->alias_list;
1100 }
1101
1102 /*
1103   report on any missing help pages
1104 */
1105 int com_acheckhelp(int p, param_list param)
1106 {
1107         int i;
1108         int count;
1109
1110         for (count=i=0; command_list[i].comm_name; i++) {
1111                 char *fname;
1112                 asprintf(&fname, "%s/%s", 
1113                          command_list[i].adminLevel?ADHELP_DIR:HELP_DIR, 
1114                          command_list[i].comm_name);
1115                 if (!file_exists(fname)) {
1116                         pprintf(p, "Help for command '%s' is missing%s\n",
1117                                 command_list[i].comm_name,
1118                                 command_list[i].adminLevel?" (admin)":"");
1119                         count++;
1120                 }
1121                 free(fname);
1122         }
1123
1124         pprintf(p, "%d commands are missing help files\n", count);
1125
1126         for (count=i=0; command_list[i].comm_name; i++) {
1127                 char *fname;
1128                 asprintf(&fname, "%s/%s", USAGE_DIR, command_list[i].comm_name);
1129                 if (!file_exists(fname)) {
1130                         pprintf(p, "Usage for command '%s' is missing%s\n",
1131                                 command_list[i].comm_name,
1132                                 command_list[i].adminLevel?" (admin)":"");
1133                         count++;
1134                 }
1135                 free(fname);
1136         }
1137
1138         pprintf(p, "%d commands are missing usage files\n", count);
1139
1140         return COM_OK;
1141 }