Check-in modifications made by HGM so far
[capablanca.git] / lasker-2.2.3 / src / network.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
23 extern int errno;
24
25 /* Index == fd, for sparse array, quick lookups! wasted memory :( */
26 static int findConnection(int fd)
27 {
28         if (fd == -1 || net_globals.con[fd]->status == NETSTAT_EMPTY)
29                 return -1;
30         else
31                 return fd;
32 }
33
34
35 static void set_sndbuf(int fd, int len)
36 {
37         net_globals.con[fd]->sndbufpos = len;
38         if (len < net_globals.con[fd]->sndbufsize) {
39                 net_globals.con[fd]->sndbuf[len] = 0;
40         }
41 }
42
43 static int net_addConnection(int fd, struct in_addr fromHost)
44 {
45         int noblock = 1;
46
47         /* possibly expand the connections array */
48         if (fd >= net_globals.no_file) {
49                 int i;
50                 net_globals.con = (struct connection_t **)realloc(net_globals.con,
51                                                                   (fd+1) * sizeof(struct connection_t *));
52                 for (i=net_globals.no_file;i<fd+1;i++) {
53                         net_globals.con[i] = (struct connection_t *)calloc(1, 
54                                                                            sizeof(struct connection_t));
55                         net_globals.con[i]->status = NETSTAT_EMPTY;
56                 }
57                 net_globals.no_file = fd+1;
58         }
59
60         if (findConnection(fd) >= 0) {
61                 d_printf( "CHESSD: FD already in connection table!\n");
62                 return -1;
63         }
64         if (ioctl(fd, FIONBIO, &noblock) == -1) {
65                 d_printf( "Error setting nonblocking mode errno=%d\n", errno);
66         }
67         net_globals.con[fd]->fd = fd;
68         if (fd != 0)
69                 net_globals.con[fd]->outFd = fd;
70         else
71                 net_globals.con[fd]->outFd = 1;
72         net_globals.con[fd]->fromHost = fromHost;
73         net_globals.con[fd]->status = NETSTAT_CONNECTED;
74         net_globals.con[fd]->timeseal = 0;
75         net_globals.con[fd]->timeseal_init = 1;
76         net_globals.con[fd]->time = 0;
77         net_globals.con[fd]->numPending = 0;
78         net_globals.con[fd]->inBuf[0] = 0;
79         net_globals.con[fd]->processed = 0;
80         net_globals.con[fd]->outPos = 0;
81         if (net_globals.con[fd]->sndbuf == NULL) {
82 #ifdef DEBUG
83                 d_printf( "CHESSD: nac(%d) allocating sndbuf.\n", fd);
84 #endif
85                 net_globals.con[fd]->sndbufpos = 0;
86                 net_globals.con[fd]->sndbufsize = MAX_STRING_LENGTH;
87                 net_globals.con[fd]->sndbuf = malloc(MAX_STRING_LENGTH);
88         } else {
89 #ifdef DEBUG
90                 d_printf( "CHESSD: nac(%d) reusing old sndbuf size %d pos %d.\n", fd, net_globals.con[fd].sndbufsize, net_globals.con[fd].sndbufpos);
91 #endif
92         }
93         net_globals.con[fd]->state = 0;
94         net_globals.numConnections++;
95         
96 #ifdef DEBUG
97         d_printf( "CHESSD: fd: %d connections: %d  descriptors: %d \n", fd, numConnections, getdtablesize());   /* sparky 3/13/95 */
98 #endif
99         
100         return 0;
101 }
102
103 static int remConnection(int fd)
104 {
105         int which, i;
106         if ((which = findConnection(fd)) < 0) {
107                 d_printf( "remConnection: Couldn't find fd to close.\n");
108                 return -1;
109         }
110         net_globals.numConnections--;
111         net_globals.con[fd]->status = NETSTAT_EMPTY;
112         if (net_globals.con[fd]->sndbuf == NULL) {
113                 d_printf( "CHESSD: remcon(%d) SNAFU, this shouldn't happen.\n", fd);
114         } else {
115                 if (net_globals.con[fd]->sndbufsize > MAX_STRING_LENGTH) {
116                         net_globals.con[fd]->sndbufsize = MAX_STRING_LENGTH;
117                         net_globals.con[fd]->sndbuf = realloc(net_globals.con[fd]->sndbuf, MAX_STRING_LENGTH);
118                 }
119                 if (net_globals.con[fd]->sndbufpos) {   /* didn't send everything, bummer */
120                         set_sndbuf(fd, 0);
121                 }
122         }
123
124         /* see if we can shrink the con array */
125         for (i=fd;i<net_globals.no_file;i++) {
126                 if (net_globals.con[i]->status != NETSTAT_EMPTY) {
127                         return 0;
128                 }
129         }
130
131         /* yep! shrink it */
132         for (i=fd;i<net_globals.no_file;i++) {
133                 FREE(net_globals.con[i]->sndbuf);
134                 FREE(net_globals.con[i]);
135         }
136         net_globals.no_file = fd;
137         net_globals.con = (struct connection_t **)realloc(net_globals.con,
138                                                           net_globals.no_file * sizeof(struct connection_t *));
139
140         return 0;
141 }
142
143 static void net_flushme(int which)
144 {
145   int sent;
146
147   sent = send(net_globals.con[which]->outFd, net_globals.con[which]->sndbuf, net_globals.con[which]->sndbufpos, 0);
148   if (sent == -1) {
149     if (errno != EPIPE)         /* EPIPE = they've disconnected */
150       d_printf( "CHESSD: net_flushme(%d) couldn't send, errno=%d.\n", which, errno);
151     set_sndbuf(which, 0);
152   } else {
153     net_globals.con[which]->sndbufpos -= sent;
154     if (net_globals.con[which]->sndbufpos)
155       memmove(net_globals.con[which]->sndbuf, net_globals.con[which]->sndbuf + sent, net_globals.con[which]->sndbufpos);
156   }
157   if (net_globals.con[which]->sndbufsize > MAX_STRING_LENGTH && net_globals.con[which]->sndbufpos < MAX_STRING_LENGTH) {
158     /* time to shrink the buffer */
159     net_globals.con[which]->sndbuf = realloc(net_globals.con[which]->sndbuf, MAX_STRING_LENGTH);
160     net_globals.con[which]->sndbufsize = MAX_STRING_LENGTH;
161   }
162   set_sndbuf(which, net_globals.con[which]->sndbufpos);
163 }
164
165 static void net_flush_all_connections(void)
166 {
167         int which;
168         fd_set writefds;
169         struct timeval to;
170         
171         FD_ZERO(&writefds);
172         for (which = 0; which < net_globals.no_file; which++) {
173                 if (net_globals.con[which]->status == NETSTAT_CONNECTED && 
174                     net_globals.con[which]->sndbufpos){
175                         FD_SET(net_globals.con[which]->outFd, &writefds);
176                 }
177         }
178
179         to.tv_usec = 0;
180         to.tv_sec = 0;
181         select(net_globals.no_file, NULL, &writefds, NULL, &to);
182         for (which = 0; which < net_globals.no_file; which++) {
183                 if (FD_ISSET(net_globals.con[which]->outFd, &writefds)) {
184                         net_flushme(which);
185                 }
186         }
187 }
188
189 static void net_flush_connection(int fd)
190 {
191   int which;
192   fd_set writefds;
193   struct timeval to;
194
195   if (((which = findConnection(fd)) >= 0) && (net_globals.con[which]->sndbufpos)) {
196     FD_ZERO(&writefds);
197     FD_SET(net_globals.con[which]->outFd, &writefds);
198     to.tv_usec = 0;
199     to.tv_sec = 0;
200     select(net_globals.no_file, NULL, &writefds, NULL, &to);
201     if (FD_ISSET(net_globals.con[which]->outFd, &writefds)) {
202       net_flushme(which);
203     }
204   }
205 }
206
207 static int sendme(int which, char *str, int len)
208 {
209   int i, count;
210   fd_set writefds;
211   struct timeval to;
212   count = len;
213
214   while ((i = ((net_globals.con[which]->sndbufsize - net_globals.con[which]->sndbufpos) < len) ? (net_globals.con[which]->sndbufsize - net_globals.con[which]->sndbufpos) : len) > 0) {
215     memmove(net_globals.con[which]->sndbuf + net_globals.con[which]->sndbufpos, str, i);
216     net_globals.con[which]->sndbufpos += i;
217     if (net_globals.con[which]->sndbufpos == net_globals.con[which]->sndbufsize) {
218
219       FD_ZERO(&writefds);
220       FD_SET(net_globals.con[which]->outFd, &writefds);
221       to.tv_usec = 0;
222       to.tv_sec = 0;
223       select(net_globals.no_file, NULL, &writefds, NULL, &to);
224       if (FD_ISSET(net_globals.con[which]->outFd, &writefds)) {
225         net_flushme(which);
226       } else {
227         /* time to grow the buffer */
228         net_globals.con[which]->sndbufsize += MAX_STRING_LENGTH;
229         net_globals.con[which]->sndbuf = realloc(net_globals.con[which]->sndbuf, net_globals.con[which]->sndbufsize);
230       }
231     }
232     str += i;
233     len -= i;
234   }
235   set_sndbuf(which, net_globals.con[which]->sndbufpos);
236   return count;
237 }
238
239 /*
240  * -1 for an error other than EWOULDBLOCK.
241  * Put <lf> after every <cr> and put \ at the end of overlength lines.
242  * Doesn't send anything unless the buffer fills, output waits until
243  * flushed
244 */
245 /* width here is terminal width = width var + 1 at presnt) */
246 int net_send_string(int fd, char *str, int format, int width)
247 {
248   int which, i, j;
249
250   if ((which = findConnection(fd)) < 0) {
251     return -1;
252   }
253   while (*str) {
254     for (i = 0; str[i] >= ' '; i++);
255     if (i) {
256       if (format && (i >= (j = width - net_globals.con[which]->outPos))) {      /* word wrap */
257         i = j-1;
258         while (i > 0 && str[i - 1] != ' ')
259           i--;
260 /*
261         while (i > 0 && str[i - 1] == ' ')
262           i--;
263 */
264         if (i == 0)
265           i = j - 1;
266         sendme(which, str, i);
267         sendme(which, "\n\r\\   ", 6);
268         net_globals.con[which]->outPos = 4;
269         while (str[i] == ' ')   /* eat the leading spaces after we wrap */
270           i++;
271       } else {
272         sendme(which, str, i);
273         net_globals.con[which]->outPos += i;
274       }
275       str += i;
276     } else {                    /* non-printable stuff handled here */
277       switch (*str) {
278       case '\t':
279         sendme(which, "        ", 8 - (net_globals.con[which]->outPos & 7));
280         net_globals.con[which]->outPos &= ~7;
281         if (net_globals.con[which]->outPos += 8 >= width)
282           net_globals.con[which]->outPos = 0;
283         break;
284       case '\n':
285         sendme(which, "\n\r", 2);
286         net_globals.con[which]->outPos = 0;
287         break;
288       case '\033':
289         net_globals.con[which]->outPos -= 3;
290       default:
291         sendme(which, str, 1);
292       }
293       str++;
294     }
295   }
296   return 0;
297 }
298
299 /* if we get a complete line (something terminated by \n), copy it to com
300    and return 1.
301    if we don't get a complete line, but there is no error, return 0.
302    if some error, return -1.
303  */
304 static int readline2(char *com, int who)
305 {
306   unsigned char *start, *s, *d;
307   int howmany, state, fd, pending;
308
309   const unsigned char will_tm[] = {IAC, WILL, TELOPT_TM, '\0'};
310   const unsigned char will_sga[] = {IAC, WILL, TELOPT_SGA, '\0'};
311   const unsigned char ayt[] = "[Responding to AYT: Yes, I'm here.]\n";
312
313   state = net_globals.con[who]->state;
314   if ((state == 2) || (state > 4)) {
315     d_printf( "CHESSD: state screwed for net_globals.con[%d], this is a bug.\n", who);
316     state = 0;
317   }
318   s = start = net_globals.con[who]->inBuf;
319   pending = net_globals.con[who]->numPending;
320   fd = net_globals.con[who]->fd;
321
322   howmany = recv(fd, start + pending, MAX_STRING_LENGTH - 1 - pending, 0);
323   if (howmany == 0)             /* error: they've disconnected */
324     return (-1);
325   else if (howmany == -1) {
326     if (errno != EWOULDBLOCK) { /* some other error */
327       return (-1);
328     } else if (net_globals.con[who]->processed) {       /* nothing new and nothing old */
329       return (0);
330     } else {                    /* nothing new, but some unprocessed old */
331       howmany = 0;
332     }
333   }
334   if (net_globals.con[who]->processed)
335     s += pending;
336   else
337     howmany += pending;
338   d = s;
339
340   for (; howmany-- > 0; s++) {
341     switch (state) {
342     case 0:                     /* Haven't skipped over any control chars or
343                                    telnet commands */
344       if (*s == IAC) {
345         d = s;
346         state = 1;
347       } else if (*s == '\n') {
348         *s = '\0';
349         strcpy(com, start);
350         if (howmany)
351           bcopy(s + 1, start, howmany);
352         net_globals.con[who]->state = 0;
353         net_globals.con[who]->numPending = howmany;
354         net_globals.con[who]->inBuf[howmany] = 0;
355         net_globals.con[who]->processed = 0;
356         net_globals.con[who]->outPos = 0;
357         return (1);
358 /*
359  Cannot strip ^? yet otherwise timeseal probs occur
360 */
361       } else if (*s == '\010') { /* ^H lets delete */
362         if (s > start)
363           d = s-1;
364         else
365           d = s;
366         state = 2;
367       } else if ((*s > (0xff - 0x20)) || (*s < 0x20)) {   
368         d = s;
369         state = 2;
370       }
371       break;
372     case 1:                     /* got telnet IAC */
373       if (*s == IP)
374         return (-1);            /* ^C = logout */
375       else if (*s == DO)
376         state = 4;
377       else if ((*s == WILL) || (*s == DONT) || (*s == WONT))
378         state = 3;              /* this is cheesy, but we aren't using em */
379       else if (*s == AYT) {
380         send(fd, (char *) ayt, strlen((char *) ayt), 0);
381         state = 2;
382       } else if (*s == EL) {    /* erase line */
383         d = start;
384         state = 2;
385       } else                    /* dunno what it is, so ignore it */
386         state = 2;
387       break;
388     case 2:                     /* we've skipped over something, need to
389                                    shuffle processed chars down */
390       if (*s == IAC)
391         state = 1;
392       else if (*s == '\n') {
393         *d = '\0';
394         strcpy(com, start);
395         if (howmany)
396           memmove(start, s + 1, howmany);
397         net_globals.con[who]->state = 0;
398         net_globals.con[who]->numPending = howmany;
399         net_globals.con[who]->inBuf[howmany] = 0;
400         net_globals.con[who]->processed = 0;
401         net_globals.con[who]->outPos = 0;
402         return (1);
403 /*
404  Cannot strip ^? yet otherwise timeseal probs occur
405 */
406       } else if (*s == '\010') { /* ^H lets delete */
407         if (d > start)
408           d = d-1;
409       } else if (*s >= 0x20)
410         *(d++) = *s;
411       break;
412     case 3:                     /* some telnet junk we're ignoring */
413       state = 2;
414       break;
415     case 4:                     /* got IAC DO */
416       if (*s == TELOPT_TM)
417         send(fd, (char *) will_tm, strlen((char *) will_tm), 0);
418       else if (*s == TELOPT_SGA)
419         send(fd, (char *) will_sga, strlen((char *) will_sga), 0);
420       state = 2;
421       break;
422     }
423   }
424   if (state == 0)
425     d = s;
426   else if (state == 2)
427     state = 0;
428   net_globals.con[who]->state = state;
429   net_globals.con[who]->numPending = d - start;
430   net_globals.con[who]->inBuf[d-start] = 0;
431   net_globals.con[who]->processed = 1;
432   if (net_globals.con[who]->numPending == MAX_STRING_LENGTH - 1) {      /* buffer full */
433     *d = '\0';
434     strcpy(com, start);
435     net_globals.con[who]->state = 0;
436     net_globals.con[who]->numPending = 0;
437     net_globals.con[who]->inBuf[0] = 0;
438     net_globals.con[who]->processed = 0;
439     return (1);
440   }
441   return (0);
442 }
443
444 int net_init(int port)
445 {
446   int opt;
447   struct sockaddr_in serv_addr;
448   struct linger lingeropt;
449   
450   net_globals.no_file = 0;
451   net_globals.con = NULL;
452
453   /* Open a TCP socket (an Internet stream socket). */
454   if ((net_globals.sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
455     d_printf( "CHESSD: can't open stream socket\n");
456     return -1;
457   }
458   /* Bind our local address so that the client can send to us */
459   memset((char *) &serv_addr, 0, sizeof(serv_addr));
460   serv_addr.sin_family = AF_INET;
461   serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
462   serv_addr.sin_port = htons(port);
463
464   /** added in an attempt to allow rebinding to the port **/
465
466   opt = 1;
467   setsockopt(net_globals.sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt));
468   opt = 1;
469   setsockopt(net_globals.sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt));
470   lingeropt.l_onoff = 0;
471   lingeropt.l_linger = 0;
472   setsockopt(net_globals.sockfd, SOL_SOCKET, SO_LINGER, (char *) &lingeropt, sizeof(lingeropt));
473   
474 /*
475 #ifdef DEBUG
476   opt = 1;
477   setsockopt(sockfd, SOL_SOCKET, SO_DEBUG, (char *)&opt, sizeof(opt));
478 #endif
479 */
480
481   if (bind(net_globals.sockfd, (struct sockaddr *) & serv_addr, sizeof(serv_addr)) < 0) {
482     d_printf( "CHESSD: can't bind local address.  errno=%d\n", errno);
483     return -1;
484   }
485   opt = 1;
486   ioctl(net_globals.sockfd, FIONBIO, &opt);
487   fcntl(net_globals.sockfd, F_SETFD, 1);
488   listen(net_globals.sockfd, 5);
489   return 0;
490 }
491
492 void net_close(void)
493 {
494         int i;
495         for (i = 0; i < net_globals.no_file; i++) {
496                 if (net_globals.con[i]->status != NETSTAT_EMPTY)
497                         net_close_connection(net_globals.con[i]->fd);
498         }
499 }
500
501 void net_close_connection(int fd)
502 {
503   if (net_globals.con[fd]->status == NETSTAT_CONNECTED)
504     net_flush_connection(fd);
505   else
506     d_printf( "Trying to close an unconnected socket?!?!\n");
507
508   if (!remConnection(fd)) {
509     if (fd > 2)
510       if (close(fd) < 0) {
511         d_printf( "Couldn't close socket %d - errno %d\n", fd, errno);
512       }
513   } else {
514     d_printf( "Failed to remove connection (Socket %d)\n", fd);
515   }
516 }
517
518 void turn_echo_on(int fd)
519 {
520         const unsigned char wont_echo[] = {IAC, WONT, TELOPT_ECHO, '\0'};
521
522         send(fd, (char *) wont_echo, strlen((char *) wont_echo), 0);
523 }
524
525 void turn_echo_off(int fd)
526 {
527   static unsigned char will_echo[] = {IAC, WILL, TELOPT_ECHO, '\0'};
528
529   send(fd, (char *) will_echo, strlen((char *) will_echo), 0);
530 }
531
532 static struct in_addr net_connected_host(int fd)
533 {
534         int which;
535         
536         if ((which = findConnection(fd)) < 0) {
537                 static struct in_addr ip_zero;
538                 d_printf( "CHESSD: FD not in connection table!\n");
539                 return ip_zero;
540         }
541         return net_globals.con[which]->fromHost;
542 }
543
544 void select_loop(void )
545 {
546         char com[MAX_STRING_LENGTH];
547         struct sockaddr_in cli_addr;
548         int cli_len = (int) sizeof(struct sockaddr_in);
549         int fd, loop, nfound, lineComplete;
550         fd_set readfds;
551         struct timeval to;
552         int current_socket;
553         int timeout = 2;
554
555 #if 0
556         m_check_all();
557 #endif
558
559         /* we only want to get signals here. This tries to
560            ensure a clean shutdown on 'kill' */
561         unblock_signal(SIGTERM);
562         block_signal(SIGTERM);
563         
564         while ((fd = accept(net_globals.sockfd, (struct sockaddr *) & cli_addr, &cli_len)) != -1) {
565                 if (net_addConnection(fd, cli_addr.sin_addr)) {
566                         d_printf( "FICS is full.  fd = %d.\n", fd);
567                         psend_raw_file(fd, MESS_DIR, MESS_FULL);
568                         close(fd);
569                 } else {
570                         if (fd >= net_globals.no_file)
571                                 d_printf("FICS (ngc2): Out of range fd!\n");
572                         else {
573                                 fcntl(fd, F_SETFD, 1);
574                                 process_new_connection(fd, net_connected_host(fd));
575                         }
576                 }
577         }
578   
579         if (errno != EWOULDBLOCK)
580                 d_printf( "CHESSD: Problem with accept().  errno=%d\n", errno);
581
582         net_flush_all_connections();
583         
584         if (net_globals.numConnections == 0) {
585                 sleep(1); /* prevent the server spinning */
586         }
587
588         FD_ZERO(&readfds);
589         for (loop = 0; loop < net_globals.no_file; loop++)
590                 if (net_globals.con[loop]->status != NETSTAT_EMPTY)
591                         FD_SET(net_globals.con[loop]->fd, &readfds);
592         
593         to.tv_usec = 0;
594         to.tv_sec = timeout;
595         nfound = select(net_globals.no_file, &readfds, NULL, NULL, &to);
596         for (loop = 0; loop < net_globals.no_file; loop++) {
597                 if (net_globals.con[loop]->status != NETSTAT_EMPTY) {
598                         fd = net_globals.con[loop]->fd;
599                 more_commands:
600                         lineComplete = readline2(com, fd);
601                         if (lineComplete == 0)  /* partial line: do nothing with it */
602                                 continue;
603                         if (lineComplete > 0) { /* complete line: process it */
604                                 if (!timeseal_parse(com, net_globals.con[loop])) continue;
605                                 if (process_input(fd, com) != COM_LOGOUT) {
606                                         net_flush_connection(fd);
607                                         goto more_commands;
608                                 }
609                         }
610                         /* Disconnect anyone who gets here */
611                         process_disconnection(fd);
612                         net_close_connection(fd);
613                 }
614         }
615
616         if (process_heartbeat(&current_socket) == COM_LOGOUT) {
617                 process_disconnection(current_socket);
618                 net_close_connection(current_socket);
619         }
620 }
621