/*
Calisto (c) 1998-2000 Peter Howkins, Matthew Howkins, Simon Howkins
$Id: main.c,v 1.32 2000/03/23 23:18:05 peter Exp $
*/
static char rcsid[] = "$Id: main.c,v 1.32 2000/03/23 23:18:05 peter Exp $";
#include "config.h"
/* Ansi Includes */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>
/* Unix Includes */
#include "netdb.h"
#include "unistd.h"
#include "netinet/in.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "sys/ioctl.h"
#include "sys/time.h"
#include "errno.h"
#ifdef HAVE_CRYPT_H
#include "crypt.h"
#endif
#ifdef HAVE_SYS_FILIO_H
#include "sys/filio.h"
#endif
/* Our Includes */
#include "dllist.h"
#include "globals.h"
#include "commands.h"
#include "help.h"
#include "inifile.h"
#include "library.h"
#include "log.h"
#include "playerdb.h"
#include "pool.h"
#include "privs.h"
#include "structs.h"
#include "strplus.h"
const unsigned version = 3; /* Don't prefix constant with 0 - that's octal */
listhead AllConns;
listhead AllPlayers;
pool *descriptor_pool = NULL;
bool sdown;
time_t starttime;
/* Globals from .ini file */
char talker_name[256] = "";
unsigned port;
char admin_name[256] = "";
bool use_net_lookups = FALSE;
unsigned idle_boot_time = 0;
unsigned max_connections = 20;
static fd_set readfds, creadfds;
bool player_exists(const char *n)
{
char path[256], name[256];
FILE *player;
STRNCOPY(name, n, sizeof(name));
strlower(name);
STRNCOPY(path, "lib/players/", sizeof(path));
STRNAPPEND(path, name, sizeof(path));
player = fopen(path, "r+");
if(player == FALSE) {
return FALSE;
} else {
fclose(player);
return TRUE;
}
}
void do_motd(descriptor *des)
{
bar2(des, "Message Of The Day", NULL, NULL);
send_file_to_descriptor("lib/etc/motd", des);
bar2(des, NULL, NULL, NULL);
if (haspriv(&des->player, "s_base")) {
bar2(des, "Super-user Message Of The Day", NULL, NULL);
send_file_to_descriptor("lib/etc/sumotd", des);
bar2(des, NULL, NULL, NULL);
}
}
int init_mother(void)
{
int mother_sfd;
struct sockaddr_in sai;
int opt = 1;
/* Create mother description socket, and set socket options */
mother_sfd = socket(PF_INET, SOCK_STREAM, 0);
if (mother_sfd == EOF) {
log(debug, "Could not open mother descriptor socket: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if (setsockopt(mother_sfd, SOL_SOCKET, SO_REUSEADDR,
(char *) &opt, sizeof(opt)) == EOF)
{
log(debug,"Could not set mother descriptor options1: %s", strerror(errno));
}
if (ioctl(mother_sfd, FIONBIO, &opt) == EOF) {
log(debug, "Could not set mother descriptor options2: %s", strerror(errno));
close(mother_sfd);
exit(EXIT_FAILURE);
}
sai.sin_family = AF_INET;
sai.sin_port = htons(port);
sai.sin_addr.s_addr = htonl(INADDR_ANY);
/* Bind mother description socket */
if (bind(mother_sfd, (struct sockaddr *) &sai, sizeof(sai)) < 0) {
log(debug, "Could not bind socket: %s", strerror(errno));
close(mother_sfd);
exit(EXIT_FAILURE);
}
/* Set socket to listen */
if (listen(mother_sfd, 3) ) {
log(debug, "Could not listen() on socket: %s", strerror(errno));
close(mother_sfd);
exit(EXIT_FAILURE);
}
return mother_sfd;
}
void descriptor_new(int sfd, struct sockaddr_in sin)
{
struct hostent *host;
int opt = 1;
descriptor *pnew_des = pool_malloc(descriptor_pool);
if (!pnew_des) {
fprintf(stderr, "descriptor_new: Out of memory.");
exit(sizeof(descriptor));
}
/* Set socket to non-blocking */
if (ioctl(sfd, FIONBIO, &opt) == EOF) {
log(debug, "Could not set %d socket options: %s", sfd, strerror(errno));
close(sfd);
pool_free(pnew_des);
return;
}
/* perform hostname/number lookup (Socket faq Q4.9) */
/* Nuts and part of EW-too use 4 as the size argument, but ew-too use's
sin.sin_addr.s_addr instead of sin.sin_addr in another part */
if (use_net_lookups) {
if ((host = gethostbyaddr((char *) &sin.sin_addr,
sizeof(sin.sin_addr),
AF_INET)) == NULL) {
log(debug, "Error on incoming socket during gethostbyaddr(): %s", strerror(errno));
STRNCOPY(pnew_des->hostname, "* Error *", sizeof(pnew_des->hostname));
} else {
if(strlen(host->h_name) > MAX_HOST_NAME) {
log(debug, "Hostnamelen exceeded maximum (%d)", MAX_HOST_NAME);
STRNCOPY(pnew_des->hostname, "* Error *", sizeof(pnew_des->hostname));
} else {
STRNCOPY(pnew_des->hostname, host->h_name, sizeof(pnew_des->hostname));
}
}
} else {
STRNCOPY(pnew_des->hostname, "* Not Enabled *", sizeof(pnew_des->hostname));
}
pnew_des->player.loggedin = FALSE;
pnew_des->safe = TRUE;
pnew_des->connecttime = time(NULL);
pnew_des->idletime = pnew_des->connecttime;
pnew_des->sfd = sfd;
pnew_des->inbuf_used = 0;
pnew_des->inbuf_used_lines = 0;
pnew_des->termtype[0] = '\0';
pnew_des->term_height = 24; /* a couple of sensible defaults */
pnew_des->term_width = 80;
LIST_LINK_NODE_AT_END(AllConns, &(pnew_des->descriptorlink) );
/* Add sfd to fd sets */
FD_SET(sfd, &readfds);
log(usage, "New descriptor %d from %s", sfd, pnew_des->hostname);
/* Put the terminal in echo on, line mode */
echo_on(pnew_des);
/* log(debug, "about to inquire telnet");*/
inquire_termtype(pnew_des);
inquire_windowsize(pnew_des);
/* log(debug, "successfully inquired telnet"); */
if (send_file_to_descriptor("lib/etc/login", pnew_des) != 0)
send_to_descriptor(pnew_des, "Welcome to %s\n", talker_name);
send_to_descriptor(pnew_des, "Calisto [Version %u.%02u, " __DATE__"]\n",
version / 100, version % 100);
send_to_descriptor(pnew_des, "\nlogin: ");
pnew_des->state = STATE_LOGIN;
}
void character_new(descriptor *des, const char *name)
{
character *c = &des->player;
/* Do concurrency check, in case new character of same name has been
created during the login process */
if (player_exists(name)) {
send_to_descriptor(des,
"Sorry, that name has *just* been taken\n"
"Please choose another\n");
send_to_descriptor(des, "\nlogin: ");
des->state = STATE_LOGIN;
return;
}
/* OK to continue, setup the new character */
STRNCOPY(c->name, name, sizeof(c->name));
/* set privs and check for the admin character */
if (STRIEQ(c->name, admin_name)) {
setpriv(c, "ha");
log(usage, "Player %s got full Admin Rights automatically", c->name);
} else {
setpriv(c, "resident");
}
c->loggedin = TRUE;
c->logintime = time(NULL);
c->prevtime = 0;
STRNCOPY(c->group, "Public", sizeof(c->group));
STRNCOPY(c->password, des->data.password, sizeof(c->password));
LIST_LINK_NODE_AT_END(AllPlayers, &(c->characterlink));
STRNCOPY(c->prompt, "Calisto> ", sizeof(c->prompt));
save_player(c, c->name);
des->state = STATE_PLAY;
log(usage, "Making new character %s from %s on sfd %d", c->name, des->hostname, des->sfd);
do_motd(des);
/* Send messages to new character and other characters */
send_to_all_except(c, "\n+++ ^r%s^n has logged in\n", c->name);
send_to_char(c, "\n+++ You have logged in\n");
send_to_char(c, "%s^n", c->prompt);
}
void new1(descriptor *des, const char *buffer)
{
char temp[MAX_RAW_INPUT_BUFFER];
STRNCOPY(temp, buffer, sizeof(temp));
strlower(temp);
if (temp[0] == 'y') {
send_to_descriptor(des, "Please give a password for the new character: ");
echo_off(des);
des->state = STATE_NEW2;
} else if(temp[0] == 'n') {
send_to_descriptor(des, "login: ");
des->state = STATE_LOGIN;
} else {
send_to_descriptor(des,
"Do you wish to make a new character called %s? (y/n)\n",
des->data.name);
}
}
void new2(descriptor *des, const char *buffer)
{
echo_on(des);
if (strlen(buffer) > MAX_PASSWORD_LENGTH) {
send_to_descriptor(des, "Password too long (Max %d)\n"
"Please re-enter a password: ", MAX_PASSWORD_LENGTH);
} else if (strlen(buffer) < MIN_PASSWORD_LENGTH) {
send_to_descriptor(des, "Password too short (Min %d)\n"
"Please re-enter a password: ", MIN_PASSWORD_LENGTH);
} else {
#if HAVE_CRYPT
STRNCOPY(des->data.password, crypt(buffer, CRYPT_SALT),
sizeof(des->data.password));
#else
STRNCOPY(des->data.password, buffer, sizeof(des->data.password));
#endif /* HAVE_CRYPT */
send_to_descriptor(des, "Please re-enter your password: ");
des->state = STATE_NEW3;
}
echo_off(des);
}
void new3(descriptor *des, const char *buffer)
{
echo_on(des);
if (strlen(buffer) > MAX_PASSWORD_LENGTH) {
send_to_descriptor(des, "Password too long (Max %d)\nPlease re-enter both passwords: ", MAX_PASSWORD_LENGTH);
echo_off(des);
des->state = STATE_NEW2;
} else if(strlen(buffer) < MIN_PASSWORD_LENGTH) {
send_to_descriptor(des, "Password too short (Min %d)\nPlease re-enter both passwords: ", MIN_PASSWORD_LENGTH);
echo_off(des);
des->state = STATE_NEW2;
#ifdef HAVE_CRYPT
} else if (!STREQ(crypt(buffer, CRYPT_SALT), des->data.password)) {
#else
} else if (!STREQ(buffer, des->data.password)) {
#endif /* HAVE_CRYPT */
send_to_descriptor(des, "Passwords don't match\n"
"Please re-enter both passwords: ");
echo_off(des);
des->state = STATE_NEW2;
} else {
character_new(des, des->data.name);
}
}
void login(descriptor *des, const char *buffer)
{
char temp[MAX_RAW_INPUT_BUFFER];
STRNCOPY(temp, buffer, sizeof(temp));
/* handle incorect data, too long, too short and illegal chars */
if (strlen(temp) < MIN_NAME_LENGTH || strlen(temp) > MAX_NAME_LENGTH
|| !strisalnum(temp)) {
if (strlen(temp) < MIN_NAME_LENGTH) {
send_to_descriptor(des, "Name too short (Min %d)\n", MIN_NAME_LENGTH);
}
if (strlen(temp) > MAX_NAME_LENGTH) {
send_to_descriptor(des, "Name too long (Max %d)\n", MAX_NAME_LENGTH);
}
if(!strisalnum(temp)) {
send_to_descriptor(des, "Name can only contain characters (a-z, A-Z, 0-9)\n");
}
send_to_descriptor(des, "login: ");
des->state = STATE_LOGIN;
return;
}
/* change name to lower case and check to see if it exists in the player
dir */
if (player_exists(temp) == FALSE) {
strlower(temp);
temp[0] = toupper(temp[0]);
STRNCOPY(des->data.name, temp, sizeof(des->data.name));
send_to_descriptor(des,
"Do you wish to make a new character called %s? (y/n)\n",
des->data.name);
des->state = STATE_NEW1;
} else {
STRNCOPY(des->data.name, temp, sizeof(des->data.name));
send_to_descriptor(des, "Please enter your password or press return to enter another name: \n");
echo_off(des);
des->state = STATE_PASSWORD;
}
}
void password(descriptor *des, const char *buffer)
{
character *c = &des->player;
if (STREQ(buffer, "")) {
echo_on(des);
send_to_descriptor(des, "login: \n");
des->state = STATE_LOGIN;
} else {
if (player_exists(des->data.name) == FALSE) {
/* paranoia , should never reach this error, as the file should have been
found in the previous function */
log(debug, "This error should never occur, main.c, password()");
log(debug, "Failed to open %s, when successfully opened previously", des->data.name);
} else {
load_player(c, des->data.name);
#ifdef HAVE_CRYPT
if (STREQ(crypt(buffer, CRYPT_SALT), c->password)) {
#else
if (STREQ(buffer, c->password)) {
#endif /* HAVE_CRYPT */
character *current_session = character_from_name_exact(des->data.name);
/* is char allready logged on ? */
if (current_session) {
descriptor *d = getdes(current_session);
memcpy(&des->player, current_session, sizeof(character));
d->state = STATE_CLOSING;
} else {
STRNCOPY(c->group, "Public", sizeof(c->group));
c->loggedin = TRUE;
c->logintime = time(NULL);
}
echo_on(des);
des->state = STATE_PLAY;
LIST_LINK_NODE_AT_END(AllPlayers, &(c->characterlink));
log(usage, "%s has logged in from %s on sfd %d", c->name, des->hostname, des->sfd);
do_motd(des);
send_to_all_except(c, "\n+++ ^r%s^n has logged in\n", c->name);
send_to_char(c, "\n+++ You have logged in\n");
send_to_char(c, "%s^n", c->prompt);
} else {
echo_on(des);
send_to_descriptor(des, "\nIncorrect Password\nlogin: ");
des->state = STATE_LOGIN;
}
}
}
}
void descriptor_close(descriptor *des)
{
if(des->player.loggedin == TRUE) {
character *c = &des->player;
/* Test (save the player) */
save_player(c, c->name);
log(usage, "%s has logged out", c->name);
LIST_UNLINK_NODE(&c->characterlink);
}
/* remove sfd from fd sets */
FD_CLR(des->sfd, &readfds);
close(des->sfd);
}
void close_all_connections(const char *message)
{
listnode *node = AllConns.head.next;
log(usage, "Shutdown: Closed all connections, saved all characters");
log(usage, "Shutdown: \'%s\'", message);
while (LIST_NODE_IS_REAL(node)) {
descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);
send_to_descriptor(des, "\n+++ %s\n", message);
descriptor_close(des);
LIST_UNLINK_NODE(node);
pool_free(des);
node = node->next;
}
}
static void init_inifile(void)
{
if (inifile_open("calisto.ini")) {
/* we may be able to do without, and make up some values - for now exit */
fprintf(stderr, "Could not open .ini file\n");
exit(EXIT_FAILURE);
}
inifile_get_token("talker_name", "%s", talker_name);
if (inifile_get_token("port", "%u", &port)) {
fprintf(stderr, "Value for 'port' not found in .ini file\n");
exit(EXIT_FAILURE);
}
if (inifile_get_token("admin_name", "%s", admin_name)) {
fprintf(stderr, "Value for 'admin_name' not found in .ini file\n");
exit(EXIT_FAILURE);
}
inifile_get_token("use_net_lookups", "%d", &use_net_lookups);
inifile_get_token("idle_boot_time", "%u", &idle_boot_time);
inifile_get_token("max_connections", "%u", &max_connections);
inifile_close();
}
void init_stuff(void)
{
/* process the .ini file for options */
init_inifile();
/* initialise logs, if the logs fail to open thats not actually that bad */
usage = log_init("log/usage", LOGDATE);
debug = log_init("log/debug", LOGDATE);
bug = log_init("lib/text/bug", LOGDATE);
idea = log_init("lib/text/idea", LOGDATE);
typo = log_init("lib/text/typo", LOGDATE);
log(usage, "%s (Calisto %u.%02u) booted on port %u",
talker_name, version/100, version%100, port);
/* Init the starttime */
starttime = time(NULL);
/* Initialise the help text */
help_init("lib/helpfile");
/* init fd set */
FD_ZERO(&readfds);
/* Initialise pool-based memory management */
descriptor_pool = pool_create(sizeof(descriptor));
if (!descriptor_pool) {
fprintf(stderr, "Not enough memory creating descriptor_pool\n");
log(usage, "Not enough memory creating descriptor_pool\n");
exit(EXIT_FAILURE);
}
/* Initialise descriptor list */
LIST_INIT(AllConns);
LIST_INIT(AllPlayers);
}
RETSIGTYPE signal_term(int signal)
{
/* The int received is the signal - in this case always SIGTERM */
switch(signal) {
case SIGTERM:
close_all_connections("Received SIGTERM - shutting down");
break;
case SIGINT:
close_all_connections("Received SIGINT - shutting down");
break;
}
exit(0);
}
void init_signal(void)
{
signal(SIGPIPE, SIG_IGN); /* handles problems, such as trying to write to
a disconnected socket */
signal(SIGTERM, signal_term);
signal(SIGINT, signal_term);
}
void auto_idle_booting(void)
{
time_t now = time(NULL);
listnode *node = AllConns.head.next;
if (idle_boot_time != 0) {
while (LIST_NODE_IS_REAL(node)) {
descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);
if (difftime(now, des->idletime) > (idle_boot_time * 60)) {
send_to_descriptor(des, "\n+++ You have been automatically logged off "
"as you have been idle for %u minute(s).\n",
idle_boot_time);
if (des->player.loggedin == TRUE) {
character *c = &des->player;
send_to_all_except(c, "\n+++ ^r%s^n idles out of the program\n",
des->player.name);
}
des->state = STATE_CLOSING;
des->safe = FALSE;
log(usage, "%s will be logged off for being idle for %u minutes",
des->player.name, idle_boot_time);
}
node = node->next;
}
}
}
int main(void)
{
int mother_sfd;
struct timeval tv;
socklen_t len = sizeof(struct sockaddr_in); /* for the accept call later */
init_signal();
init_stuff();
mother_sfd = init_mother();
sdown = FALSE;
fprintf(stderr, "%s booted on port %d\n", talker_name, port);
/* The Main Loop */
while (!sdown) {
int new_con_sfd;
int retval;
struct sockaddr_in sin;
/* throw off the idle buggers */
auto_idle_booting();
/* See if there's a new connection ready */
new_con_sfd = accept(mother_sfd, (struct sockaddr *) &sin, &len);
if (new_con_sfd != -1)
descriptor_new(new_con_sfd, sin);
/* Use select() to see if there is any data to be read on any sfd, set
time struct for select, this below should make it loop 5 times a
second (0.2 seconds) and copy the fd sets so they don't get corrupted */
tv.tv_sec = 0; tv.tv_usec = 200;
creadfds = readfds;
retval = select(FD_SETSIZE, &creadfds, NULL, NULL, &tv);
if(retval != -1) { /* Loop through connected descriptors to see if they have changed state */
listnode *node = AllConns.head.next;
while (LIST_NODE_IS_REAL(node)) {
descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);
int state = des->state;
int read;
/* check for incoming input */
if (FD_ISSET(des->sfd, &creadfds)) {
read = get_from_descriptor(des);
if (read < 0) {
log(usage, "Connection broken on socket %d read() function",
des->sfd);
des->state = STATE_CLOSING;
des->safe = FALSE;
/* if logged in inform other players of their departure */
if(des->player.loggedin == TRUE) {
character *c = &des->player;
send_to_all_except(c, "\n+++ ^r%s^n disappears suddenly\n",
c->name);
}
} else if (read >= 0) {
int line;
char *curline = &des->inbuf[0];
des->idletime = time(NULL);
/* begin loop */
for (line=0 ; line<read ; line++) {
switch(state) {
case STATE_LOGIN: login(des, curline); break;
case STATE_PASSWORD: password(des, curline); break;
case STATE_NEW1: new1(des, curline); break;
case STATE_NEW2: new2(des, curline); break;
case STATE_NEW3: new3(des, curline); break;
case STATE_PLAY: command_do(des, curline); break;
}
/* move onto next command */
if(line != read-1) {
curline += strlen(curline) + 1;
}
} /* end loop through comand line */
}
} /* endif check for fd set inclusion */
/* Ensure that sfd is checked next time */
FD_SET(des->sfd, &readfds);
/* dump all those who have been told to bob off, as they may have been
removed from the fd sets */
if (state == STATE_CLOSING) {
descriptor_close(des);
LIST_UNLINK_NODE(node);
pool_free(des);
break;
} /* endif (state == STATE_CLOSING) */
node = node->next;
} /* end while(LIST_NODE_IS_REAL(node)) */
} else { /* end loop through connections to see if they have changed state */
/* select has returned -1 */
/* best to bail out, as I can't do much with the errors */
sdown = TRUE;
}
} /* end while(!sdown) */
/* Program is shutting down */
close_all_connections("System shutting down normally");
close(mother_sfd);
fprintf(stderr, "%s has shutdown normally\n", talker_name);
return EXIT_SUCCESS;
}