/*
* io.cpp
* Socket input/output/establishment functions.
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* 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-2012 Jason Mitchell, Randi Mitchell
* Contributions by Tim Callahan, Jonathan Hseu
* Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
*
*/
#include <arpa/telnet.h>
#include "mud.h"
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h> // Needs: htons, htonl, INADDR_ANY, sockaddr_in
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include "login.h"
#include "socket.h"
#include "timer.h"
#include "clans.h"
#include <sstream>
#define TELOPT_COMPRESS2 86
#define buflen(a,b,c) (a-b + (a<b ? c:0))
#define saddr addr.sin_addr.s_addr
#define MAXPLAYERS 100
int Numplayers;
int controlSock;
extern unsigned short Port;
//********************************************************************
// broadcast
//********************************************************************
bool hearBroadcast(Creature* target, Socket* ignore1, Socket* ignore2, bool showTo(Socket*)) {
if(!target)
return(false);
if(target < 0)
return(false);
if(target->getSock() == ignore1 || target->getSock() == ignore2)
return(false);
if(showTo && !showTo(target->getSock()))
return(false);
Player* pTarget = target->getAsPlayer();
if(pTarget) {
if( ignore1 != NULL &&
ignore1->getPlayer() &&
pTarget->isGagging(ignore1->getPlayer()->getName())
)
return(false);
if( ignore2 != NULL &&
ignore2->getPlayer() &&
pTarget->isGagging(ignore2->getPlayer()->getName())
)
return(false);
}
return(true);
}
// global broadcast
void doBroadCast(bool showTo(Socket*), bool showAlso(Socket*), const char *fmt, va_list ap, Creature* player) {
for(Socket* sock : gServer->sockets) {
const Player* ply = sock->getPlayer();
if(!ply)
continue;
if(ply->fd < 0)
continue;
if(!showTo(sock))
continue;
if(showAlso && !showAlso(sock))
continue;
// No gagging staff!
if(player && ply->isGagging(player->getName()) && !player->isCt())
continue;
ply->vprint(ply->customColorize(fmt).c_str(), ap);
ply->printColor("^x\n");
}
}
// room broadcast
void doBroadcast(bool showTo(Socket*), Socket* ignore1, Socket* ignore2, const Container* container, const char *fmt, va_list ap) {
if(!container)
return;
for(Player* ply : container->players) {
if(!hearBroadcast(ply, ignore1, ignore2, showTo))
continue;
if(ply->flagIsSet(P_UNCONSCIOUS))
continue;
ply->vprint(ply->customColorize(fmt).c_str(), ap);
ply->printColor("^x\n");
}
}
// room broadcast, 1 ignore
void broadcast(Socket* ignore, const Container* container, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
doBroadcast(0, ignore, NULL, container, fmt, ap);
va_end(ap);
}
// room broadcast, 2 ignores
void broadcast(Socket* ignore1, Socket* ignore2, const Container* container, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
doBroadcast(0, ignore1, ignore2, container, fmt, ap);
va_end(ap);
}
// room broadcast, 1 ignore, showTo function
void broadcast(bool showTo(Socket*), Socket* ignore, const Container* container, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
doBroadcast(showTo, ignore, NULL, container, fmt, ap);
va_end(ap);
}
// simple broadcast
void broadcast(const char *fmt,...) {
va_list ap;
va_start(ap, fmt);
doBroadCast(yes, 0, fmt, ap);
va_end(ap);
}
// broadcast with showTo function
void broadcast(bool showTo(Socket*), bool showAlso(Socket*), const char *fmt,...) {
ASSERTLOG(showTo);
va_list ap;
va_start(ap, fmt);
doBroadCast(showTo, showAlso, fmt, ap);
va_end(ap);
}
// broadcast with showTo function
void broadcast(bool showTo(Socket*), const char *fmt,...) {
ASSERTLOG(showTo);
va_list ap;
va_start(ap, fmt);
doBroadCast(showTo, 0, fmt, ap);
va_end(ap);
}
void broadcast(Creature* player, bool showTo(Socket*), int color, const char *fmt,...) {
ASSERTLOG(showTo);
va_list ap;
va_start(ap, fmt);
doBroadCast(showTo, (bool(*)(Socket*))NULL, fmt, ap, player);
va_end(ap);
}
bool yes(Creature* player) {
return(true);
}
bool yes(Socket* sock) {
return(true);
}
bool wantsPermDeaths(Socket* sock) {
Player* ply = sock->getPlayer();
return(ply && ply && !ply->flagIsSet(P_NO_BROADCASTS) && ply->flagIsSet(P_PERM_DEATH));
}
void announcePermDeath(Creature* player, const char *fmt,...) {
va_list ap;
if(player->isStaff())
return;
va_start(ap, fmt);
doBroadCast(wantsPermDeaths, 0, fmt, ap);
va_end(ap);
}
//********************************************************************
// broadcast_login
//********************************************************************
// This function broadcasts a message to all the players that are in the
// game. If they have the NO-BROADCAST flag set, then they will not see it.
void broadcast_login(Player* player, BaseRoom* inRoom, int login) {
std::ostringstream preText, postText, extra, room;
bstring text = "", illusion = "";
int logoff=0;
ASSERTLOG( player );
ASSERTLOG( !player->getName().empty() );
preText << "### " << player->fullName() << " ";
if(login) {
preText << "the " << gConfig->getRace(player->getDisplayRace())->getAdjective();
// the illusion will be checked here
postText << " " << player->getTitle().c_str();
if(player->getDeity()) {
const DeityData* deity = gConfig->getDeity(player->getDeity());
postText << " (" << deity->getName() << ")";
} else if(player->getClan()) {
const Clan* clan = gConfig->getClan(player->getClan());
postText << " (" << clan->getName() << ")";
}
postText << " just logged in.";
extra << "### (Lvl: " << (int)player->getLevel() << ") (Host: " << player->getSock()->getHostname().c_str() << ")";
} else {
logoff = 1;
preText << "just logged off.";
}
// format text for illusions
text = preText.str() + postText.str();
illusion = preText.str();
if(login && player->getDisplayRace() != player->getRace())
illusion += " (" + gConfig->getRace(player->getRace())->getAdjective() + ")";
illusion += postText.str();
if(inRoom) {
if(inRoom->isUniqueRoom())
room << " (" << inRoom->getAsUniqueRoom()->info.str() << ")";
else if(inRoom->isAreaRoom())
room << " " << inRoom->getAsAreaRoom()->mapmarker.str();
}
// TODO: these are set elsewhere, too... check that out
if(!player->isStaff()) {
player->clearFlag(P_DM_INVIS);
player->removeEffect("incognito");
} else if(player->getClass()==BUILDER) {
player->addEffect("incognito", -1);
}
if(player->flagIsSet(P_DM_INVIS) || player->isEffected("incognito")) {
if(player->isDm())
broadcast(isDm, "^g%s", illusion.c_str());
else if(player->getClass() == CARETAKER)
broadcast(isCt, "^Y%s", illusion.c_str());
else if(player->getClass() == BUILDER) {
broadcast(isStaff, "^G%s", illusion.c_str());
}
} else {
Player* target=0;
for(std::pair<bstring, Player*> p : gServer->players) {
target = p.second;
if(!target->isConnected())
continue;
if(target->isGagging(player->getName()))
continue;
if(target->flagIsSet(P_NO_LOGIN_MESSAGES))
continue;
if(target->getClass() <= BUILDER && !target->isWatcher()) {
if( !player->isStaff() &&
!(logoff && target->getClass() == BUILDER)
)
target->print("%s\n", player->willIgnoreIllusion() ? illusion.c_str() : text.c_str());
} else if((target->isCt() || target->isWatcher()) && player->getClass() != BUILDER) {
target->print("%s%s\n", player->willIgnoreIllusion() ? illusion.c_str() : text.c_str(), room.str().c_str());
if(login && target->isCt())
target->print("%s\n", extra.str().c_str());
}
}
}
}
//********************************************************************
// broadcast_rom
//********************************************************************
// This function outputs a message to everyone in the room specified
// by the integer in the second parameter. If the first parameter
// is greater than -1, then if the player specified by that file
// descriptor is present in the room, they are not given the message
// TODO: Dom: remove
void broadcast_rom_LangWc(int lang, Socket* ignore, Location currentLocation, const char *fmt,...) {
char fmt2[1024];
va_list ap;
va_start(ap, fmt);
strcpy(fmt2, fmt);
strcat(fmt2, "\n");
Player* ply;
for(std::pair<bstring, Player*> p : gServer->players) {
ply = p.second;
Socket* sock = ply->getSock();
if(!sock->isConnected())
continue;
if( ignore != NULL &&
ignore->getPlayer() &&
ignore->getPlayer()->inSameRoom(ply) &&
ply->isGagging(ignore->getPlayer()->getName())
)
continue;
if( ( (currentLocation.room.id && ply->currentLocation.room == currentLocation.room) ||
(currentLocation.mapmarker.getArea() != 0 && currentLocation.mapmarker == ply->currentLocation.mapmarker)
) &&
sock->getFd() > -1 &&
sock != ignore &&
!ply->flagIsSet(P_UNCONSCIOUS) &&
(ply->languageIsKnown(lang) || ply->isEffected("comprehend-languages")))
{
ply->printColor("%s", get_lang_color(lang));
ply->vprint(fmt2, ap);
}
}
va_end(ap);
}
//********************************************************************
// broadcastGroup
//********************************************************************
// this function broadcasts a message to all players in a group except
// the source player
void broadcastGroupMember(bool dropLoot, Creature* player, const Player* listen, const char *fmt, va_list ap) {
if(!listen)
return;
if(listen == player)
return;
// dropLoot broadcasts to all of this
if(!dropLoot) {
if(listen->isGagging(player->getName()))
return;
if(listen->flagIsSet(P_IGNORE_ALL))
return;
if(listen->flagIsSet(P_IGNORE_GROUP_BROADCAST))
return;
} else {
if(!player->inSameRoom(listen))
return;
}
// staff don't broadcast to mortals when invis
if(player->isPlayer() && player->isStaff() && player->getClass() > listen->getClass()) {
if(player->flagIsSet(P_DM_INVIS))
return;
if(player->isEffected("incognito") && !player->inSameRoom(listen))
return;
}
listen->vprint(listen->customColorize(fmt).c_str(), ap);
}
void broadcastGroup(bool dropLoot, Creature* player, const char *fmt,...) {
Group* group = player->getGroup();
if(!group)
return;
va_list ap;
va_start(ap, fmt);
// broadcastGroupMember(dropLoot, player, leader, fmt, ap);
for(Creature* crt : group->members) {
broadcastGroupMember(dropLoot, player, crt->getAsConstPlayer(), fmt, ap);
}
}
//**********************************************************************
// inetname
//**********************************************************************
// This function returns the internet address of the address structure
// passed in as the first parameter.
char *inetname(struct in_addr in) {
register char *cp=0;
static char line[50];
if(in.s_addr == INADDR_ANY)
strcpy(line, "*");
else if(cp)
strcpy(line, cp);
else {
in.s_addr = ntohl(in.s_addr);
sprintf(line, "%u.%u.%u.%u",
(int)(in.s_addr >> 24) & 0xff,
(int)(in.s_addr >> 16) & 0xff,
(int)(in.s_addr >> 8) & 0xff,
(int)(in.s_addr) & 0xff
);
}
return (line);
}
//**********************************************************************
// child_died
//**********************************************************************
// This function gets called when a SIGCHLD signal is sent to the
// program.
void child_died(int sig) {
gServer->childDied();
std::cout << "Child died: " << gServer->getDeadChildren() << " children dead now.\n";
signal(SIGCHLD, child_died);
}
//**********************************************************************
// This causes the game to shutdown in one minute. It is used
// by signal to force a shutdown in response to a HUP signal
// (i.e. kill -HUP pid) from the system.
void quick_shutdown(int sig) {
Shutdown.ltime = time(0);
Shutdown.interval = 120;
}
//**********************************************************************
// broadcastGuild
//**********************************************************************
// Broadcasts to everyone in the same guild as player
void broadcastGuild(int guildNum, int showName, const char *fmt,...) {
char fmt2[1024];
va_list ap;
va_start(ap, fmt);
if(showName) {
strcpy(fmt2, "*CC:GUILD*[");
strcat(fmt2, getGuildName(guildNum));
strcat(fmt2, "] ");
} else
strcpy(fmt2, "");
strcat(fmt2, fmt);
strcat(fmt2, "\n");
Player* target=0;
for(std::pair<bstring, Player*> p : gServer->players) {
target = p.second;
if(!target->isConnected())
continue;
if(target->getGuild() == guildNum && target->getGuildRank() >= GUILD_PEON && !target->flagIsSet(P_NO_BROADCASTS)) {
target->vprint(target->customColorize(fmt2).c_str(), ap);
}
}
va_end(ap);
}
//*********************************************************************
// shutdown_now
//*********************************************************************
void shutdown_now(int sig) {
broadcast("### Quick shutdown now!");
gServer->processOutput();
loge("--- Game shutdown with SIGINT\n");
gConfig->resaveAllRooms(1);
gServer->saveAllPly();
printf("Goodbye.\n");
kill(getpid(), 9);
}
//*********************************************************************
// check_flood
//*********************************************************************
int check_flood(int fd) {
// int i,j=0;
//
// for(i=0; i<Tablesize ;i++) {
// if(fd!=i) {
// if(Ply[i].sock) {
// if(!strcmp(Ply[i].sock->getHostname().c_str(),sock->getHostname().c_str()) && i != fd) {
// j++;
// if(j >= 3) {
// disconnect(i);
// return(1);
// }
// }
// }
// }
// }
// gServer->cleanUp();
return(0);
}
//*********************************************************************
// checkWinFilename
//*********************************************************************
int checkWinFilename(Socket* sock, const bstring str) {
// only do this if we're on windows
#ifdef __CYGWIN__
if( str.equals("aux") ||
str.equals("prn") ||
str.equals("nul") ||
str.equals("con") ||
str.equals("com1") ||
str.equals("com2"))
{
if(sock) sock->print("\"%s\" could not be read.\n", str);
return(0);
}
#endif
return(1);
}
//*********************************************************************
// pueblo functions
//*********************************************************************
bool Pueblo::is(bstring txt) {
return(txt.left(activation.getLength()).toLower() == activation);
}
bstring Pueblo::multiline(bstring str) {
int i=1, plen = activation.getLength()-1;
if(Pueblo::is(str)) {
i = plen;
str.Insert(0, ' ');
}
int len = str.getLength();
for(; i<len; i++) {
if( str.getAt(i-1) == '\n' &&
Pueblo::is(str.right(len-i))
) {
str.Insert(i, ' ');
len++;
i += plen;
}
}
return(str);
}
//*********************************************************************
// escapeText
//*********************************************************************
void Monster::escapeText() {
if(Pueblo::is(getName()))
setName("clay form");
description = Pueblo::multiline(description);
}
void Player::escapeText() {
if(Pueblo::is(description))
description = "";
}
void Object::escapeText() {
if(Pueblo::is(getName()))
setName("clay ball");
if(Pueblo::is(use_output))
zero(use_output, sizeof(use_output));
description = Pueblo::multiline(description);
}
void UniqueRoom::escapeText() {
if(Pueblo::is(getName()))
setName("New Room");
short_desc = Pueblo::multiline(short_desc);
long_desc = Pueblo::multiline(long_desc);
for(Exit* exit : exits) {
exit->escapeText();
}
}
void Exit::escapeText() {
if(Pueblo::is(getName()))
setName("exit");
if(Pueblo::is(enter))
enter = "";
if(Pueblo::is(open))
open = "";
description = Pueblo::multiline(description);
}
//*********************************************************************
// xsc
//*********************************************************************
// Stands for xmlspecialchars (based on htmlspecialchars for PHP) -
// escapes special characters to make the text safe for XML.
// If given Br�gal (which the xml parser cannot save), it will turn it
// into Brügal (which the xml parser can save). Display will be affected
// on old clients, so this should only be run when saving the string.
bstring xsc(const bstring& txt) {
bstring ret = "";
unsigned char c=0;
int t = txt.getLength();
for(int i=0; i<t; i++) {
c = txt.getAt(i);
// beyond 127 we get into the unsupported character range
if(c > 127)
ret += (bstring)"&#"+c+";";
else
ret += (char)c;
}
return(ret);
}
//*********************************************************************
// unxsc
//*********************************************************************
// Reverse of xsc - attempts to turn ü into �. We do thhis when we load from file.
bstring unxsc(const bstring& txt) {
return(unxsc(txt.c_str()));
}
bstring unxsc(const char* txt) {
bstring ret = "";
int c=0, len = strlen(txt);
for(int i=0; i<len; i++) {
c = txt[i];
if(c == '&' && txt[i+1] == '#') {
// get the number from the string
c = atoi(&txt[i+2]);
// advance i appropriately
i += 2;
while(txt[i] != ';') {
i++;
if(i >= len)
return("");
}
}
ret += (char)c;
}
return(ret);
}
//*********************************************************************
// keyTxtConvert
//*********************************************************************
// Treat all accented characters like their unaccented version. This makes
// it easier for players without the accents on their keyboards. Make
// sure everything is treated as lower case.
char keyTxtConvert(unsigned char c) {
// early exit for most characters
if(isalpha(c))
return(tolower(c));
switch(c) {
case 192:
case 193:
case 194:
case 195:
case 196:
case 197:
case 224:
case 225:
case 226:
case 227:
case 228:
case 229:
return('a');
case 199:
case 231:
return('c');
case 200:
case 201:
case 202:
case 203:
case 232:
case 233:
case 234:
case 235:
return('e');
case 204:
case 205:
case 206:
case 207:
case 236:
case 237:
case 238:
case 239:
return('i');
case 209:
case 241:
return('n');
case 210:
case 211:
case 212:
case 213:
case 214:
case 216:
case 242:
case 243:
case 244:
case 245:
case 246:
case 248:
return('o');
case 217:
case 218:
case 219:
case 220:
case 249:
case 250:
case 251:
case 252:
return('u');
case 221:
case 253:
case 255:
return('y');
default:
return(tolower(c));
}
}
//*********************************************************************
// keyTxtConvert
//*********************************************************************
bstring keyTxtConvert(const bstring& txt) {
bstring ret = "";
for(int i=0; i<txt.getLength(); i++) {
ret += keyTxtConvert(txt.getAt(i));
}
return(ret);
}
//*********************************************************************
// keyTxtCompare
//*********************************************************************
// This does the hard work of comparing two different strings:
// Br�gAl, bRUügaL, Br�g�l, and brugal must all match. We can do
// this by using keyTxtConvert to take any supported character and make it
// lower case and by manually extracting the ASCII code.
bool keyTxtCompare(const char* key, const char* txt, int tLen) {
int kI=0, tI=0, convert=0, kLen = strlen(key);
char kC, tC;
if(!kLen || !tLen)
return(false);
for(;;) {
// at the end of the input string and no errors
if(tI >= tLen)
return(true);
// at the end of the main string but not at the end of the input string
if(kI >= kLen)
return(false);
// remove color
if(key[kI] == '^') {
kI += 2;
continue;
}
if(txt[tI] == '^') {
tI += 2;
continue;
}
if(key[kI] == '&' && key[kI+1] == '#') {
// get the number from the string
convert = atoi(&key[kI+2]);
// advance kI appropriately
kI += 2;
while(key[kI] != ';') {
kI++;
if(kI >= kLen)
return(false);
}
// turn it into a character we can compare
kC = keyTxtConvert(convert);
} else
kC = keyTxtConvert(key[kI]);
tC = keyTxtConvert(txt[tI]);
if(tC != kC)
return(false);
tI++;
kI++;
}
}
//*********************************************************************
// keyTxtEqual
//*********************************************************************
bool keyTxtEqual(const Creature* target, const char* txt) {
int len = strlen(txt);
if( keyTxtCompare(target->key[0], txt, len) ||
keyTxtCompare(target->key[1], txt, len) ||
keyTxtCompare(target->key[2], txt, len) ||
keyTxtCompare(target->getCName(), txt, len) ||
target->getId() == txt
)
return(true);
return(false);
}
bool keyTxtEqual(const Object* target, const char* txt) {
int len = strlen(txt);
if( keyTxtCompare(target->key[0], txt, len) ||
keyTxtCompare(target->key[1], txt, len) ||
keyTxtCompare(target->key[2], txt, len) ||
keyTxtCompare(target->getCName(), txt, len) ||
target->getId() == txt
)
return(true);
return(false);
}
//*********************************************************************
// isPrintable
//*********************************************************************
// wrapper for isprint to handle supported characters
bool isPrintable(char c) {
return(isprint(keyTxtConvert(c)));
}