/*
* 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