2 Copyright (c) 1993 Richard V. Nash.
3 Copyright (c) 2000 Dan Papasian
4 Copyright (C) Andrew Tridgell 2002
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.
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.
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.
25 /* Index == fd, for sparse array, quick lookups! wasted memory :( */
26 static int findConnection(int fd)
28 if (fd == -1 || net_globals.con[fd]->status == NETSTAT_EMPTY)
35 static void set_sndbuf(int fd, int len)
37 net_globals.con[fd]->sndbufpos = len;
38 if (len < net_globals.con[fd]->sndbufsize) {
39 net_globals.con[fd]->sndbuf[len] = 0;
43 static int net_addConnection(int fd, struct in_addr fromHost)
47 /* possibly expand the connections array */
48 if (fd >= net_globals.no_file) {
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;
57 net_globals.no_file = fd+1;
60 if (findConnection(fd) >= 0) {
61 d_printf( "CHESSD: FD already in connection table!\n");
64 if (ioctl(fd, FIONBIO, &noblock) == -1) {
65 d_printf( "Error setting nonblocking mode errno=%d\n", errno);
67 net_globals.con[fd]->fd = fd;
69 net_globals.con[fd]->outFd = fd;
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) {
83 d_printf( "CHESSD: nac(%d) allocating sndbuf.\n", fd);
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);
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);
93 net_globals.con[fd]->state = 0;
94 net_globals.numConnections++;
97 d_printf( "CHESSD: fd: %d connections: %d descriptors: %d \n", fd, numConnections, getdtablesize()); /* sparky 3/13/95 */
103 static int remConnection(int fd)
106 if ((which = findConnection(fd)) < 0) {
107 d_printf( "remConnection: Couldn't find fd to close.\n");
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);
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);
119 if (net_globals.con[fd]->sndbufpos) { /* didn't send everything, bummer */
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) {
132 for (i=fd;i<net_globals.no_file;i++) {
133 FREE(net_globals.con[i]->sndbuf);
134 FREE(net_globals.con[i]);
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 *));
143 static void net_flushme(int which)
147 sent = send(net_globals.con[which]->outFd, net_globals.con[which]->sndbuf, net_globals.con[which]->sndbufpos, 0);
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);
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);
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;
162 set_sndbuf(which, net_globals.con[which]->sndbufpos);
165 static void net_flush_all_connections(void)
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);
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)) {
189 static void net_flush_connection(int fd)
195 if (((which = findConnection(fd)) >= 0) && (net_globals.con[which]->sndbufpos)) {
197 FD_SET(net_globals.con[which]->outFd, &writefds);
200 select(net_globals.no_file, NULL, &writefds, NULL, &to);
201 if (FD_ISSET(net_globals.con[which]->outFd, &writefds)) {
207 static int sendme(int which, char *str, int len)
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) {
220 FD_SET(net_globals.con[which]->outFd, &writefds);
223 select(net_globals.no_file, NULL, &writefds, NULL, &to);
224 if (FD_ISSET(net_globals.con[which]->outFd, &writefds)) {
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);
235 set_sndbuf(which, net_globals.con[which]->sndbufpos);
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
245 /* width here is terminal width = width var + 1 at presnt) */
246 int net_send_string(int fd, char *str, int format, int width)
250 if ((which = findConnection(fd)) < 0) {
254 for (i = 0; str[i] >= ' '; i++);
256 if (format && (i >= (j = width - net_globals.con[which]->outPos))) { /* word wrap */
258 while (i > 0 && str[i - 1] != ' ')
261 while (i > 0 && str[i - 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 */
272 sendme(which, str, i);
273 net_globals.con[which]->outPos += i;
276 } else { /* non-printable stuff handled here */
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;
285 sendme(which, "\n\r", 2);
286 net_globals.con[which]->outPos = 0;
289 net_globals.con[which]->outPos -= 3;
291 sendme(which, str, 1);
299 /* if we get a complete line (something terminated by \n), copy it to com
301 if we don't get a complete line, but there is no error, return 0.
302 if some error, return -1.
304 static int readline2(char *com, int who)
306 unsigned char *start, *s, *d;
307 int howmany, state, fd, pending;
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";
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);
318 s = start = net_globals.con[who]->inBuf;
319 pending = net_globals.con[who]->numPending;
320 fd = net_globals.con[who]->fd;
322 howmany = recv(fd, start + pending, MAX_STRING_LENGTH - 1 - pending, 0);
323 if (howmany == 0) /* error: they've disconnected */
325 else if (howmany == -1) {
326 if (errno != EWOULDBLOCK) { /* some other error */
328 } else if (net_globals.con[who]->processed) { /* nothing new and nothing old */
330 } else { /* nothing new, but some unprocessed old */
334 if (net_globals.con[who]->processed)
340 for (; howmany-- > 0; s++) {
342 case 0: /* Haven't skipped over any control chars or
347 } else if (*s == '\n') {
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;
359 Cannot strip ^? yet otherwise timeseal probs occur
361 } else if (*s == '\010') { /* ^H lets delete */
367 } else if ((*s > (0xff - 0x20)) || (*s < 0x20)) {
372 case 1: /* got telnet IAC */
374 return (-1); /* ^C = logout */
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);
382 } else if (*s == EL) { /* erase line */
385 } else /* dunno what it is, so ignore it */
388 case 2: /* we've skipped over something, need to
389 shuffle processed chars down */
392 else if (*s == '\n') {
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;
404 Cannot strip ^? yet otherwise timeseal probs occur
406 } else if (*s == '\010') { /* ^H lets delete */
409 } else if (*s >= 0x20)
412 case 3: /* some telnet junk we're ignoring */
415 case 4: /* got IAC DO */
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);
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 */
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;
444 int net_init(int port)
447 struct sockaddr_in serv_addr;
448 struct linger lingeropt;
450 net_globals.no_file = 0;
451 net_globals.con = NULL;
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");
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);
464 /** added in an attempt to allow rebinding to the port **/
467 setsockopt(net_globals.sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt));
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));
477 setsockopt(sockfd, SOL_SOCKET, SO_DEBUG, (char *)&opt, sizeof(opt));
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);
486 ioctl(net_globals.sockfd, FIONBIO, &opt);
487 fcntl(net_globals.sockfd, F_SETFD, 1);
488 listen(net_globals.sockfd, 5);
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);
501 void net_close_connection(int fd)
503 if (net_globals.con[fd]->status == NETSTAT_CONNECTED)
504 net_flush_connection(fd);
506 d_printf( "Trying to close an unconnected socket?!?!\n");
508 if (!remConnection(fd)) {
511 d_printf( "Couldn't close socket %d - errno %d\n", fd, errno);
514 d_printf( "Failed to remove connection (Socket %d)\n", fd);
518 void turn_echo_on(int fd)
520 const unsigned char wont_echo[] = {IAC, WONT, TELOPT_ECHO, '\0'};
522 send(fd, (char *) wont_echo, strlen((char *) wont_echo), 0);
525 void turn_echo_off(int fd)
527 static unsigned char will_echo[] = {IAC, WILL, TELOPT_ECHO, '\0'};
529 send(fd, (char *) will_echo, strlen((char *) will_echo), 0);
532 static struct in_addr net_connected_host(int fd)
536 if ((which = findConnection(fd)) < 0) {
537 static struct in_addr ip_zero;
538 d_printf( "CHESSD: FD not in connection table!\n");
541 return net_globals.con[which]->fromHost;
544 void select_loop(void )
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;
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);
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);
570 if (fd >= net_globals.no_file)
571 d_printf("FICS (ngc2): Out of range fd!\n");
573 fcntl(fd, F_SETFD, 1);
574 process_new_connection(fd, net_connected_host(fd));
579 if (errno != EWOULDBLOCK)
580 d_printf( "CHESSD: Problem with accept(). errno=%d\n", errno);
582 net_flush_all_connections();
584 if (net_globals.numConnections == 0) {
585 sleep(1); /* prevent the server spinning */
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);
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;
600 lineComplete = readline2(com, fd);
601 if (lineComplete == 0) /* partial line: do nothing with it */
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);
610 /* Disconnect anyone who gets here */
611 process_disconnection(fd);
612 net_close_connection(fd);
616 if (process_heartbeat(¤t_socket) == COM_LOGOUT) {
617 process_disconnection(current_socket);
618 net_close_connection(current_socket);