musicmud-2.1.6/data/
musicmud-2.1.6/data/help/
musicmud-2.1.6/data/policy/
musicmud-2.1.6/data/wild/
musicmud-2.1.6/data/world/
musicmud-2.1.6/doc/
musicmud-2.1.6/src/ident/
musicmud-2.1.6/src/lua/
musicmud-2.1.6/src/lua/include/
musicmud-2.1.6/src/lua/src/lib/
musicmud-2.1.6/src/lua/src/lua/
musicmud-2.1.6/src/lua/src/luac/
/*
 * MusicMUD Daemon, version 1.0
 * Copyright (C) 1998-2003 Abigail Brady, others
 * 
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <dlfcn.h>
#include <ctype.h>
#include <unistd.h>
#include <locale.h>
#include <dirent.h>
#include <fcntl.h>

#include "config.h"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#include <set>

#define EXTERN

#include "startup.h"
#include "musicversion.h"
#include "Socket.h"
#include "musicmud.h"
#include "verbs.h"
#include "Player.h"
#include "State.h"
#include "Pager.h"
#include "log.h"
#include "zoneload.h"
#include "magic.h"
#include "Library.h"
#include "msi.h"
#include "aberchat.h"
#include "config.h"
#include "pflagnames.h"
#include "flagnames.h"
#include "trap.h"
#include "Library.h"
#include "Interpret.h"
#include "util.h"
#include "Writer.h"
#include "hooks.h"

#include "misc.h"

#ifdef CONFIG_PIPE_IDENT
#include "pipe-ident.h"
#endif

//! returns true if the mud is running on the test site - ie is on port 6665
bool is_test() {
  return msi::port() == 6665;
}

//! is the mud rebooting at the moment?
bool rebooting = false;

//! container containing every object on the mud. will create new ones on demand.

Global *planet;

//! all the zones
World<MudObject> *zones;

//! all the verbs
World<Verb> *verbs;

//! all the states
World<State> *states;

//! all the loaded libraries
World<Library> *libs;

//! all the interpreters
World<Interpreter> *interpreters;

//! all the players
World<Player> *players;

//! the root object
MudObject *mud;

void (*aberhook)() = 0;
void (*abermail_hook)(const char *, const char *, const char *, const char *,
				    const char *, const char *) = 0;
Socket *aberchat_socket = 0;
bool aberchat_isup = false;
bool aberchat_auth = false;
bool logstdout = true;

/* aberchat properties */
bool achat_auto_run = true;
int achat_fuckup_timeout=0;//used to store time to try to start again.
int pending_achat_socket=-1;//used as part-time store for achat connection
int achat_ping_delay=60;//do a mudlist every 60 seconds.
int achat_ping_sched=0;//time of next ping.
int last_achat=0;//time since last aberchat thing happened.
char * achat_crash_mat=NULL;
char * achat_crash_err=NULL;
set<string> achat_list;
/* end aberchat properties */

time_t code_reload = 0, mud_reset = 0, mud_reboot = 0, mud_start = 0, now = 0;

#ifndef HAVE_STRCASESTR
const char *strcasestr(const char *big, const char *little)
{
  string b = make_lower(big);
  string l = make_lower(little);
  const char *t = strstr(b.c_str(), l.c_str());
  if (!t)
    return 0;
  size_t off = t - b.c_str();
  return big + off;
}
#endif

//! compare two strings. either of them can be NULL
bool streq(const char *a, const char *b) {
	if (!a || !b) return false;
	return a==b ||
	       (strcmp(a, b)==0);
}

//! convert an argv[] list back into a string
string the_rest(int argc, const char *argv[], int first) {
  string tmp;

  for (int i=first;i<argc;i++) {
    tmp += argv[i];
    if (i!=(argc-1)) tmp += " ";
  }

  return tmp;
}

static void add_con(fd_set *fdset);
static void nuke_needed();

int which_player = 1;

//! create a new temporary player id.
const char *player_id() {
	static char name[100];
	sprintf(name, "@player_%i", which_player);
	which_player++;
	return name;
}

//! callback for the ident code. given a ticket and a uid, we search through the players to find that ticket.
void ident_handler(int ticket, const char *uid) {
  MudObject *o;
  int i;
  foreach(players, o, i) {
    if (o->get_int("_ticket")==ticket) {
      o->set("_username", uid);
      log(PFL_HOSTS, 0, "net", "userid for %s : %s", o->id, uid);
      return;
    }
  }
  log(PFL_HOSTS, 0, "net", "lookup arrived late : %s (ticket %i)", uid, ticket);
}

//! start and connect to the ident lookup code
void ident_start() {
    ident::init();
    ident::set_handler(ident_handler);
}

//! deserialise preserved state from a reboot.
static void add_connected() {
    char name[256];

    sprintf(name, VARDATA "/reboot-misc.%i", getpid());
    FILE *f=xopen(".", name, "r");
    unlink(name);
    
    if (f) {
      mud_reboot = mud_start;
      fscanf(f, "%i\n", &(int)mud_start);
      fclose(f);
    }

    sprintf(name, VARDATA "/reboot.%i", getpid());
    f=xopen(".", name, "r");
    //    unlink(name);

    if (!f) {
	log(PFL_SEEINFO, 0, "reboot", "reboot failed...");
	exit(1);
    }

    while (true) {
      char previous_owner[256];
      int fd;
      int previous_idle;
      char serial[1000];

      string s = getline(f);

      sscanf(s.c_str(), "%s %i %s %i %s", name, &fd, previous_owner, &previous_idle, serial);

      if (feof(f)) 
	break;
      
      msi::player *p = new msi::player(fd);
      
      xreg(fd, "passed from reboot");
      xselect(fd);
      
      if (strlen(serial)>10)
	p->deserialise(serial);
      
      const char *pname = player_id();
      
      Player *p2 = new Player(pname, p);
      p2->get_idle() = previous_idle;
      planet->add(*p2);
      
      if (name[0]!='@') {
	p2->set("short", pname);
	p2->set("name", pname);
	p2->set("owner", "@musicmud");

	state_t f = (state_t)Library::find_symbol("state.so", "reboot_recover");
	if (f) {
	  f(p2, name);
	  MudObject *ho = p2->get_object("reboothome");
	  set_owner(p2, previous_owner);
	  if ((!p2->owner || p2->owner == mud) && ho) {
	    set_owner(p2, ho);
	  }
	  if ((!p2->owner) || p2->owner==mud) {
	    set_owner(p2, MUD_DEFHOME);
	  }
	  if (!p2->owner)
	    p2->set("owner", "@musicmud");
	} else {
	  p2->printf("Sorry, you'll have to log in again...\n");
	}
      } else {	
	string s = getline(f);
	if (s.length()>1) {
	  deserialise_personstate(p2->get_person_state(), s);
	}

	p2->setf("short", "player%i", which_player-1);
	p2->setf("name", "[Logging In %i]", which_player-1);
	p2->set("desc", "This player has no description.");
	p2->set("owner", "@musicmud");
	
	if (p2->p->thingy_from==msi::tNormal) {
	  p2->set_bflag(FL_COLOUR, 1);
	}
      }
    }
    xclose(f);
    
    sprintf(name, VARDATA "/reboot-parties.%i", getpid());
    f=xopen(".", name, "r");
    unlink(name);
    char buf[4000];
    if (!f)
      return;

    while (1) {
      fgets2(buf, 3000, f);
      if (feof(f))
	break;
      buf[3000] = 0;
      
	char *a = strtok(buf, " ");
	MudObject *leader = planet->get(a);
	if (leader) {
	  while ((a=strtok(NULL, " "))) {
	    MudObject *victim = planet->get(a);
	    if (victim) {
	      victim->follow(leader);
	    }
	  }
	}
    }
    xclose(f);
}

//! return the file descriptor associate with /p/, or -1
static int file_desc(Player *p) {
	if (!p) return -1;
	if (!p->p) return -1;
	if (!p->p->socket) return -1;
	return p->p->socket->getfd();
}

//! does this object want execute() running on it every second?
inline int query(MudObject *what) {
  if (is_player(what)) return 1;
  if (what->quit) return 1;
  if (what->get_flag(FL_MISSION)) return 1;
  if (what->get_flag(FL_MOBILE)) return 1;
  if (what->get_flag(FL_TIMER)) return 1;
  if (what->get_flag(FL_RANTER)) return 1;
  if (what->get_flag(FL_WILDERNESS)) return 1;
  if (what->get_flag(FL_ONFIRE)) return 1;
  return 0;
}

extern MudObject *qui;

//! the main loop
void main_cycle() {
    
    log(PFL_SEEINFO, 0, "main", "starting cycle");
    
    if (rebooting) {
	log(PFL_SEEINFO, 0, "global", "connection restoration started");
	add_connected();
	rebooting = false;
	log(PFL_NONE, 0, "global", "reboot finished");
    }
        
    signals_start();
    
    while(true) {
#ifdef PROFILE
      struct timeval start;
      gettimeofday(&start, NULL);
#endif

      ident::poll();

      if (aberhook) 
	aberhook();
      
      time_t then = now;
      time(&now);
      
      qui = 0;
      if (then != now) {
	if(LUA_TIMERHOOK) LUA_TIMERHOOK();
	MudObject *o;
	int i;
	foreach(planet, o, i) 
	  if (query(o))
	    o->execute();
	
	Player *p;
	foreach(players, p, i) {
	  p->tryio(0);
	}
      }
      qui = 0;
      
#ifdef PROFILE
      struct timeval end;
      gettimeofday(&end, NULL);
      {
	long long foo = end.tv_usec - start.tv_usec;
	foo += (end.tv_sec * 1000000L) - (start.tv_sec * 1000000L);
	printf("%f seconds\n", foo/1000000.0);
      }
#endif
      
      nuke_needed();
      struct timeval tv;
      tv.tv_sec = 0;
      tv.tv_usec = 500000;
      fd_set fdset, writes;
      int maxfd = makefdset(&fdset);

      {
	Player *l;
	int j;
	foreach (players, l, j)
	  if (l->p && l->p->nosend)
	    FD_SET(file_desc(l), &writes);
      }

      qui = 0;
      if (select(maxfd, &fdset, &writes, NULL, &tv)) {
	Player *l;
	int j;
	foreach(players, l, j) {
	  int f = file_desc(l);
	  if (f != -1 && FD_ISSET(f, &writes)) {
	    l->p->nosend = 0;
	    l->p->flush();
	  }
	  if (f != -1 && FD_ISSET(f, &fdset)) {
	    qui = 0;
	    l->tryio(1);
	    qui = 0;
	  }
	}
	add_con(&fdset);
	foreach(players, l, j) {
	  l->tryio(0);
	  if (l->p)
	    l->p->flush();
	}
	
      }
      
      if(BUGNOTIFY_ROUTINE)
	BUGNOTIFY_ROUTINE();
      
      {
	Player *l;
	int j;
	foreach(players, l, j) {
	  if( (l->p) && (now - l->p->last_keepalive > 60) )
	    l->p->keepalive();
	}
      }
      
      
      qui = 0;
      if (g_sigpower == 1) {
	broadcast("%#s will make a scheduled shutdown in 20 seconds.\n"
		  "Please finish what you are doing, and log out.\n"
		  , THE_MUDNAME);
	g_sigpower = 0;
	mud->interpret("file_plan shutdown");
      }
      
      if (g_sigint==1) {
	mud->interpret2("shutdown");
	exit(1);
      }
      
      int tourn = mud->get_int("tournament", 0);
      if (tourn && tourn < now) {
	mud->interpret2("tournament off");
      }
    }
}

//! print out the version number to stdout.
void report_on_version() {
    printf("MusicMUD daemon version %s\n", MUSICVERSION);
}

//! print the process id to stdout
void report_on_pid() {
    printf("process id %i\n", (int)getpid());
}

#ifndef DEFINED_MAIN
//! the entry point
int main(int argc, char **argv) {

    now = time(NULL);
    srand(now ^ getpid());
    
    mud_start = mud_reset = time(NULL);
  
    msi::set_port(MUD_DEFPORT);

#ifdef HAVE_GETOPT_H
    struct option opts[] = {
	    { "port", 1, NULL, 'p' },
	    { "rebooting", 0, NULL, 'r' },
	    { "quiet", 0, NULL, 'q' },
	    { "help", 0, NULL, 'h' },
	    { "version", 0, NULL, 'v' },
	    { "trace", 0, NULL, 't' },
	    { 0, 0, 0, 0 },
    };
#endif

    while(1) {
#ifdef HAVE_GETOPT_H
	int ch = getopt_long(argc, argv, "qrp:hvt", opts, 0);
#else
	int ch = getopt(argc, argv, "qrp:hvt");
#endif
	if (ch==EOF) 
	  break;
	
	switch (ch) {
	 case 'r': 
	    rebooting = true;
	    break;
	case 't':
	  extern bool sock_debug;
	  sock_debug = true;
	  break;
	case 'p':
	  {
	    char *e;
	    long l = strtol(optarg, &e, 10);
	    if (*e) {
	      printf("Bad port.\n");
	      exit(0);
	    }
	    if (l < 0 || l >= 65536) {
	      printf("Port out of range.\n");
	      exit(0);
	    }
	    msi::set_port(l);
	  }
	  break;
	case 'q':
	  logstdout = false;
	  break;
	case 'h':
	  report_on_version();
	  printf("Usage : %s [--port port] [--rebooting] [--quiet] [--version] [--trace]\n", argv[0]);
	  exit(1);
	case 'v':
	  report_on_version();
	  exit(1);
	default:
	  exit(1);
	    break;
	}
	
    }

    {
      char b[1000];
      char *dir = getcwd(b, 1000);
      if (dir)
	if (const char *d = strstr(dir, "/src"))
	  if (!d[4])
	    chdir("../");
    }

    report_on_version();
    report_on_pid();
    xreg(fileno(stdin), "stdin");
    xreg(fileno(stdout), "stdout");
    xreg(fileno(stderr), "stderr");
    
    load_flags();
    load_pflags();
    
    init_vectors();
    msi::init();
    register_pager();
    register_writer();
    ident_start();
    register_version();
    register_intrinsic();
    
	FILE *f = xopen(STADATA, "modules", "r");
	if (!f) {
	   perror("opening data/modules!");
	} else {
		char foo[256];
		while (1) {
			fgets(foo, 200, f);
			if (feof(f)) break;
			char *a = strchr(foo, '\n');
			if (!a) break;
			*a = 0;
		 	if (!*foo) break;
			libs->add(*new Library(foo));
		}
		fclose(f);
	}

	init_world();

	load_zones();

	load_var_data();

	doreset("all");

	iforeach(l, *libs) {
	  l->after_reset();
	}
		
	main_cycle();
}
#endif

//! wildcard matcher. looks for 'pattern' in string. pattern can have '?', and '*'.
bool wcmatch (const char *pattern, const char *string) {

  int a = 0;
  int b = 0;

  while (pattern[a] != 0) {

    if (pattern[a] == '?') {            /* Match exactly one arbitrary character */

      if (string[b] == 0)
        return 0;

      a++;
      b++;
    } else if (pattern[a] != '*') {     /* Match one specific character */

      if (string[b] != pattern[a])
        return 0;

      a++;
      b++;
    } else {    /* Match arbitrary number of arbitrary characters...
                 * All sub-patterns of "*" and "?" are equivalent to
                 * a simpler pattern of a single * and the right number of ?'s
                 * That is, it matches an arbitrary sequence of _at least_
                 * some number of characters. Skip that many characters. */
      while (pattern[a] == '*' || pattern[a] == '?') {

        if (pattern[a] == '?') {
          if (string[b] == 0)
            return 0;
          b++;
        }

        a++;
      }

      /* The next character in the pattern is either a text character */
      /* or end-of-string */
      if (pattern[a] == 0)
        return 1;

      /* Match remainder of string */
      /* If it's text, test all possible matches recursively */
      while (string[b] != 0) {
        if (string[b] == pattern[a]) {
          if (wcmatch (&pattern[a+1],&string[b+1]))
            return 1;
        }
        b++;
      }

      return 0;                 /* None of them matched */
    }
  }                             /* End while */

  /* If we get here, the pattern ran out without encountering a "*" wildcard
   * If the string has also run out, it matches.  If not, it doesn't match */
  return (string[b] == 0);
}

//! checks to see if the given player is banned.
bool check_ban(msi::player *p) {
    FILE *f = xopen(VARDATA, "banned", "r");
    if (!f) return false;
    
    string what = p->get_ip();
    string what2 = p->get_hostname();
    
    char name[256];
    
    while (1) {
	fgets(name, 256, f);
	if (feof(f)) break;
	*strchr(name, '\n')=0;
	if (wcmatch(name, what.c_str())||
	    wcmatch(name, what2.c_str())) {
	    xclose(f);
	    return true;
	}
    }
    
    xclose(f);
    return false;
}

//! check to see if player is banned, and if so close their connection
void doban(Player *p) {
  if (!p || !p->p)
    return;
  
  if (check_ban(p->p)) {
    p->p->write("\n\033[2J\033[H\2Your host has been banned for some reason.\n", def_cols);
    log(PFL_HOSTS, 0, "network", "connection from banned IP : closing");
    delete p->p;
    p->p = 0;
    p->nuke_me = 1;
    return;
  }
}

void try_bans() {
  Player *p;
  int i;
  foreach(players, p, i) {
    doban(p);
  }
}

//! accept as many connections as are available.
void add_con(fd_set *fdset) {
  while (1) {
    msi::player *me = msi::player::accept(fdset);
    if (!me) return;
    
    if (check_ban(me)) {
      me->write("Your host has been banned for some reason.\n", def_cols);
      log(PFL_HOSTS, 0, "network", "connection from banned IP : closing");
      delete me;
      return;
    }
    
    Player *p = new Player(player_id(), me);
    
    switch(p->p->thingy_from) {
    case msi::tWebmake:
      break;
    case msi::tNormal:
    case msi::tMono:
    default:
      ;
      p->set("_ticket", ident::lookup(p->p->socket->get_addr(), 
				      p->p->socket->get_local_addr()));
    }
    
    char name[256];
    sprintf(name, "player%i", which_player-1);
    p->set("short", name);
    sprintf(name, "[Logging In %i]", which_player-1);
    p->set("name", name);
    p->set("desc", "This player has no description.");
    p->set("owner", "@musicmud");

    switch(p->p->thingy_from) {
    case msi::tWebmake :
      me->rawmode = 1;
      if(states->get("webmake")) {
	me->write("220 Musicmud\n", def_cols);
	me->flush();
	p->push_state("webmake");
      } else {
	me->write("421 webmake not enabled.\n", def_cols);
	log(PFL_HOSTS, 0, "network", "webmake not loaded - closing");
	delete me;
	p->p = 0;
	delete p;
	return;
      }
      break;
    default:
    case msi::tNormal :
      p->set_bflag(FL_COLOUR, 1);
    case msi::tMono :
      if (!p->p->socket->is6() || !p->spewfile(DATA_INFO, "issue6.i"))
	p->spewfile(DATA_INFO, "issue.i");
    }
    
    planet->add(*p);
  }
}

//! delete any objects that have been marked as needing deletion.
void nuke_needed() {
  MudObject *o;
  int i;
  foreach(planet, o, i) {
    if (!o->nuke_me)
      continue;

    if (o==mud) {
      o->nuke_me = 0;
      continue;
    }
    
    MudObject *where = o->owner;
    if (!where)
      where = mud;

    MudObject *p;
    int j;
    
    foreach(o->children, p, j) {
      set_owner(p, where);
      if (p->owner != o)
	j--;
    }
    
    planet->remove(*o);
    delete o;
    i--;
  }
}

//! are mobiles and other events turned on?
int game_mobiles = 0;

//! compare two strings, with 1 < 2 < 10
int strcmpn(const char *s1, const char *s2)
{
  int digits, predigits = 0;
  const char *ss1, *ss2;

  while (1) {
    if (*s1 == 0 && *s2 == 0)
      return 0;
    digits = isdigit(*s1) && isdigit(*s2);
    if (digits && !predigits) {
      ss1 = s1;
      ss2 = s2;
      while (isdigit(*ss1) && isdigit(*ss2))
        ss1++, ss2++;
      if (!isdigit(*ss1) && isdigit(*ss2))
        return -1;
      if (isdigit(*ss1) && !isdigit(*ss2))
        return 1;
    }
    if ((unsigned char)*s1 < (unsigned char)*s2)
      return -1;
    if ((unsigned char)*s1 > (unsigned char)*s2)
      return 1;
    predigits = digits;
    s1++, s2++;
  }
}

//! like 'ctime' but with the timezone of who's choice, and without \n
const char *ctime_for(MudObject *who, time_t *t)
{
  const char *tz = 0; 
  if ((tz=who->get("timezone"))) {
    setenv("TZ", tz, 1);
  } else {
    tz = MUD_DEFTZ;
    setenv("TZ", MUD_DEFTZ, 1);
  }
  tzset();
  char *c = ctime(t);
  static char blah[1000];
  strcpy(blah, c);
  if ((c = strchr(blah, '\n'))) {
    *c = 0;
    strcat(c, " (");
    strcat(c, tz);
    strcat(c, ")");
  }
  unsetenv("TZ");
  return blah;
}

//! strftime for timezone of who's choice. returns the string rather than using a buffer.
string sstrftime(MudObject *who, const char *fmt, time_t when)
{
  char buf[200];

  if (const char *tz=who->get("timezone")) {
    setenv("TZ", tz, 1);
  } else {
    setenv("TZ", MUD_DEFTZ, 1);
  }
  tzset();

  struct tm *loctime = localtime(&when);
  strftime(buf, 100, fmt, loctime);

  unsetenv("TZ");

  buf[100] = 0;
  return buf;
}