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