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