/* * WarGames - a WOPR emulator written in C * Copyright (C) 2014 Franklin Wei * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Contact the author at contact@fwei.tk */ #include "joshua.h" #include "telnet.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_PORT 23 #define LOG_LOCATION "/var/log/wopr" int server_socket; uint16_t port; int pipes[FD_SETSIZE][2]; struct connection_data_t connection_data[FD_SETSIZE]; int debugf(const char* fmt, ...) { va_list l; va_start(l, fmt); int ret=vprintf(fmt, l); va_end(l); return ret; } int make_server_socket(uint16_t port) { int sock; struct sockaddr_in name; sock=socket(AF_INET, SOCK_STREAM, 0); if(sock<0) { debugf("FATAL: Error opening socket.\n"); return -1; } name.sin_family=AF_INET; name.sin_port=htons(port); name.sin_addr.s_addr=htonl(INADDR_ANY); int ret=bind(sock, (struct sockaddr*) &name, sizeof(name)); if(ret<0) { debugf("FATAL: Error binding to port %d\n", port); return -1; } return sock; } char pending_buffer[1024]; void handle_command(unsigned char* buf, int buflen, int connection) { unsigned char cmd, opt; if(buflen<2) { debugf("Invalid command.\n"); return; } cmd=buf[1]; /* handle two-byte commands */ switch(cmd) { case AYT: { unsigned char iac_nop[]={IAC, NOP}; write(connection, iac_nop, sizeof(iac_nop)); return; } } if(buflen<3) { debugf("Invalid command.\n"); return; } opt=buf[2]; switch(cmd) { case SB: { switch(opt) { case NAWS: { /* format of NAWS data: IAC SB NAWS W W H H IAC SE */ uint16_t height, width; uint8_t height_hi, height_lo, width_hi, width_lo; if(buflen<9) { debugf("IAC SB NAWS command too short!\n"); return; } width_hi=buf[3]; width_lo=buf[4]; height_hi=buf[5]; height_lo=buf[6]; height=(height_hi<<8)|height_lo; width=(width_hi<<8)|width_lo; connection_data[connection].know_termsize=(height==0 || width==0)?0:1; connection_data[connection].term_height=height; connection_data[connection].term_width=width; return; } } break; } } /* unimplemented command, just deny it */ unsigned char deny_cmd[3]={IAC, WONT, opt}; if(opt==LINEMODE) { deny_cmd[1]=WILL; } write(connection, deny_cmd, sizeof(deny_cmd)); fsync(connection); return; } int process_data(int fd) { unsigned char buf[1024]; memset(buf, 0, sizeof(buf)); int ret=read(fd, buf, sizeof(buf)); debugf("Byte dump of data: "); for(int i=0;buf[i];++i) { debugf("%d ", buf[i]); } debugf("\n"); char ctrl_c[]={0xff, 0xf4, 0xff, 0xfd, 0x06, 0x00}; if(strcmp(ctrl_c, buf)==0) { debugf("Got CTRL-C from client %d.\n", fd); return -1; } if(ret<0) /* error */ { debugf("Error in read()\n"); return -1; } if(ret==0) { debugf("EOF from client %d.\n", fd); return -1; } else { debugf("Client %d sends: %s\n", fd, buf); int buflen=strlen(buf); if(buflen>0) /* no need to write nothing to the input stream :D */ { if(buf[0]==0xff) { handle_command(buf, buflen, fd); } else if(strlen(buf)>0) { write(pipes[fd][1],buf,strlen(buf)); } } return 0; } return 0; } void serv_cleanup() { debugf("\nPreparing to exit...\n"); fflush(stdout); shutdown(server_socket, SHUT_RDWR); } void setup_new_connection(int fd) { unsigned char will_naws[]={IAC, WILL, NAWS}; write(fd, will_naws, sizeof(will_naws)); unsigned char dont_echo[]={IAC, WILL, ECHO}; write(fd, dont_echo, sizeof(dont_echo)); dont_echo[1]=DONT; write(fd, dont_echo, sizeof(dont_echo)); unsigned char dont_sga[]={IAC, WONT, SGA}; write(fd, dont_sga, sizeof(dont_sga)); unsigned char will_linemode[]={IAC, WILL, LINEMODE}; write(fd, will_linemode, sizeof(will_linemode)); memset(&connection_data[fd], 0, sizeof(struct connection_data_t)); debugf("New connection set up.\n"); } int main(int argc, char* argv[]) { if(argc!=2) { debugf("Listening on default port: %d\n", DEFAULT_PORT); port=DEFAULT_PORT; } else { int port2=atoi(argv[1]); if(port2<0 || port2>65535) { debugf("FATAL: Port out of range.\n"); return 2; } port=atoi(argv[1]); } debugf("Initializing server on port %u...\n", port); signal(SIGINT, &serv_cleanup); int sock=make_server_socket(port); server_socket=sock; fd_set active_fd_set, read_fd_set; struct sockaddr_in client; if(listen(sock, 1)<0) { debugf("FATAL: Error opening socket.\n"); return 1; } FD_ZERO(&active_fd_set); FD_SET(sock, &active_fd_set); debugf("Server ready, listening on port %d.\n", port); debugf("Maximum clients: %d\n", FD_SETSIZE); while(1) { read_fd_set=active_fd_set; int ret=select(FD_SETSIZE, &read_fd_set, 0,0,0); if(ret<0) { debugf("Error in select().\n"); return 1; } for(int i=0;i