/* * server.cpp * Server code, Handles sockets and input/output * ____ _ * | _ \ ___ __ _| |_ __ ___ ___ * | |_) / _ \/ _` | | '_ ` _ \/ __| * | _ < __/ (_| | | | | | | \__ \ * |_| \_\___|\__,_|_|_| |_| |_|___/ * * Permission to use, modify and distribute is granted via the * Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License * http://creativecommons.org/licenses/by-nc-sa/3.0/ * * Copyright (C) 2007-2009 Jason Mitchell, Randi Mitchell * Contributions by Tim Callahan, Jonathan Hseu * Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman * */ // Mud Includes #include "mud.h" #include "timer.h" #include "login.h" #include "version.h" #include "factions.h" #include "web.h" #include "calendar.h" #include "magic.h" // C Includes #include <netinet/in.h> // Needs: htons, htonl, INADDR_ANY, sockaddr_in #include <sys/socket.h> // Needs: bind, std::listen, socket, AF_INET #include <fcntl.h> // Needs: fnctl #include <netdb.h> // Needs: gethostbyaddr #include <sys/wait.h> // Needs: WNOHANG, wait3 #include <errno.h> // C++ Includes #include <iostream> #include <sstream> #include <iomanip> #include <locale> // External declarations extern int Numplayers; extern long last_time_update; extern long last_weather_update; // Function prototypes char *inetname(struct in_addr in ); void initSpellList(); // TODO: Move spelling stuff into server // Global server pointer Server* gServer = NULL; // Static initialization Server* Server::myInstance = NULL; //-------------------------------------------------------------------- // Constructors, Destructors, etc //******************************************************************** // Server //******************************************************************** Server::Server() { FD_ZERO(&inSet); FD_ZERO(&outSet); FD_ZERO(&excSet); rebooting = GDB = valgrind = false; running = false; Deadchildren = 0; pulse = 0; webInterface = 0; first_active = 0; lastDnsPrune = lastUserUpdate = lastRoomPulseUpdate = lastRandomUpdate = lastActiveUpdate = 0; loadDnsCache(); pythonHandler = 0; } //******************************************************************** // ~Server //******************************************************************** Server::~Server() { std::cout << "Server destructor called!\n"; if(running) { // Do shutdown here } effectsIndex.clear(); cleanUpPython(); } //******************************************************************** // init //******************************************************************** bool Server::init() { std::cout << "Initializing Server.\n"; printf("Installing signal handlers."); installSignalHandlers(); printf("done.\n"); #ifndef __CYGWIN__ printf("Installing custom printf handlers..."); if(installPrintfHandlers() == 0) printf("done\n"); else printf("failed\n"); #endif gConfig->load(); gConfig->startFlashPolicy(); gConfig->setLotteryRunTime(); Port = gConfig->getPortNum(); Tablesize = getdtablesize(); printf("Initializing Spelling..."); init_spelling(); printf("done.\n"); initWebInterface(); initSpellList(); // Python initPython(); //cleanUpPython(); umask(000); srand(getpid() + time(0)); if(rebooting) { printf("Doing a reboot.\n"); finishReboot(); } else { addListenPort(Port); unlink(REBOOTFILE); } return(true); } //******************************************************************** // installSignalHandlers //******************************************************************** void Server::installSignalHandlers() { signal(SIGABRT, crash); // abnormal termination triggered by abort call printf("."); signal(SIGFPE, crash); // floating point exception printf("."); signal(SIGSEGV, crash); // segment violation printf("."); signal(SIGPIPE, SIG_IGN); printf("."); signal(SIGTERM, SIG_IGN); printf("."); signal(SIGCHLD, child_died); printf("."); signal(SIGHUP, quick_shutdown); printf("."); signal(SIGINT, shutdown_now); printf("."); } void Server::setGDB() { GDB = true; } void Server::setRebooting() { rebooting = true; } void Server::setValgrind() { valgrind = true; } bool Server::isRebooting() { return(rebooting); } bool Server::isGDB() { return(GDB); } bool Server::isValgrind() { return(valgrind); } int Server::getNumSockets() { return(sockets.size()); } // End - Constructors, Destructors, etc //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Instance Functions //******************************************************************** // Get Instance - Return the static instance of config //******************************************************************** Server* Server::getInstance() { if(myInstance == NULL) myInstance = new Server; return(myInstance); } //******************************************************************** // Destroy Instance - Destroy the static instance //******************************************************************** void Server::destroyInstance() { if(myInstance != NULL) delete myInstance; myInstance = NULL; } // End - Instance Functions //-------------------------------------------------------------------- //******************************************************************** // run //******************************************************************** int Server::run(void) { Timer timer; if(running == false) { std::cerr << "Not bound to any ports, exiting." << std::endl; exit(-1); } while(running) { if(Deadchildren) reapChildren(); processChildren(); timer.start(); // Start the timer poll(); checkNew(); processInput(); processCommands(); // Update game here updateGame(); processOutput(); cleanUp(); // Temp pulse++; checkWebInterface(); timer.end(); // End the timer timer.sleep(); } return(0); } //******************************************************************** // addListenPort //******************************************************************** int Server::addListenPort(int port) { struct sockaddr_in sa; int optval = 1; int control; memset((char *)&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY); if((control = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Error with socket\n"); return(-1); } if(setsockopt(control, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval)) < 0) { printf("Error with setSockOpt\n"); return(-1); } if(bind(control, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(control); printf("Unable to bind to port(%d)\n", port); return(-1); } if(nonBlock(control) != 0) { printf("Error with nonBlock\n"); return(-1); } if(listen(control, 100) != 0) { printf("Error with listen\n"); return(-1); } std::cout << "Mud is now listening on port " << port << std::endl; // TODO: Leaky, make sure to erase these when the server shuts down controlSocks.push_back(controlSock(port, control)); running = true; return(0); } //******************************************************************** // poll //******************************************************************** int Server::poll() { if(!this || controlSocks.empty()) { printf("Not bound to any ports, nothing to poll.\n"); exit(0); } int maxFd = 0; struct timeval noTime; noTime.tv_sec = 0; noTime.tv_usec = 0; FD_ZERO(&inSet); FD_ZERO(&outSet); FD_ZERO(&excSet); foreach(controlSock & cs, controlSocks) { if(cs.control > maxFd) maxFd = cs.control; FD_SET(cs.control, &inSet); } foreach(Socket * sock, sockets) { if(sock->getFd() > maxFd) maxFd = sock->getFd(); FD_SET(sock->getFd(), &inSet); FD_SET(sock->getFd(), &outSet); FD_SET(sock->getFd(), &excSet); } if(select(maxFd+1, &inSet, &outSet, &excSet, &noTime) < 0) return(-1); return(0); } //******************************************************************** // checkNew //******************************************************************** int Server::checkNew() { foreach(controlSock & cs, controlSocks) { if(FD_ISSET(cs.control, &inSet)) { // We have a new connection waiting std::cout << "Got a new connection on port " << cs.port << ", control sock " << cs.control << std::endl; handleNewConnection(cs); } } return(0); } //******************************************************************** // handleNewConnection //******************************************************************** int Server::handleNewConnection(controlSock& cs) { int fd; int len; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(Port); addr.sin_addr.s_addr = INADDR_ANY; len = sizeof(struct sockaddr_in); if(( fd = accept(cs.control, (struct sockaddr *) &addr, (socklen_t *) &len)) < 0) { return(-1); } // Game's full, drop the connection if(getNumSockets() > Tablesize-10) { close(fd); return -1; } Socket *newSock; bool dnsDone = false; newSock = new Socket(fd, addr, dnsDone); sockets.push_back(newSock); newSock->showLoginScreen(dnsDone); return(0); } //******************************************************************** // processInput //******************************************************************** int Server::processInput() { foreach(Socket * sock, sockets) { if(sock->getState() == CON_DISCONNECTING) continue; // Clear out the descriptor of we have an exception if(FD_ISSET(sock->getFd(), &excSet)) { FD_CLR(sock->getFd(), &inSet); FD_CLR(sock->getFd(), &outSet); sock->setState(CON_DISCONNECTING); printf("Exception found\n"); continue; } // Try to read something if(FD_ISSET(sock->getFd(), &inSet)) { if(sock->processInput() != 0) { FD_CLR(sock->getFd(), &outSet); std::cout << "Error reading from socket " << sock->getFd() << std::endl; sock->setState(CON_DISCONNECTING); continue; } } } return(0); } //******************************************************************** // processCommands //******************************************************************** int Server::processCommands() { foreach(Socket * sock, sockets) { if(sock->hasCommand() && sock->getState() != CON_DISCONNECTING) { if(sock->processOneCommand() == -1) { sock->setState(CON_DISCONNECTING); continue; } } } return(0); } //******************************************************************** // processOutput //******************************************************************** int Server::processOutput() { foreach(Socket * sock, sockets) { if(FD_ISSET(sock->getFd(), &outSet) && sock->hasOutput()) { sock->flush(); } } return(0); } //******************************************************************** // deleteSocket //******************************************************************** // This should force a socket off right away int Server::deleteSocket(Socket* sock) { sock->setState(CON_DISCONNECTING); cleanUp(); return(0); } //******************************************************************** // disconnectAll //******************************************************************** void Server::disconnectAll() { foreach(Socket * sock, sockets) { // Set everyone to disconnecting sock->setState(CON_DISCONNECTING); } // And now clean them up cleanUp(); } //******************************************************************** // cleanUp //******************************************************************** int Server::cleanUp() { std::list<Socket*>::iterator it; Socket *sock=0; for(it = sockets.begin() ; it != sockets.end() ;) { sock = *it; if(sock->getState() == CON_DISCONNECTING) { // Flush any residual data sock->flush(); delete sock; it = sockets.erase(it); } else it++; } return(0); } //******************************************************************** // getDnsCacheString //******************************************************************** bstring Server::getDnsCacheString() { std::ostringstream dnsStr; int num = 0; dnsStr << "^cCached DNS Information\r\n"; dnsStr << "IP | Address\r\n"; dnsStr << "---------------------------------------------------------------\n"; dnsStr.setf(std::ios::left, std::ios::adjustfield); foreach(dnsCache & dns, cachedDns) { dnsStr << "^c" << std::setw(16) << dns.ip << " | ^C" << dns.hostName << "\n"; num++; } dnsStr << "\n\n^x Found " << num << " cached item(s).\n"; return(dnsStr.str()); } //******************************************************************** // getDnsCache //******************************************************************** bool Server::getDnsCache(bstring &ip, bstring &hostName) { foreach(dnsCache & dns, cachedDns) { if(dns.ip == ip) { // Got a match hostName = dns.hostName; printf("DNS: Found %s in dns cache\n", ip.c_str()); return(true); } } return(false); } //******************************************************************** // pruneDns //******************************************************************** void Server::pruneDns() { long fifteenDays = 60*60*24*15; long currentTime = time(0); std::list<dnsCache>::iterator it; std::list<dnsCache>::iterator oldIt; std::cout << "Pruning DNS\n"; for( it = cachedDns.begin(); it != cachedDns.end() ; ) { oldIt = it++; if(currentTime - (*oldIt).time >= fifteenDays) { cachedDns.erase(oldIt); } } saveDnsCache(); lastDnsPrune = currentTime; } //******************************************************************** // pulseTicks //******************************************************************** void Server::pulseTicks(long t) { std::list<Socket*>::iterator it; Player* player=0; for(it = sockets.begin() ; it != sockets.end() ; ) { player = (*it)->getPlayer(); if(player) { player->pulseTick(t); if(!player) { it = sockets.erase(it); continue; } } it++; } } //********************************************************************* // updateUsers //********************************************************************* void Server::updateUsers(long t) { int tout = 300; lastUserUpdate = t; Player* player=0; foreach(Socket * sock, sockets) { player = sock->getPlayer(); if(player) { if(player->isDm()) tout = t; else if(player->isStaff()) tout = 1200; else tout = 600; } if(t - sock->ltime > tout) { sock->write("\n\rTimed out.\n\r"); sock->setState(CON_DISCONNECTING); } if(player) { player->checkOutlawAggro(); player->update(); } } } //******************************************************************** // update_random //******************************************************************** // This function checks all player-occupied rooms to see if random monsters // have entered them. If it is determined that random monster should enter // a room, it is loaded and items it is carrying will be loaded with it. void Server::updateRandom(long t) { Monster* monster=0; BaseRoom* room=0; Room* uRoom=0; AreaRoom* aRoom=0; WanderInfo* wander=0; CatRef cr; int num=0, l=0; std::map<bstring, bool> check; //Object *object; //int k, numout=0, alnum=0, x=0; lastRandomUpdate = t; Player* player; foreach(Socket * sock, sockets) { player = sock->getPlayer(); if(!player || !player->getRoom()) continue; uRoom = player->parent_rom; aRoom = player->area_room; room = player->getRoom(); wander = 0; if(uRoom) { // handle monsters arriving in unique rooms if(!uRoom->info.id) continue; if(check.find(uRoom->info.str()) != check.end()) continue; check[uRoom->info.str()] = true; wander = &uRoom->wander; } else { // handle monsters arriving in area rooms if(check.find(aRoom->mapmarker.str()) != check.end()) continue; if(aRoom->unique.id) continue; check[aRoom->mapmarker.str()] = true; wander = aRoom->getRandomWanderInfo(); } if(!wander) continue; if(mrand(1,100) > wander->getTraffic()) continue; cr = wander->getRandom(); if(!cr.id) continue; if(room->countCrt() >= room->getMaxMobs()) continue; // Will make mobs not spawn if a DM is invis in the room. -TC if(!room->countVisPly()) continue; if(!loadMonster(cr, &monster)) continue; // if the monster cant go there, they wont wander there if(aRoom && !aRoom->area->canPass(monster, &aRoom->mapmarker, true)) { free_crt(monster); continue; } if(!monster->flagIsSet(M_CUSTOM)) monster->validateAc(); if((monster->flagIsSet(M_NIGHT_ONLY) && isDay()) ||(monster->flagIsSet(M_DAY_ONLY) && !isDay())) { free_crt(monster); continue; } if(room->flagIsSet(R_PLAYER_DEPENDENT_WANDER)) num = mrand(1, room->countVisPly()); else if(monster->getNumWander() > 1) num = mrand(1, monster->getNumWander()); else num = 1; for(l=0; l<num; l++) { monster->initMonster(); if(monster->flagIsSet(M_PERMENANT_MONSTER)) monster->clearFlag(M_PERMENANT_MONSTER); if(!l) monster->addToRoom(room, num); else monster->addToRoom(room, 0); if(!monster->flagIsSet(M_PERMENANT_MONSTER) || monster->flagIsSet(M_NO_ADJUST)) monster->adjust(-1); gServer->addActive(monster); if(l != num-1) loadMonster(cr, &monster); } } } //********************************************************************* // update_active //********************************************************************* // This function updates the activities of all monsters who are currently // active (ie. monsters on the active list). Usually this is reserved // for monsters in rooms that are occupied by players. void Server::updateActive(long t) { Player *ply=0; Creature *target=0; Monster *monster=0; BaseRoom* room=0; ctag *cp = 0, *imp=0, *ap=0; long tt = gConfig->currentHour(); int timetowander=0, immort=0; lastActiveUpdate = t; if(!(cp = first_active)) return; while(cp) { ASSERTLOG( cp->crt ); monster = cp->crt->getMonster(); // Better be a monster to be on the active list ASSERTLOG(monster); if(!monster->parent_rom && !monster->area_room) { broadcast(isStaff, "^y%s without a parent/area room on the active list. Info: %s. Deleting.", monster->name, monster->info.str().c_str()); monster->deleteFromRoom(); gServer->delActive(monster); free_crt(monster); cp = first_active; continue; } room = monster->getRoom(); // Reset's mob's smack-talking broadcasts at 7am every day if(tt == 7 && ((t - last_time_update) / 2) == 0) monster->daily[DL_BROAD].cur = 20; if( (monster->flagIsSet(M_NIGHT_ONLY) && isDay()) || (monster->flagIsSet(M_DAY_ONLY) && !isDay()) ) { imp = room->first_ply; while(imp) { if(imp->crt->isStaff()) { immort = 1; break; } imp = imp->next_tag; } if(!immort) { timetowander=1; broadcast(NULL, monster->getRoom(), "%M wanders slowly away.", monster); monster->deleteFromRoom(); gServer->delActive(monster); free_crt(monster); cp = first_active; continue; } } // fast wanderers and pets always stay active if( !room->first_ply && !monster->flagIsSet(M_ALWAYS_ACTIVE) && !monster->flagIsSet(M_FAST_WANDER) && !monster->flagIsSet(M_FAST_TICK) && !monster->flagIsSet(M_REGENERATES) && !monster->isPet() && !monster->isPoisoned() && !monster->isEffected("slow") && !monster->flagIsSet(M_PERMENANT_MONSTER) && !monster->flagIsSet(M_AGGRESSIVE)) { gServer->delActive(monster); cp = first_active; continue; } // Lets see if we'll attack any other monsters in this room if(monster->checkEnemyMobs()) { cp = cp->next_tag; continue; } if(monster->flagIsSet(M_KILL_PERMS)) { ap = room->first_mon; while(ap) { if(ap->crt == monster) { ap = ap->next_tag; continue; } if(ap->crt->flagIsSet(M_PERMENANT_MONSTER) && !monster->willAssist(ap->crt->getMonster()) && !monster->isEnmCrt(ap->crt->name)) monster->addEnmCrt(ap->crt); ap = ap->next_tag; } } if(monster->flagIsSet(M_KILL_NON_ASSIST_MOBS)) { ap = room->first_mon; while(ap) { if(ap->crt == monster) { ap = ap->next_tag; continue; } if(!monster->willAssist(ap->crt->getMonster()) && !ap->crt->flagIsSet(M_PERMENANT_MONSTER) && !monster->isEnmCrt(ap->crt->name)) monster->addEnmCrt(ap->crt); ap = ap->next_tag; } } monster->checkAssist(); monster->pulseTick(t); monster->checkSpellWearoff(); if(monster->isPoisoned() && !monster->immuneToPoison()) { if(monster->spellIsKnown(S_CURE_POISON) && monster->mp.getCur() >=6 && (mrand(1,100) < (30+monster->intelligence.getCur()/10))) { broadcast(NULL, monster->getRoom(), "%M casts a curepoison spell on %sself.", monster, monster->himHer()); monster->mp.decrease(6); monster->curePoison(); cp = cp->next_tag; continue; } } if(!monster->checkAttackTimer(false)) { cp = cp->next_tag; continue; } if(t > LT(monster, LT_CHARMED) && monster->flagIsSet(M_CHARMED)) monster->clearFlag(M_CHARMED); if(monster->doHarmfulAuras()) { cp = first_active; continue; } // Calls beneficial casting routines for mobs. if(monster->flagIsSet(M_BENEVOLENT_SPELLCASTER)) monster->beneficialCaster(); if(monster->petCaster()) { cp = cp->next_tag; continue; } if(monster->getPrimeFaction() != "") Faction::worshipSocial(monster); // summoned monsters expire here if( monster->isPet() && (t > LT(monster, LT_INVOKE) || t > LT(monster, LT_ANIMATE)) ) { if(monster->isUndead()) broadcast(NULL, room, "%1M wanders away.", monster); else broadcast(NULL, room, "%1M fades away.", monster); monster->die(monster->following); gServer->delActive(monster); cp = first_active; continue; } monster->updateAttackTimer(); if(monster->dexterity.getCur() > 200 || monster->isEffected("haste")) monster->modifyAttackDelay(-10); if(monster->isEffected("slow")) monster->modifyAttackDelay(10); if(monster->flagIsSet(M_WILL_WIELD)) monster->mobWield(); else monster->clearFlag(M_WILL_WIELD); // Grab some stuff from the room monster->checkScavange(t); // See if we can wander around or away int mobileResult = monster->checkWander(t); if(mobileResult == 1) { cp = cp->next_tag; continue; } if(mobileResult == 2) { monster->deleteFromRoom(); gServer->delActive(monster); free_crt(monster); cp = first_active; continue; } // Steal from people if(monster->flagIsSet(M_STEAL_ALWAYS) && (t - monster->lasttime[LT_STEAL].ltime) > 60 && mrand(1, 100) <= 5) { ply = lowest_piety(room, monster->isEffected("detect-invisible")); if(ply) monster->steal(ply); } // Try to death scream if( monster->flagIsSet(M_DEATH_SCREAM) && monster->first_enm && monster->nearEnemy() && !monster->flagIsSet(M_CHARMED) && monster->canSpeak() && monster->mobDeathScream() ) { cp = first_active; continue; } // Update combat here if( monster->first_enm && !timetowander && monster->updateCombat()) { cp = first_active; continue; } if( !monster->flagIsSet(M_AGGRESSIVE) && monster->flagIsSet(M_WILL_BE_AGGRESSIVE) && !monster->first_enm && mrand(1,100) <= monster->getUpdateAggro() && room->countVisPly() ) { //broadcast(isDm, "^g%M(L%d,R:%s) just became aggressive!", monster, monster->getLevel(), room->fullName().c_str()); monster->setFlag(M_AGGRESSIVE); } target = monster->whoToAggro(); if(target) { if( !monster->flagIsSet(M_HIDDEN) && !(monster->isInvisible() && target->isEffected("detect-invisible")) ) { if(monster->aggroString[0]) broadcast(NULL, target->getRoom(), "%M says, \"%s.\"", monster,monster->aggroString); target->printColor("^r%M attacks you.\n", monster); broadcast(target->getSock(), target->getRoom(), "%M attacks %N.", monster, target); } monster->updateAttackTimer(true, DEFAULT_WEAPON_DELAY); monster->addEnmCrt(target); if(target->flagIsSet(P_LAG_PROTECTION_SET)) target->setFlag(P_LAG_PROTECTION_ACTIVE); } cp = cp->next_tag; } } //-------------------------------------------------------------------- // Active list manipulation //********************************************************************* // add_active //********************************************************************* // This function adds a monster to the active-monster list. A pointer // to the monster is passed in the first parameter. void Server::addActive(Monster* monster) { ctag *ct; if(!monster) { loga("add_active: tried to activate a NULL crt!\n"); return; } if(isActive(monster)) return; // try and guess if the creature is valid { char *p = monster->name; while(*p) { ASSERTLOG(isPrintable((unsigned char)*p)); p++; } ASSERTLOG(p - monster->name); } ct = 0; ct = new ctag; if(!ct) merror("add_active", FATAL); ct->crt = monster; ct->next_tag = 0; if(!first_active) first_active = ct; else { ct->next_tag = first_active; first_active = ct; } } //********************************************************************* // del_active //********************************************************************* // This function removes a monster from the active-monster list. The // parameter contains a pointer to the monster which is to be removed void Server::delActive(Monster* monster) { if(!this) return; ctag *cp, *prev; if(!(cp = first_active)) return; if(!(isActive(monster))) return; if(cp->crt == monster) { first_active = cp->next_tag; delete cp; return; } prev = cp; cp = cp->next_tag; while(cp) { if(cp->crt == monster) { prev->next_tag = cp->next_tag; delete cp; return; } prev = cp; cp = cp->next_tag; } } //********************************************************************* // isActive //********************************************************************* // This function returns 1 if the parameter passed is in the // active list. bool Server::isActive(Monster* monster) { ctag *cp=0; if(!(cp = first_active)) return(false); while(cp) { if(cp->crt == monster) { return(true); } cp = cp->next_tag; } return(false); } //******************************************************************** // getFirstActive //******************************************************************** const ctag* Server::getFirstActive() { return(first_active); } // End - Active List Manipulation //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Children Control //******************************************************************** // childDied //******************************************************************** void Server::childDied() { Deadchildren++; } //******************************************************************** // getDeadChildren //******************************************************************** int Server::getDeadChildren() const { return(Deadchildren); } //******************************************************************** // reapChildren //******************************************************************** int Server::reapChildren() { int pid, status; bool dnsChild = false; childProcess c; bool found=false; while(Deadchildren > 0) { pid = waitpid(-1, &status, WNOHANG); if(pid <= 0) { Deadchildren--; return(-1); } std::list<childProcess>::iterator it; for( it = children.begin(); it != children.end();) { if((*it).pid == pid) { if((*it).type == CHILD_DNS_RESOLVER) { char tmpBuf[1024]; memset(tmpBuf, '\0', sizeof(tmpBuf)); // Read in the results from the resolver int n = read((*it).fd, tmpBuf, 1023); // Close the read fd in the pipe now, won't need it anymore close((*it).fd); // If we have an error reading, just use the ip address then if( n <= 0 ) { if(errno == EWOULDBLOCK) printf("DNS ReapChildren: Error would block\n"); else printf("DNS ReapChildren: Error\n"); strcpy(tmpBuf, (*it).extra.c_str()); } // Add dns to cache addCache((*it).extra, tmpBuf); dnsChild = true; // Now we want to look through all connected sockets and update dns where appropriate foreach(Socket * sock, sockets) { if(sock->getState() == LOGIN_DNS_LOOKUP && sock->getIp() == (*it).extra) { // Be sure to set the hostname first, then check for lockout sock->setHostname(tmpBuf); sock->checkLockOut(); } } printf("Reaped DNS child (%d-%s).\n", pid, tmpBuf); } else if((*it).type == CHILD_LISTER) { printf("Reaping LISTER child (%d-%s)\n", pid, (*it).extra.c_str()); processListOutput(*it); // Don't forget to close the pipe! close((*it).fd); } else if((*it).type == CHILD_FIND_SPACE) { printf("Reaping FindSpace Find child (%d-%s)\n", pid, (*it).extra.c_str()); bstring result = gServer->simpleChildRead(*it); if(result.getLength()) gServer->isAvailableSpace((*it).extra, result != "0"); // Don't forget to close the pipe! close((*it).fd); } else if((*it).type == CHILD_MOVEROOM_FIND) { printf("Reaping MoveRoom Find child (%d-%s)\n", pid, (*it).extra.c_str()); c = *it; found = true; } else if((*it).type == CHILD_MOVEROOM_FINISH) { printf("Reaping MoveRoom Finish child (%d-%s)\n", pid, (*it).extra.c_str()); c = *it; found = true; } else { printf("ReapChildren: Unknown child type %d\n", (*it).type); } // // Child was processed, reduce number of children // Deadchildren--; // printf("Reaped Child: %d dead children left.\n", Deadchildren); it = children.erase(it); // finish moveRoom after they've been deleted from the list if(found) { found = false; if(c.type == CHILD_MOVEROOM_FIND) { gConfig->findNextEmpty(c, true); } else if(c.type == CHILD_MOVEROOM_FINISH) { gConfig->offlineMoveRoom(c, true); } // Don't forget to close the pipe! close(c.fd); } continue; } else { it++; continue; } } Deadchildren--; } if(dnsChild) saveDnsCache(); // just in case, kill off any zombies wait3(&status, WNOHANG, (struct rusage *)0); return(0); } //******************************************************************** // processListOutput //******************************************************************** int Server::processListOutput(childProcess &lister) { bool found = false; std::list<Socket*>::iterator sIt; Socket *sock; for(sIt = sockets.begin() ; sIt != sockets.end() ; ++sIt ) { sock = *sIt; if(sock->getPlayer() && lister.extra == sock->getPlayer()->name) { found = true; break; } } char tmpBuf[4096]; bstring toWrite; int n; for(;;) { // Even if no socket is found, read in all of the data memset(tmpBuf, '\0', sizeof(tmpBuf)); n = read(lister.fd, tmpBuf, sizeof(tmpBuf)-1); if(n <= 0) break; if(found) { toWrite = tmpBuf; toWrite.Replace("\n", "\nList> "); sock->write(toWrite, false); } } return(1); } //******************************************************************** // processChildren //******************************************************************** int Server::processChildren(void) { foreach(childProcess & child, children) { if(child.type == CHILD_DNS_RESOLVER) { // Ignore, will be handled by reapChildren } else if(child.type == CHILD_LISTER) { processListOutput(child); } else if(child.type == CHILD_MOVEROOM_FIND) { gConfig->findNextEmpty(child, false); } else if(child.type == CHILD_MOVEROOM_FINISH) { gConfig->offlineMoveRoom(child, false); } else if(child.type == CHILD_FIND_SPACE) { bstring result = gServer->simpleChildRead(child); if(result.getLength()) gServer->isAvailableSpace(child.extra, result != "0"); } else { printf("processChildren: Unknown child type %d\n", child.type); } } return(1); } //******************************************************************** // runList //******************************************************************** int Server::runList(Socket* sock, cmd* cmnd) { int fds[2]; int pid; // We're going to make a pipe here. // The child process will write the if(pipe(fds) == -1) { printf("LIST: Error with pipe!\n"); abort(); } pid = fork(); if(!pid) { // Child Process // Close the reading end, we'll only be writing close(fds[0]); bstring lister = BINPATH; lister += "list.exe"; std::cout << "Running <" << lister << ">\n"; // Remap stdout to fds[1] so cout will print to fds[1] and we can // read it in from the mud if(dup2(fds[1], STDOUT_FILENO) != STDOUT_FILENO) { std::cout << "LIST: Error with dup2.\n"; return(0); } execl(lister.c_str(), lister.c_str(), cmnd->str[1], cmnd->str[2], cmnd->str[3], cmnd->str[4], NULL); printf("Error!"); exit(0); } else { // pid != 0 // Parent Process // Close the writing end, we'll only be reading close(fds[1]); nonBlock(fds[0]); std::cout << "Watching Child Lister for(" << sock->getPlayer()->name << ") running with pid " << pid << " reading from fd " << fds[0] << std::endl; // Let the server know we're monitoring this child process addChild(pid, CHILD_LISTER, fds[0], sock->getPlayer()->name); } return(0); } //******************************************************************** // startDnsLookup //******************************************************************** int Server::startDnsLookup(Socket* sock, struct sockaddr_in addr) { int fds[2]; int pid; // We're going to make a pipe here. // The child process will write the if(pipe(fds) == -1) { printf("DNS: Error with pipe!\n"); abort(); } pid = fork(); if(!pid) { // Child Process // Close the reading end, we'll only be writing close(fds[0]); struct hostent *he = 0; int tries = 0; while(tries < 5 && tries >= 0) { he = gethostbyaddr((char *)&addr.sin_addr.s_addr, sizeof(addr.sin_addr.s_addr), AF_INET); if(he == NULL) { switch(h_errno) { case HOST_NOT_FOUND: printf("DNS Error: Host not found for %s.\n", sock->getIp().c_str()); tries = -1; break; case NO_RECOVERY: printf("DNS Error: Unrecoverable error for %s.\n", sock->getIp().c_str()); tries = -1; break; case TRY_AGAIN: printf("DNS Error: Try again for %s.\n", sock->getIp().c_str()); tries++; break; } } else break; } printf("DNS: Resolver finished for %s(%s).\n", sock->getIp().c_str(), he ? he->h_name : sock->getIp().c_str()); if(he) { // Found a hostname so print it write(fds[1], he->h_name, strlen(he->h_name)); } else { // Didn't find a hostname, print the ip write(fds[1], sock->getIp().c_str(), sock->getIp().length()); } exit(0); } else { // pid != 0 // Parent Process // Close the writing end, we'll only be reading close(fds[1]); std::cout << "Watching Child DNS Resolver for(" << sock->getIp() << ") running with pid " << pid << " reading from fd " << fds[0] << std::endl; // Let the server know we're monitoring this child process addChild(pid, CHILD_DNS_RESOLVER, fds[0], sock->getIp()); } return(0); } //******************************************************************** // addCache //******************************************************************** void Server::addCache(bstring ip, bstring hostName, time_t t) { if(t == -1) t = time(0); cachedDns.push_back(dnsCache(ip, hostName, t)); } //******************************************************************** // addChild //******************************************************************** void Server::addChild(int pid, childType pType, int pFd, bstring pExtra) { printf("Adding pid %d as child type %d, watching fd %d\n", pid, pType, pFd); children.push_back(childProcess(pid, pType, pFd, pExtra)); } // End - Children Control //-------------------------------------------------------------------- //******************************************************************** // saveDnsCache //******************************************************************** void Server::saveDnsCache() { xmlDocPtr xmlDoc; xmlNodePtr rootNode; xmlNodePtr curNode; char filename[80]; xmlDoc = xmlNewDoc(BAD_CAST "1.0"); rootNode = xmlNewDocNode(xmlDoc, NULL, BAD_CAST "DnsCache", NULL); xmlDocSetRootElement(xmlDoc, rootNode); std::list<dnsCache>::iterator it; for( it = cachedDns.begin(); it != cachedDns.end() ; it++) { curNode = xmlNewChild(rootNode, NULL, BAD_CAST"Dns", NULL); xml::newStringChild(curNode, "Ip", (*it).ip.c_str()); xml::newStringChild(curNode, "HostName", (*it).hostName.c_str()); xml::newNumChild(curNode, "Time", (long)(*it).time); } sprintf(filename, "%s/dns.xml", CONFPATH); xml::saveFile(filename, xmlDoc); xmlFreeDoc(xmlDoc); } //******************************************************************** // loadDnsCache //******************************************************************** void Server::loadDnsCache() { xmlDocPtr xmlDoc; xmlNodePtr curNode, childNode; xmlDoc = xml::loadFile(CONFPATH "dns.xml", "DnsCache"); if(xmlDoc == NULL) return; curNode = xmlDocGetRootElement(xmlDoc); curNode = curNode->children; while(curNode && xmlIsBlankNode(curNode)) { curNode = curNode->next; } if(curNode == 0) { xmlFreeDoc(xmlDoc); return; } while(curNode != NULL) { if(NODE_NAME(curNode, "Dns")) { bstring ip, hostname; long time=0; childNode = curNode->children; while(childNode != NULL) { if(NODE_NAME(childNode, "Ip")) { xml::copyToBString(ip, childNode); } else if(NODE_NAME(childNode, "HostName")) { xml::copyToBString(hostname, childNode); } else if(NODE_NAME(childNode, "Time")) { xml::copyToNum(time, childNode); addCache(ip, hostname, time); } childNode = childNode->next; } } curNode = curNode->next; } xmlFreeDoc(xmlDoc); xmlCleanupParser(); } //-------------------------------------------------------------------- // Reboot Functions //******************************************************************** // resetShipsFile //******************************************************************** void Config::resetShipsFile() { // copying ships.midnight.xml to ships.xml char sfile1[80], sfile2[80], command[255]; sprintf(sfile1, "%s/ships.midnight.xml", CONFPATH); sprintf(sfile2, "%s/ships.xml", CONFPATH); sprintf(command, "cp %s %s", sfile1, sfile2); system(command); } //******************************************************************** // startReboot //******************************************************************** bool Server::startReboot(bool resetShips) { gConfig->save(); gServer->setRebooting(); // First give all players a free restore foreach(Socket *sock , sockets) { Player* player = sock->getPlayer(); if(player && player->fd > -1) { player->hp.restore(); player->mp.restore(); } } // Then run through and save the reboot file saveRebootFile(resetShips); // Now disconnect people that won't make it through the reboot foreach(Socket *sock , sockets) { Player* player = sock->getPlayer(); if(player && player->fd > -1 ) { // End the compression, we'll try to restart it after the reboot if(sock->getMccp()) { sock->endCompress(); } player->save(true); players[player->name] = 0; player->uninit(); free_crt(player); player = 0; sock->setPlayer(0); } else { sock->write("\n\r\n\r\n\rSorry, we are rebooting. You may reconnect in a few seconds.\n\r"); sock->disconnect(); } } processOutput(); cleanUp(); if(resetShips) gConfig->resetShipsFile(); char port[10], path[80]; sprintf(port, "%d", Port); strcpy(path, gConfig->cmdline); execl(path, path, "-r", port, (char *)NULL); unlink(CONFPATH "config.xml"); merror("dmReboot failed!!!", FATAL); return(0); } //******************************************************************** // saveRebootFile //******************************************************************** bool Server::saveRebootFile(bool resetShips) { xmlDocPtr xmlDoc; xmlNodePtr rootNode; xmlNodePtr serverNode; xmlNodePtr curNode; xmlDoc = xmlNewDoc(BAD_CAST "1.0"); rootNode = xmlNewDocNode(xmlDoc, NULL, BAD_CAST "Reboot", NULL); xmlDocSetRootElement(xmlDoc, rootNode); // Save server information serverNode = xmlNewChild(rootNode, NULL, BAD_CAST"Server", NULL); // Dump control socket information std::list<controlSock>::const_iterator it1; for( it1 = controlSocks.begin(); it1 != controlSocks.end(); it1++ ) { curNode = xmlNewChild(serverNode, NULL, BAD_CAST"ControlSock", NULL); xml::newNumProp(curNode, "Port", (*it1).port); xml::newNumProp(curNode, "Control", (*it1).control); } xml::newNumChild(serverNode, "StartTime", StartTime); xml::newNumChild(serverNode, "LastWeatherUpdate", last_weather_update); xml::newNumChild(serverNode, "LastRandomUpdate", lastRandomUpdate); xml::newNumChild(serverNode, "LastTimeUpdate", last_time_update); xml::newNumChild(serverNode, "InBytes", InBytes); xml::newNumChild(serverNode, "OutBytes", OutBytes); if(resetShips) xml::newStringChild(serverNode, "ResetShips", "true"); foreach(Socket *sock , sockets) { Player*player = sock->getPlayer(); if(player && player->fd > -1) { curNode = xmlNewChild(rootNode, NULL, BAD_CAST"Player", NULL); xml::newStringChild(curNode, "Name", player->name); xml::newNumChild(curNode, "Fd", sock->getFd()); xml::newStringChild(curNode, "Ip", sock->getIp().c_str()); xml::newStringChild(curNode, "HostName", sock->getHostname().c_str()); } } xml::saveFile(REBOOTFILE, xmlDoc); xmlFreeDoc(xmlDoc); return(true); } //******************************************************************** // finishReboot //******************************************************************** int Server::finishReboot() { xmlDocPtr doc; xmlNodePtr curNode; xmlNodePtr childNode; bool resetShips = false; std::cout << "Running finishReboot()" << std::endl; // We are rebooting rebooting = true; // build an XML tree from a the file doc = xml::loadFile(REBOOTFILE, "Reboot"); unlink(REBOOTFILE); if(doc == NULL) { printf("Unable to load reboot file\n"); merror("Loading reboot file", FATAL); } curNode = xmlDocGetRootElement(doc); curNode = curNode->children; while(curNode && xmlIsBlankNode(curNode)) { curNode = curNode->next; } if(curNode == 0) { xmlFreeDoc(doc); merror("Parsing reboot file", FATAL); } Numplayers = 0; while(curNode != NULL) { if(NODE_NAME(curNode, "Server")) { childNode = curNode->children; while(childNode != NULL) { if(NODE_NAME(childNode, "ControlSock")) { int port = xml::getIntProp(childNode, "Port"); int control = xml::getIntProp(childNode, "Control"); controlSocks.push_back(controlSock(port, control)); running = true; } else if(NODE_NAME(childNode, "StartTime")) xml::copyToNum(StartTime, childNode); else if(NODE_NAME(childNode, "LastWeatherUpdate")) xml::copyToNum(last_weather_update, childNode); else if(NODE_NAME(childNode, "LastRandomUpdate")) xml::copyToNum(lastRandomUpdate, childNode); else if(NODE_NAME(childNode, "LastTimeUpdate")) xml::copyToNum(last_time_update, childNode); else if(NODE_NAME(childNode, "InBytes")) xml::copyToNum(InBytes, childNode); else if(NODE_NAME(childNode, "OutBytes")) xml::copyToNum(OutBytes, childNode); else if(NODE_NAME(childNode, "ResetShips")) resetShips = true; //xml::copyToBool(resetShips, childNode); childNode = childNode->next; } } else if(NODE_NAME(curNode, "Player")) { childNode = curNode->children; Player* player=0; Socket* sock=0; while(childNode != NULL) { if(NODE_NAME(childNode, "Name")) { bstring name; xml::copyToBString(name, childNode); if(!loadPlayer(name.c_str(), &player)) { merror("finishReboot: loadPlayer", FATAL); } } else if(NODE_NAME(childNode, "Fd")) { int fd; xml::copyToNum(fd, childNode); sock = new Socket(fd); if(!player || !sock) merror("finishReboot: No Sock/Player", FATAL); player->fd = fd; sock->setPlayer(player); player->setSock(sock); addPlayer(player); } else if(NODE_NAME(childNode, "Ip")) { bstring ip; xml::copyToBString(ip, childNode); sock->setIp(ip); } else if(NODE_NAME(childNode, "HostName")) { bstring host; xml::copyToBString(host, childNode); sock->setHostname(host); } childNode = childNode->next; } if(!player || !sock) merror("finishReboot: Finished, still no Sock/Player", FATAL); sock->ltime = time(0); sock->print("The world comes back into focus!\n"); player->init(); if(player->isDm()) { sock->getPlayer()->print("Now running on version %s.\n", VERSION); if(resetShips) sock->getPlayer()->print("Time has been moved back %d hour%s.\n", gConfig->currentHour(), gConfig->currentHour() != 1 ? "s" : ""); } sock->intrpt = 1; sock->setState(CON_PLAYING); sockets.push_back(sock); Numplayers++; } curNode = curNode->next; } xmlFreeDoc(doc); xmlCleanupParser(); StartTime = time(0); if(resetShips) gConfig->calendar->resetToMidnight(); else gConfig->calendar->shipUpdates = gConfig->calendar->shipUpdates % 60; gConfig->resetMinutes(); // Done rebooting rebooting = false; return(true); } // End - Reboot Functions //-------------------------------------------------------------------- // ------------------------------------------------------------------- // Player Functions //******************************************************************** // findPlayer //******************************************************************** Player* Server::findPlayer(bstring name) { std::map<bstring, Player*>::const_iterator it = players.find(name); if(it != players.end()) return((*it).second); return(0); } //********************************************************************* // clearPlayer //********************************************************************* // This will NOT free up the player, it will just remove them from the list bool Server::clearPlayer(bstring name) { players.erase(name); return(true); } //********************************************************************* // addPlayer //********************************************************************* bool Server::addPlayer(Player* player) { ASSERTLOG(player); players[player->name] = player; player->getSock()->addToPlayerList(); return(true); } //********************************************************************* // checkDuplicateName //********************************************************************* bool Server::checkDuplicateName(Socket* sock, bool dis) { foreach(Socket *s, sockets) { if(sock != s && s->getPlayer() && !strcmp(s->getPlayer()->name, sock->getPlayer()->name)) { if(!dis) { sock->printColor("\n\n^ySorry, that character is already logged in.^x\n\n\n"); sock->reconnect(); } else { s->disconnect(); } return(true); } } return(false); } //********************************************************************* // checkDouble //********************************************************************* // returning true will disconnect the connecting socket (sock) bool Server::checkDouble(Socket* sock) { if(!gConfig->checkDouble) return(false); if(sock->getPlayer()->flagIsSet(P_ON_PROXY) || sock->getPlayer()->isCt()) return(false); if(strstr(sock->getHostname().c_str(), "localhost")) return(false); Player* player=0; foreach(Socket *s, sockets) { player = s->getPlayer(); if(!player || s == sock) continue; if(player->isCt()) continue; if(player->flagIsSet(P_LINKDEAD) || player->flagIsSet(P_ON_PROXY)) continue; if(sock->getIp() != s->getIp()) continue; if(gConfig->canDoubleLog(sock->getPlayer()->getForum(), s->getPlayer()->getForum())) continue; s->printColor("^Y\n\nAnother character (%s) from your IP address has just logged in.\n\n", sock->getPlayer()->name); s->disconnect(); return(false); } return(false); } //********************************************************************* // sendCrash //********************************************************************* void Server::sendCrash() { foreach(Socket *sock , sockets) { viewLoginFile(sock, CONFPATH "crash.txt"); } } //********************************************************************* // getTimeZone //********************************************************************* // not accurate for the fractional hour timezones bstring Server::getTimeZone() { // current local time time_t curr = time(0); // convert curr to GMT, store as tm tm local = *gmtime(&curr); // convert GMT tm to GMT time_t time_t utc = mktime(&local); // difference in hours int tz = (int)(difftime(utc,curr) / -3600); switch(tz) { case -12: return("International Date Line West"); case -11: return("Midway Island, Samoa"); case -10: return("Hawaii"); case -9: return("Alaska"); case -8: return("Pacific"); case -7: return("Mountain"); case -6: return("Central"); case -5: return("Eastern"); case -4: return("Atlantic"); case -3: return("Brasilia, Buenos Aires, Georgetown, Greenland"); case -2: return("Mid-Atlantic"); case -1: return("Azores, Cape verde Is."); case 0: return("Greenwich Mean Time"); case 1: return("Berlin, Rome, Prague, Warsaw, , West Central Africa"); case 2: return("Athens, Minsk, Cairo, Jerusalem"); case 3: return("Baghdad, Moscow, Nairobi"); case 4: return("Abu Dhabi, Tbilsi"); case 5: return("Islamabad, Karachi, Tashkent"); case 6: return("Almaty, Dhaka, Sri Jayawardenepura"); case 7: return("Bangkok, Jakarta, Krasnoyarsk"); case 8: return("Beijing, Hong Kong, Singapore, Taipei"); case 9: return("Osaka, Tokyo, Seoul");; case 10: return("Melbourne, Sydney, Guam, Vladivostok"); case 11: return("Magadan, Solomon Is., New Caledonia"); case 12: return("Auckland, , Fiji, Marshall Is."); case 13: return("Nuku'alofa"); default: return("Unknown"); } } int Server::getNumPlayers() { std::pair<bstring, Player*> p; int numPlayers=0; Player* target=0; foreach(p, players) { target = p.second; if(!target->isConnected()) continue; // if(target->isStaff()) // immorts++; if(!target->isStaff()) numPlayers++; } return(numPlayers); }