/* * socket.cpp * Stuff to deal with sockets * ____ _ * | _ \ ___ __ _| |_ __ ___ ___ * | |_) / _ \/ _` | | '_ ` _ \/ __| * | _ < __/ (_| | | | | | | \__ \ * |_| \_\___|\__,_|_|_| |_| |_|___/ * * 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 * */ // Mud Includes #include "mud.h" #include "login.h" #include "commands.h" #include "property.h" #include "version.h" #include "msdp.h" // C++ Includes #include <iostream> #include <sstream> // C Includes #include <arpa/telnet.h> #include <fcntl.h> // Needs: fnctl #include <netdb.h> // Needs: gethostbyaddr #include <sys/socket.h> // Needs: AF_INET #include <errno.h> #include <stdlib.h> // Static initialization const int Socket::COMPRESSED_OUTBUF_SIZE = 8192; int Socket::NumSockets = 0; enum telnetNegotiation { NEG_NONE, NEG_IAC, NEG_WILL, NEG_WONT, NEG_DO, NEG_DONT, NEG_SB, NEG_START_NAWS, NEG_SB_NAWS_COL_HIGH, NEG_SB_NAWS_COL_LOW, NEG_SB_NAWS_ROW_HIGH, NEG_SB_NAWS_ROW_LOW, NEG_END_NAWS, NEG_SB_TTYPE, NEG_SB_TTYPE_END, NEG_SB_MSDP, NEG_SB_MSDP_END, NEG_SB_ATCP, NEG_SB_ATCP_END, NEG_SB_CHARSET, NEG_SB_CHARSET_LOOK_FOR_IAC, NEG_SB_CHARSET_END, NEG_MXP_SECURE, NEG_MXP_SECURE_TWO, NEG_MXP_SECURE_THREE, NEG_MXP_SECURE_FINISH, NEG_MXP_SECURE_CONSUME, NEG_UNUSED }; //******************************************************************** // telnet namespace //******************************************************************** namespace telnet { // MSDP Support unsigned const char will_msdp[] = { IAC, WILL, TELOPT_MSDP, '\0' }; unsigned const char wont_msdp[] = { IAC, WONT, TELOPT_MSDP, '\0' }; // ATCP Support unsigned const char do_atcp[] = { IAC, DO, TELOPT_ATCP, '\0' }; unsigned const char wont_atcp[] = { IAC, WONT, TELOPT_ATCP, '\0' }; // MXP Support unsigned const char will_mxp[] = { IAC, WILL, TELOPT_MXP, '\0' }; // Start mxp string unsigned const char start_mxp[] = { IAC, SB, TELOPT_MXP, IAC, SE, '\0' }; // MCCP V2 support unsigned const char will_comp2[] = { IAC, WILL, TELOPT_COMPRESS2, '\0' }; // MCCP V1 support unsigned const char will_comp1[] = { IAC, WILL, TELOPT_COMPRESS, '\0' }; // Start string for compress2 unsigned const char start_mccp2[] = { IAC, SB, TELOPT_COMPRESS2, IAC, SE, '\0' }; // start string for compress1 unsigned const char start_mccp[] = { IAC, SB, TELOPT_COMPRESS, WILL, SE, '\0' }; // Echo input unsigned const char will_echo[] = { IAC, WILL, TELOPT_ECHO, '\0' }; // EOR After every prompt unsigned const char will_eor[] = { IAC, WILL, TELOPT_EOR, '\0' }; // MSP Support unsigned const char will_msp[] = { IAC, WILL, TELOPT_MSP, '\0' }; // MSP Stop unsigned const char wont_msp[] = { IAC, WONT, TELOPT_MSP, '\0' }; // MSSP Support unsigned const char will_mssp[] = { IAC, WILL, TELOPT_MSSP, '\0' }; // MSSP SB unsigned const char sb_mssp_start[] = { IAC, SB, TELOPT_MSSP, '\0' }; // MSSP SB stop unsigned const char sb_mssp_end[] = { IAC, SE, '\0' }; // Terminal type negotation unsigned const char do_ttype[] = { IAC, DO, TELOPT_TTYPE, '\0' }; // Charset unsigned const char do_charset[] = { IAC, DO, TELOPT_CHARSET, '\0' }; unsigned const char charset_utf8[] = { IAC, SB, TELOPT_CHARSET, 1, ' ', 'U', 'T', 'F', '-', '8', IAC, SE, '\0' }; // Start sub negotiation for terminal type unsigned const char query_ttype[] = { IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE, '\0' }; // Window size negotation NAWS unsigned const char do_naws[] = { IAC, DO, TELOPT_NAWS, '\0' }; // End of line string unsigned const char eor_str[] = { IAC, EOR, '\0' }; // MCCP Hooks void *zlib_alloc(void *opaque, unsigned int items, unsigned int size) { return calloc(items, size); } void zlib_free(void *opaque, void *address) { free(address); } } //-------------------------------------------------------------------- // Constructors, Destructors, etc //******************************************************************** // reset //******************************************************************** void Socket::reset() { fd = -1; opts.mccp = 0; opts.dumb = true; opts.mxp = false; opts.msp = false; opts.eor = false; opts.msdp = false; opts.atcp = false; opts.charset = false; opts.UTF8 = false; opts.mxpClientSecure = false; opts.color = NO_COLOR; opts.xterm256 = false; opts.lastColor = '\0'; opts.compressing = false; inPlayerList = false; out_compress_buf = NULL; out_compress = NULL; myPlayer = NULL; tState = NEG_NONE; oneIAC = watchBrokenClient = false; term.type = "dumb"; term.cols = 82; term.rows = 40; ltime = time(0); intrpt = 0; fn = 0; timeout = 0; ansi = 0; tState = NEG_NONE; connState = LOGIN_START; lastState = LOGIN_START; zero(tempstr, sizeof(tempstr)); inBuf = ""; // cmdInBuf = ""; spyingOn = 0; } //******************************************************************** // Socket //******************************************************************** Socket::Socket(int pFd) { reset(); fd = pFd; NumSockets++; } Socket::Socket(int pFd, sockaddr_in pAddr, bool &dnsDone) { reset(); struct linger ling; fd = pFd; resolveIp(pAddr, host.ip); resolveIp(pAddr, host.hostName); // Start off with the hostname as the ip, then do // an asyncronous lookup // Make this socket non blocking nonBlock(fd); // Set Linger behavior ling.l_onoff = ling.l_linger = 0; setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &ling, sizeof(struct linger)); NumSockets++; std::cout << "Constructing socket (" << fd << ") from " << host.ip << " Socket #" << NumSockets << std::endl; // If we're running under valgrind, we don't resolve dns. The child // process tends to mess with proper memory leak detection if (gServer->getDnsCache(host.ip, host.hostName) || gServer->isValgrind()) { dnsDone = true; } else { dnsDone = false; setState(LOGIN_DNS_LOOKUP); gServer->startDnsLookup(this, pAddr); } startTelnetNeg(); } //******************************************************************** // closeFd //******************************************************************** // Disconnect the underlying file descriptor void Socket::cleanUp() { clearSpying(); clearSpiedOn(); msdpClearReporting(); if (myPlayer) { if (myPlayer->fd > -1) { myPlayer->save(true); myPlayer->uninit(); } freePlayer(); } endCompress(); if(fd > -1) { close(fd); fd = -1; } } //******************************************************************** // ~Socket //******************************************************************** Socket::~Socket() { std::cout << "Deconstructing socket , "; NumSockets--; std::cout << "Num sockets: " << NumSockets << std::endl; cleanUp(); } //******************************************************************** // addToPlayerList //******************************************************************** void Socket::addToPlayerList() { inPlayerList = true; } //******************************************************************** // freePlayer //******************************************************************** void Socket::freePlayer() { if (myPlayer) free_crt(myPlayer, inPlayerList); myPlayer = 0; inPlayerList = false; } // End - Constructors, Destructors, etc //-------------------------------------------------------------------- //******************************************************************** // clearSpying //******************************************************************** void Socket::clearSpying() { if (spyingOn) { spyingOn->removeSpy(this); spyingOn = 0; } } //******************************************************************** // clearSpiedOn //******************************************************************** void Socket::clearSpiedOn() { std::list<Socket*>::iterator it; for (it = spying.begin(); it != spying.end(); it++) { Socket *sock = *it; if (sock) sock->setSpying(NULL); } spying.clear(); } //******************************************************************** // setSpying //******************************************************************** void Socket::setSpying(Socket *sock) { if (sock) clearSpying(); spyingOn = sock; if (sock) sock->addSpy(this); if (!sock) { if (myPlayer) myPlayer->clearFlag(P_SPYING); } } //******************************************************************** // removeSpy //******************************************************************** void Socket::removeSpy(Socket *sock) { spying.remove(sock); if (myPlayer->getClass() >= sock->myPlayer->getClass()) sock->printColor("^r%s is no longer observing you.\n", sock->myPlayer->getCName()); } //******************************************************************** // addSpy //******************************************************************** void Socket::addSpy(Socket *sock) { spying.push_back(sock); } //******************************************************************** // disconnect //******************************************************************** // Set this socket for removal on the next cleanup void Socket::disconnect() { // TODO: Perhaps remove player from the world right here. setState(CON_DISCONNECTING); flush(); cleanUp(); } //******************************************************************** // resolveIp //******************************************************************** void Socket::resolveIp(const sockaddr_in &addr, bstring& ip) { std::ostringstream tmp; long i = htonl(addr.sin_addr.s_addr); tmp << ((i >> 24) & 0xff) << "." << ((i >> 16) & 0xff) << "." << ((i >> 8) & 0xff) << "." << (i & 0xff); ip = tmp.str(); } bstring Socket::parseForOutput(bstring& outBuf) { int i = 0, n = outBuf.size(); std::ostringstream oStr; bool inTag = false, inEntity = false; unsigned char ch = 0; while(i < n) { ch = outBuf[i++]; if(inTag) { if(ch == CH_MXP_END) { inTag = false; if(opts.mxp) oStr << ">" << MXP_LOCK_CLOSE; } else if(opts.mxp) oStr << ch; continue; } else if(inEntity) { if(opts.mxp) oStr << ch; if(ch == ';') inEntity = false; continue; } else { if(ch == CH_MXP_BEG) { inTag = true; if(opts.mxp) oStr << MXP_SECURE_OPEN << "<"; continue; } else { if(ch == '^') { ch = outBuf[i++]; oStr << getColorCode(ch); } else if(ch == '\n') { oStr << "\r\n"; } else { oStr << ch; } continue; } } } return(oStr.str()); } bstring Socket::stripTelnet(bstring& inStr, int& newLen) { int i = 0, n = inStr.size(); std::ostringstream oStr; while(i < n) { if((unsigned char)inStr[i] == IAC) { switch((unsigned char)inStr[i+1]) { case WILL: case WONT: case DO: // Skip 3 i+=3; continue; case EOR: // Skip 2 i+=2; continue; case SB: // Skip until we find IAC SE while(i < n) { if((unsigned char)inStr[i] == IAC && (unsigned char)inStr[i+1] == SE) { // Skip two more i += 2; break; } i++; } continue; } } oStr << inStr[i++]; newLen++; } return(oStr.str()); } //******************************************************************** // checkLockOut //******************************************************************** void Socket::checkLockOut() { int lockStatus = gConfig->isLockedOut(this); if (lockStatus == 0) { askFor("\n\nPlease enter name: "); setState(LOGIN_GET_NAME); } else if (lockStatus == 2) { print("\n\nA password is required to play from your site: "); setState(LOGIN_GET_LOCKOUT_PASSWORD); } else if (lockStatus == 1) { print("\n\nYou are not wanted here. Begone.\n"); setState(CON_DISCONNECTING); } } //******************************************************************** // startTelnetNeg //******************************************************************** void Socket::startTelnetNeg() { // As noted from KaVir - // Some clients (such as GMud) don't properly handle negotiation, and simply // display every printable character to the screen. However TTYPE isn't a // printable character, so we negotiate for it first, and only negotiate for // other protocols if the client responds with IAC WILL TTYPE or IAC WONT // TTYPE. Thanks go to Donky on MudBytes for the suggestion. write(telnet::do_ttype, false); } void Socket::continueTelnetNeg(bool queryTType) { if (queryTType) write(telnet::query_ttype, false); // Not a dumb client if we've gotten a response opts.dumb = false; write(telnet::will_comp2, false); write(telnet::will_comp1, false); write(telnet::do_naws, false); write(telnet::will_msdp, false); write(telnet::do_atcp, false); write(telnet::will_mxp, false); write(telnet::will_mssp, false); write(telnet::will_msp, false); write(telnet::do_charset, false); //write(telnet::will_echo); write(telnet::will_eor, false); } //******************************************************************** // processInput //******************************************************************** int Socket::processInput() { unsigned char tmpBuf[1024]; int n; unsigned int i = 0; bstring tmp = ""; // Attempt to read from the socket n = read(getFd(), tmpBuf, 1023); if (n <= 0) { if (errno != EWOULDBLOCK) return (-1); else return (0); } tmp.reserve(n); InBytes += n; tmpBuf[n] = '\0'; // If we have any full strings, copy it over to the queue to be interpreted // Look for any IAC commands using a finite state machine for (i = 0; i < (unsigned) n; i++) { // For debugging // std::cout << "DEBUG:" << (unsigned int)tmpBuf[i] << "'" << (unsigned char)tmpBuf[i] << "'" << "\n"; // Try to handle zMud, cMud & tintin++ which don't seem to double the IAC for NAWS // during my limited testing -JM if (oneIAC && tState > NEG_START_NAWS && tState < NEG_END_NAWS && tmpBuf[i] != IAC) { // Broken Client std::cout << "NAWS: BUG - Broken Client: Non-doubled IAC\n"; i--; } if (watchBrokenClient) { // If we just finished NAWS with a 255 height...keep an eye out for the next // character to be a stray SE if (tState == NEG_NONE && (unsigned char) tmpBuf[i] == SE) { std::cout << "NAWS: BUG - Stray SE\n"; // Set the tState to NEG_IAC as it should have been, and carry gracefully on tState = NEG_IAC; } // It should only be the next character, so if we don't find it...don't keep looking for it watchBrokenClient = false; } switch (tState) { case NEG_NONE: // Expecting an IAC here if ((unsigned char) tmpBuf[i] == IAC) { tState = NEG_IAC; break; } else if((unsigned char)tmpBuf[i] == '\033') { tState = NEG_MXP_SECURE; break; } else { tmp += tmpBuf[i]; break; } break; case NEG_MXP_SECURE: if(tmpBuf[i] == '[') { tState = NEG_MXP_SECURE_TWO; break; } else { tmp += "\033" + bstring(tmpBuf[i]); } tState = NEG_NONE; break; case NEG_MXP_SECURE_TWO: if(tmpBuf[i] == '1') { tState = NEG_MXP_SECURE_FINISH; break; } else { tmp += "\033[" + bstring(tmpBuf[i]); } tState = NEG_NONE; break; case NEG_MXP_SECURE_FINISH: if(tmpBuf[i] == 'z') { opts.mxpClientSecure = true; tState = NEG_MXP_SECURE_CONSUME; std::cout << "Client secure MXP mode enabled" << std::endl; break; } else { tmp += "\033[1" + bstring(tmpBuf[i]); } tState = NEG_NONE; break; case NEG_MXP_SECURE_CONSUME: if(tmpBuf[i] == '\n') { tState = NEG_NONE; parseMXPSecure(); } else { cmdInBuf.push_back(tmpBuf[i]); } break; case NEG_IAC: switch ((unsigned char) tmpBuf[i]) { case NOP: case IP: case GA: tState = NEG_NONE; break; case WILL: tState = NEG_WILL; break; case WONT: tState = NEG_WONT; break; case DO: tState = NEG_DO; break; case DONT: tState = NEG_DONT; break; case SE: tState = NEG_NONE; break; case SB: tState = NEG_SB; break; case IAC: // Doubled IAC, send along to parser tmp += tmpBuf[i]; tState = NEG_NONE; break; default: tState = NEG_NONE; break; } break; // Handle Do and Will case NEG_DO: case NEG_WILL: case NEG_DONT: case NEG_WONT: negotiate((unsigned char) tmpBuf[i]); break; case NEG_SB: switch ((unsigned char) tmpBuf[i]) { case NAWS: tState = NEG_SB_NAWS_COL_HIGH; break; case TTYPE: tState = NEG_SB_TTYPE; break; case CHARSET: if (getCharset()) tState = NEG_SB_CHARSET; else tState = NEG_NONE; break; case MSDP: tState = NEG_SB_MSDP; break; case ATCP: tState = NEG_SB_ATCP; break; default: std::cout << "Unknown Sub Negotiation" << std::endl; tState = NEG_NONE; break; } break; case NEG_SB_MSDP: cmdInBuf.push_back(tmpBuf[i]); if (tmpBuf[i] == IAC) { tState = NEG_SB_MSDP_END; break; } break; case NEG_SB_MSDP_END: if (tmpBuf[i] == SE) { // We should have a full ATCP command now, let's parse it now parseMsdp(); tState = NEG_NONE; break; } else { // Not an SE: The last input was an IAC, so push the new input // onto the inbuf and keep going cmdInBuf.push_back(tmpBuf[i]); tState = NEG_SB_MSDP; break; } break; case NEG_SB_ATCP: if (tmpBuf[i] == IAC) { tState = NEG_SB_ATCP_END; break; } cmdInBuf.push_back(tmpBuf[i]); break; case NEG_SB_ATCP_END: if (tmpBuf[i] == SE) { // We should have a full ATCP command now, let's parse it now parseAtcp(); tState = NEG_NONE; break; } else { // Not an SE: The last input was an IAC, so push the new input // onto the inbuf and keep going cmdInBuf.push_back(tmpBuf[i]); tState = NEG_SB_ATCP; break; } break; case NEG_SB_CHARSET: // We've only asked for UTF-8, so assume if they respond it's for that // and just eat the rest of the input // // Any other sub-negotiations (such as TTABLE-*) are not handled if (tmpBuf[i] == ACCEPTED) { std::cout << "Enabled UTF8" << std::endl; opts.UTF8 = true; tState = NEG_SB_CHARSET_LOOK_FOR_IAC; } else if (tmpBuf[i] == REJECTED) { opts.UTF8 = false; tState = NEG_SB_CHARSET_LOOK_FOR_IAC; } else { tState = NEG_SB_CHARSET_LOOK_FOR_IAC; } break; case NEG_SB_CHARSET_LOOK_FOR_IAC: // Do nothing while we wait for an IAC if (tmpBuf[i] == IAC) tState = NEG_SB_CHARSET_END; break; case NEG_SB_CHARSET_END: if (tmpBuf[i] == IAC) { // Double IAC, part of the data tState = NEG_SB_CHARSET_LOOK_FOR_IAC; break; } else if (tmpBuf[i] == SE) { // Found what we were looking for } else { std::cout << "NEG_SB_CHARSET_END Error: Expected SE, got '" << (int) tmpBuf[i] << "'" << std::endl; } tState = NEG_NONE; break; case NEG_SB_TTYPE: // Grab the terminal type if (tmpBuf[i] == TELQUAL_IS) { term.type.erase(); term.type = ""; } else if (tmpBuf[i] == IAC) { // Expect a SE next tState = NEG_SB_TTYPE_END; break; } else { term.type += tmpBuf[i]; } break; case NEG_SB_TTYPE_END: if (tmpBuf[i] == SE) { std::cout << "Found term type: " << term.type << std::endl; if (term.type == "Mudlet 2.0.1") { // Mudlet doesn't seem to like MXP color, so turn it off // opts.color = ANSI_COLOR; } } else if (tmpBuf[i] == IAC) { // I doubt this will happen std::cout << "NEG_SB_TTYPE: Found double IAC" << std::endl; term.type += tmpBuf[i]; tState = NEG_SB_TTYPE; break; } else { std::cout << "NEG_SB_TTYPE_END Error: Expected SE, got '" << (int) tmpBuf[i] << "'" << std::endl; } tState = NEG_NONE; break; case NEG_SB_NAWS_COL_HIGH: if (handleNaws(term.cols, tmpBuf[i], true)) tState = NEG_SB_NAWS_COL_LOW; break; case NEG_SB_NAWS_COL_LOW: if (handleNaws(term.cols, tmpBuf[i], false)) tState = NEG_SB_NAWS_ROW_HIGH; break; case NEG_SB_NAWS_ROW_HIGH: if (handleNaws(term.rows, tmpBuf[i], true)) tState = NEG_SB_NAWS_ROW_LOW; break; case NEG_SB_NAWS_ROW_LOW: if (handleNaws(term.rows, tmpBuf[i], false)) { std::cout << "New term size: " << term.cols << " x " << term.rows << std::endl; // Some clients (tintin++, cmud, possibly zmud) don't seem to double an IAC(255) when it's // sent as data, if this happens in the cols...we should be able to gracefully catch it // but if it happens in the rows...it'll eat the IAC from IAC SE and cause problems, // so we set the state machine to keep an eye out for a stray SE if the rows were set to 255 if (term.rows == 255) watchBrokenClient = true; tState = NEG_NONE; } break; default: std::cout << "Unhandled state" << std::endl; tState = NEG_NONE; break; } } // Handles the screwy windows telnet, and its not that hard for // other clients that send \n\r too tmp.Replace("\r", "\n"); inBuf += tmp; // handle backspaces n = inBuf.getLength(); for (i = n - tmp.getLength(); i < (unsigned) n; i++) { if (inBuf.getAt(i) == '\b' || inBuf.getAt(i) == 127) { if (n < 2) { inBuf = ""; n = 0; } else { inBuf.Delete(i - 1, 2); n -= 2; i--; } } } bstring::size_type idx = 0; while ((idx = inBuf.find("\n", 0)) != bstring::npos) { bstring tmpr = inBuf.substr(0, idx); // Don't copy the \n idx += 1; // Consume the \n if (inBuf[idx] == '\n') idx += 1; // Consume the extra \n if applicable inBuf.erase(0, idx); if(opts.mxpClientSecure) { if(inBuf.left(8).equals("<version", false)) { } else if(inBuf.left(9).equals("<supports", false)) { } } input.push(tmpr); } ltime = time(0); return (0); } bool Socket::negotiate(unsigned char ch) { switch (ch) { case TELOPT_CHARSET: if (tState == NEG_WILL) { opts.charset = true; write(telnet::charset_utf8, false); std::cout << "Charset On" << std::endl; } else if (tState == NEG_WONT) { opts.charset = false; std::cout << "Charset Off" << std::endl; } tState = NEG_NONE; break; case TELOPT_TTYPE: // If we've gotten this far, we're fairly confident they support ANSI color // so enable that opts.color = ANSI_COLOR; if (tState == NEG_WILL) { // Continue and query the rest of the options, including term type continueTelnetNeg(true); } else if (tState == NEG_WONT) { // If they respond to something here they know how to negotiate, // so continue and ask for the rest of the options, except term type // which they have just indicated they won't do continueTelnetNeg(false); } tState = NEG_NONE; break; case TELOPT_MXP: if (tState == NEG_WILL || tState == NEG_DO) { write(telnet::start_mxp); // Start off in MXP LOCKED CLOSED write(MXP_LOCK_CLOSE); //TODO: send elements we're using for mxp opts.mxp = true; // Assume if they have MXP enabled, they want mxp colors opts.color = MXP_COLOR; std::cout << "Enabled MXP" << std::endl; defineMxp(); } else if (tState == NEG_WONT || tState == NEG_DONT) { opts.mxp = false; std::cout << "Disabled MXP" << std::endl; } tState = NEG_NONE; break; case TELOPT_COMPRESS2: if (tState == NEG_WILL || tState == NEG_DO) { opts.mccp = 2; startCompress(); } else if (tState == NEG_WONT || tState == NEG_DONT) { if (opts.mccp == 2) { opts.mccp = 0; endCompress(); } } tState = NEG_NONE; break; case TELOPT_COMPRESS: if (tState == NEG_WILL || tState == NEG_DO) { opts.mccp = 1; startCompress(); } else if (tState == NEG_WONT || tState == NEG_DONT) { if (opts.mccp == 1) { opts.mccp = 0; endCompress(); } } tState = NEG_NONE; break; case TELOPT_EOR: if (tState == NEG_WILL || tState == NEG_DO) { opts.eor = true; printf("Activating EOR\n"); } else if (tState == NEG_WONT || tState == NEG_DONT) { opts.eor = false; printf("Deactivating EOR\n"); } tState = NEG_NONE; break; case TELOPT_NAWS: if (tState == NEG_WILL) { opts.naws = true; } else { opts.naws = false; } tState = NEG_NONE; break; case TELOPT_ECHO: case TELOPT_NEW_ENVIRON: // TODO: Echo/New Environ tState = NEG_NONE; break; case TELOPT_MSSP: if (tState == NEG_DO) { sendMSSP(); } tState = NEG_NONE; break; case TELOPT_MSP: if (tState == NEG_WILL || tState == NEG_DO) { opts.msp = true; } else if (tState == NEG_WONT || tState == NEG_DONT) { opts.msp = false; } tState = NEG_NONE; break; case TELOPT_MSDP: if (tState == NEG_DO) { opts.msdp = true; msdpSend("SERVER_ID"); std::cout << "Enabled MSDP" << std::endl; } else { std::cout << "Disabled MSDP" << std::endl; opts.msdp = false; } tState = NEG_NONE; break; case TELOPT_ATCP: if (tState == NEG_WILL || tState == NEG_DO) { opts.atcp = true; std::cout << "Enabled ATCP" << std::endl; msdpSend("SERVER_ID"); // msdpSendPair("SERVER_ID", "The Realms of Hell v" VERSION); } else { std::cout << "Disabled ATCP" << std::endl; opts.atcp = false; } tState = NEG_NONE; break; default: tState = NEG_NONE; break; } return (true); } //******************************************************************** // handleNaws //******************************************************************** // Return true if a state should be changed bool Socket::handleNaws(int& colRow, unsigned char& chr, bool high) { // If we get an IAC here, we need a double IAC if (chr == IAC) { if (!oneIAC) { // std::cout << "NAWS: Got ONE IAC, checking for two.\n"; oneIAC = true; return (false); } else { // std::cout << "NAWS: Got second IAC.\n"; oneIAC = false; } } else if (oneIAC && chr != IAC) { // Error! std::cout << "NAWS: BUG - Expecting a doubled IAC, got " << (unsigned int) chr << "\n"; oneIAC = false; } if (high) colRow = chr << 8; else colRow += chr; return (true); } //******************************************************************** // processOneCommand //******************************************************************** // Aka interpreter int Socket::processOneCommand(void) { bstring cmd = input.front(); input.pop(); // Send the command to the people we're spying on if (!spying.empty()) { std::list<Socket*>::iterator it; for (it = spying.begin(); it != spying.end(); it++) { Socket *sock = *it; if (sock) sock->write("[" + cmd + "]\n", false); } } ((void(*)(Socket*, bstring)) (fn))(this, cmd); return (1); } //******************************************************************** // restoreState //******************************************************************** // Returns a fd to it's previous state void Socket::restoreState() { setState(lastState); createPlayer(this, ""); return; } int restoreState(Socket* sock) { sock->restoreState(); return (0); } //********************************************************************* // pauseScreen //********************************************************************* void pauseScreen(Socket* sock, bstring str) { if(str.equals("quit")) sock->disconnect(); else sock->reconnect(); } //********************************************************************* // reconnect //********************************************************************* void Socket::reconnect(bool pauseScreen) { clearSpying(); clearSpiedOn(); msdpClearReporting(); freePlayer(); if (pauseScreen) { setState(LOGIN_PAUSE_SCREEN); printColor( "\nPress ^W[RETURN]^x to reconnect or type ^Wquit^x to disconnect.\n: "); } else { setState(LOGIN_GET_NAME); showLoginScreen(); } } //********************************************************************* // setState //********************************************************************* // Sets a fd's state and changes the interpreter to the appropriate function void convertNewWeaponSkills(Socket* sock, bstring str); void Socket::setState(int pState, int pFnParam) { // Only store the last state if we're changing states, used mainly in the viewing file states if (pState != connState) lastState = connState; connState = pState; if (pState == LOGIN_PAUSE_SCREEN) fn = pauseScreen; else if (pState > LOGIN_START && pState < LOGIN_END) fn = login; else if (pState > CREATE_START && pState < CREATE_END) fn = createPlayer; else if (pState > CON_START && pState < CON_END) fn = command; else if (pState > CON_STATS_START && pState < CON_STATS_END) fn = changingStats; else if (pState == CON_CONFIRM_SURNAME) fn = doSurname; else if (pState == CON_CONFIRM_TITLE) fn = doTitle; else if (pState == CON_VIEWING_FILE) fn = viewFile; else if (pState == CON_SENDING_MAIL) fn = postedit; else if (pState == CON_EDIT_NOTE) fn = noteedit; else if (pState == CON_EDIT_HISTORY) fn = histedit; else if (pState == CON_EDIT_PROPERTY) fn = Property::descEdit; else if (pState == CON_VIEWING_FILE_REVERSE) fn = viewFileReverse; else if (pState > CON_PASSWORD_START && pState < CON_PASSWORD_END) fn = changePassword; else if (pState == CON_CHOSING_WEAPONS) fn = convertNewWeaponSkills; else { printf("Unknown connected state!\n"); fn = login; } fnparam = pFnParam; } int setState(Socket* sock, int state, int pFnParam) { sock->setState(state, pFnParam); return (0); } bstring getMxpTag( bstring tag, bstring text ) { bstring::size_type n = text.find(tag); if(n == bstring::npos) return(""); std::ostringstream oStr; // Add the legnth of the tag n += tag.length(); if( n < text.length()) { // If our first char is a quote, advance if(text[n] == '\"') n++; while(n < text.length()) { char ch = text[n++]; if(ch == '.' || isdigit(ch) || isalpha(ch) ) { oStr << ch; } else { return(oStr.str()); } } } return(""); } char caster_from_unsigned( unsigned char ch ) { return static_cast< char >( ch ); } bool Socket::parseMXPSecure() { if(getMxp()) { bstring toParse(reinterpret_cast<char*>(&cmdInBuf[0]), cmdInBuf.size()); //bstring toParse(cmdInBuf.begin(), cmdInBuf.end()); //bstring toParse; //toParse.reserve(cmdInBuf.size()); //std::transform( cmdInBuf.begin(), cmdInBuf.end(), back_inserter( toParse ), caster_from_unsigned ); //toParse.assign(&cmdInBuf[0], cmdInBuf.size()); std::cout << toParse << std::endl; bstring version = getMxpTag("VERSION=", toParse); if(!version.empty()) { term.version = version; if(term.type.equals("mushclient", false)) { if(version >= "4.02") opts.xterm256 = true; } else if (term.type.equals("cmud", false)) { if(version >= "3.04") opts.xterm256 = true; } else if (term.type.equals("atlantis", false)) { // Any version of atlantis with MXP supports xterm256 opts.xterm256 = true; } } bstring supports = getMxpTag("SUPPORT=", toParse); if(!supports.empty()) { std::cout << "Got <SUPPORT='" << supports << "'>" << std::endl; } } clearMxpClientSecure(); cmdInBuf.clear(); return(true); } bool Socket::parseMsdp() { if(getMsdp()) { bstring var, val; int nest = 0; var.reserve(15); val.reserve(30); int i = 0, n = cmdInBuf.size(); while (i < n && cmdInBuf[i] != SE) { switch (cmdInBuf[i]) { case MSDP_VAR: i++; while (i < n && cmdInBuf[i] != MSDP_VAL) { var += cmdInBuf[i++]; } break; case MSDP_VAL: i++; val.erase(); while (i < n && cmdInBuf[i] != IAC) { if (cmdInBuf[i] == MSDP_TABLE_OPEN || cmdInBuf[i] == MSDP_ARRAY_OPEN) nest++; else if (cmdInBuf[i] == MSDP_TABLE_CLOSE || cmdInBuf[i] == MSDP_ARRAY_CLOSE) nest--; else if (nest == 0 && (cmdInBuf[i] == MSDP_VAR || cmdInBuf[i] == MSDP_VAL)) break; val += cmdInBuf[i++]; } if (nest == 0) processMsdpVarVal(var, val); break; default: i++; break; } } } cmdInBuf.clear(); return (true); } bool Socket::parseAtcp() { if(getAtcp()) { bstring var, val; var.reserve(15); val.reserve(30); int i = 0, n = cmdInBuf.size(); while (i < n && cmdInBuf[i] != SE) { switch (cmdInBuf[i]) { case '@': i++; var.erase(); while (i < n && cmdInBuf[i] != ' ') { var += cmdInBuf[i++]; } break; case ' ': i++; val.erase(); while (i < n && cmdInBuf[i] != '@' && cmdInBuf[i] != ' ') { val += cmdInBuf[i++]; } processMsdpVarVal(var, val); break; default: i++; break; } } } cmdInBuf.clear(); return (true); } //******************************************************************** // bprint //******************************************************************** // Append a string to the socket's output queue void Socket::bprint(bstring toPrint) { if (!toPrint.empty()) output += toPrint; } //******************************************************************** // bprintColor //******************************************************************** // Append a string to the socket's output queue...in color! void Socket::bprintColor(bstring toPrint) { if(!toPrint.empty()) { // Append the string to the output buffer, we'll parse color there output += toPrint; } } //******************************************************************** // bprintColor //******************************************************************** // Append a string to the socket's output queue...without color! void Socket::bprintNoColor(bstring toPrint) { if(!toPrint.empty()) { // Double any color codes to prevent them from being parsed later toPrint.Replace("^", "^^"); output += toPrint; } } //******************************************************************** // println //******************************************************************** // Append a string to the socket's output queue with a \n void Socket::println(bstring toPrint) { bprint(toPrint + "\n"); return; } //******************************************************************** // print //******************************************************************** void Socket::print(const char* fmt, ...) { if (!this) return; va_list ap; va_start(ap, fmt); bstring newFmt = stripColor(fmt); vprint( newFmt.c_str(), ap); va_end(ap); } //******************************************************************** // printColor //******************************************************************** void Socket::printColor(const char* fmt, ...) { if (!this) return; va_list ap; va_start(ap, fmt); vprint(fmt, ap); va_end(ap); } //******************************************************************** // flush //******************************************************************** // Flush pending output and send a prompt void Socket::flush() { int n, len; if(!processed_output.empty()) { len = processed_output.length(); n = write(processed_output, false, false); } else { len = output.length(); if ((n = write(output)) == 0) return; output.clear(); } // If we only wrote OOB data or partial data was written because of EWOULDBLOCK, // then n is -2, don't send a prompt in that case if (n != -2 && myPlayer && connState != CON_CHOSING_WEAPONS) myPlayer->sendPrompt(); } //******************************************************************** // write //******************************************************************** // Write a string of data to the the socket's file descriptor int Socket::write(bstring toWrite, bool pSpy, bool process) { int written = 0; int n = 0; int total = 0; // Parse any color, unicode, etc here bstring toOutput; if(process) toOutput = parseForOutput(toWrite); else { toOutput = toWrite; } total = toOutput.length(); const char *str = toOutput.c_str(); // Write directly to the socket, otherwise compress it and send it if (!opts.compressing) { do { n = ::write(fd, str + written, total - written); if (n < 0) { if(errno != EWOULDBLOCK) return (n); else { // The write would have blocked n = -2; // If we haven't written the total number of bytes planned // Save the remaining string for the next go around if(written < total) { processed_output = str+written; } break; } } written += n; } while (written < total); UnCompressedBytes += written; if(n == -2) written = -2; if(written >= total && !processed_output.empty() && process == false) { processed_output.erase(); } } else { UnCompressedBytes += total; out_compress->next_in = (unsigned char*) str; out_compress->avail_in = total; while (out_compress->avail_in) { out_compress->avail_out = COMPRESSED_OUTBUF_SIZE - ((char*) out_compress->next_out - (char*) out_compress_buf); if (deflate(out_compress, Z_SYNC_FLUSH) != Z_OK) { return (0); } written += processCompressed(); if (written == 0) break; } } int strippedLen = 0; bstring forSpy = Socket::stripTelnet(toWrite, strippedLen); if (pSpy && !spying.empty()) { forSpy.Replace("\n", "\n<Spy> "); if(!forSpy.empty()) { std::list<Socket*>::iterator it; for (it = spying.begin(); it != spying.end(); it++) { Socket *sock = *it; if (sock) sock->write("<Spy> " + forSpy, false); } } } // Keep track of total outbytes OutBytes += written; // If stripped len is 0, it means we only wrote OOB data, so adjust the return so // we don't send another prompt if(strippedLen == 0) written = -2; return (written); } //-------------------------------------------------------------------- // MCCP //******************************************************************** // startCompress //******************************************************************** int Socket::startCompress(bool silent) { if (!opts.mccp) return (-1); if (opts.compressing) return (-1); out_compress_buf = new char[COMPRESSED_OUTBUF_SIZE]; //out_compress = new z_stream; out_compress = (z_stream *) malloc(sizeof(*out_compress)); out_compress->zalloc = telnet::zlib_alloc; out_compress->zfree = telnet::zlib_free; out_compress->opaque = NULL; out_compress->next_in = NULL; out_compress->avail_in = 0; out_compress->next_out = (Bytef*) out_compress_buf; out_compress->avail_out = COMPRESSED_OUTBUF_SIZE; if (deflateInit(out_compress, 9) != Z_OK) { // Problem with zlib, try to clean up delete out_compress_buf; free(out_compress); return (-1); } if (!silent) { if (opts.mccp == 2) write(telnet::start_mccp2, false); else write(telnet::start_mccp, false); } // We're compressing now opts.compressing = true; return (0); } //******************************************************************** // endCompress //******************************************************************** int Socket::endCompress() { if (out_compress && opts.compressing) { unsigned char dummy[1] = { 0 }; out_compress->avail_in = 0; out_compress->next_in = dummy; // process any remaining output first? if (deflate(out_compress, Z_FINISH) != Z_STREAM_END) { std::cout << "Error with deflate Z_FINISH\n"; return (-1); } // Send any residual data if (processCompressed() < 0) return (-1); deflateEnd(out_compress); delete[] out_compress_buf; out_compress = null; out_compress_buf = null; opts.mccp = 0; opts.compressing = false; } return (-1); } //******************************************************************** // processCompressed //******************************************************************** int Socket::processCompressed() { int len = (int) ((char*) out_compress->next_out - (char*) out_compress_buf); int written = 0; int block; int n, i; if (len > 0) { for (i = 0, n = 0; i < len; i += n) { block = tMIN<int>(len - i, 4096); if ((n = ::write(fd, out_compress_buf + i, block)) < 0) return (-1); written += n; if (n == 0) break; } if (i) { if (i < len) memmove(out_compress_buf, out_compress_buf + i, len - i); out_compress->next_out = (Bytef*) out_compress_buf + len - i; } } return (written); } // End - MCCP //-------------------------------------------------------------------- // "Telopts" bool Socket::saveTelopts(xmlNodePtr rootNode) { rootNode = xml::newStringChild(rootNode, "Telopts"); xml::newNumChild(rootNode, "MCCP", getMccp()); xml::newNumChild(rootNode, "MSDP", getMsdp()); xml::newNumChild(rootNode, "ATCP", getAtcp()); xml::newBoolChild(rootNode, "MXP", getMxp()); xml::newBoolChild(rootNode, "DumbClient", isDumbClient()); xml::newStringChild(rootNode, "Term", getTermType()); xml::newNumChild(rootNode, "Color", getColorOpt()); xml::newNumChild(rootNode, "TermCols", getTermCols()); xml::newNumChild(rootNode, "TermRows", getTermRows()); xml::newBoolChild(rootNode, "EOR", getEor()); xml::newBoolChild(rootNode, "Charset", getCharset()); xml::newBoolChild(rootNode, "UTF8", getUtf8()); return (true); } bool Socket::loadTelopts(xmlNodePtr rootNode) { xmlNodePtr curNode = rootNode->children; while (curNode) { if (NODE_NAME(curNode, "MCCP")) { int mccp = 0; xml::copyToNum(mccp, curNode); if (mccp) { write(telnet::will_comp2, false); } } else if (NODE_NAME(curNode, "MXP")) xml::copyToBool(opts.mxp, curNode); else if (NODE_NAME(curNode, "Color")) xml::copyToNum(opts.color, curNode); else if (NODE_NAME(curNode, "MSDP")) xml::copyToBool(opts.msdp, curNode); else if (NODE_NAME(curNode, "ATCP")) { xml::copyToBool(opts.atcp, curNode); if (opts.atcp) { if (opts.msdp) { opts.atcp = false; } } } else if (NODE_NAME(curNode, "Term")) xml::copyToBString(term.type, curNode); else if (NODE_NAME(curNode, "DumbClient")) xml::copyToBool(opts.dumb, curNode); else if (NODE_NAME(curNode, "TermCols")) xml::copyToNum(term.cols, curNode); else if (NODE_NAME(curNode, "TermRows")) xml::copyToNum(term.rows, curNode); else if (NODE_NAME(curNode, "EOR")) xml::copyToBool(opts.eor, curNode); else if (NODE_NAME(curNode, "Charset")) xml::copyToBool(opts.charset, curNode); else if (NODE_NAME(curNode, "UTF8")) xml::copyToBool(opts.UTF8, curNode); curNode = curNode->next; } // We can only have one of these enabled, MSDP trumps if (opts.msdp) { // Re-negotiate MSDP after a reboot write(telnet::will_msdp, false); } else if (opts.atcp) { // Re-negotiate ATCP after a reboot write(telnet::do_atcp, false); } return (true); } //******************************************************************** // ANSI //******************************************************************** void Socket::ANSI(int color) { if (fd > -1 && myPlayer) { if (myPlayer->flagIsSet(P_ANSI_COLOR)) { if (color & BOLD) print("%c[%dm", 27, Ansi[COLOR_BOLD]); if (color & BLINK) print("%c[%dm", 27, Ansi[COLOR_BLINK]); if (color & NORMAL) print("%c[%dm", 27, Ansi[COLOR_NORMAL]); if (color & RED) print("%c[%dm", 27, Ansi[COLOR_RED]); else if (color & GREEN) print("%c[%dm", 27, Ansi[COLOR_GREEN]); else if (color & YELLOW) print("%c[%dm", 27, Ansi[COLOR_YELLOW]); else if (color & BLUE) print("%c[%dm", 27, Ansi[COLOR_BLUE]); else if (color & MAGENTA) print("%c[%dm", 27, Ansi[COLOR_MAGENTA]); else if (color & CYAN) print("%c[%dm", 27, Ansi[COLOR_CYAN]); else if (color & BLACK) print("%c[%dm", 27, Ansi[COLOR_BLACK]); else if (color & WHITE) print("%c[%dm", 27, Ansi[COLOR_WHITE]); } else if (myPlayer->flagIsSet(P_MIRC) && color != BLINK) { if (color & BOLD) print("%c", 2); if (color & UNDERLINE) print("%c", 31); if (color & NORMAL) print("%c%c", 3, 3); if (color & RED) print("%c%d", 3, Mirc[COLOR_RED]); else if (color & GREEN) print("%c%d", 3, Mirc[COLOR_GREEN]); else if (color & YELLOW) print("%c%d", 3, Mirc[COLOR_YELLOW]); else if (color & BLUE) print("%c%d", 3, Mirc[COLOR_BLUE]); else if (color & MAGENTA) print("%c%d", 3, Mirc[COLOR_MAGENTA]); else if (color & CYAN) print("%c%d", 3, Mirc[COLOR_CYAN]); else if (color & BLACK) print("%c%d", 3, Mirc[COLOR_BLACK]); else if (color & WHITE) print("%c%d", 3, Mirc[COLOR_WHITE]); } } } //******************************************************************** // hasOutput //******************************************************************** bool Socket::hasOutput(void) const { return (!processed_output.empty() || !output.empty()); } //******************************************************************** // hasCommand //******************************************************************** bool Socket::hasCommand(void) const { return (!input.empty()); } //******************************************************************** // canForce //******************************************************************** // True if the socket is playing (ie: fn is command and fnparam is 1) bool Socket::canForce(void) const { return (fn == (void(*)(Socket*, bstring)) ::command && fnparam == 1); } //******************************************************************** // get //******************************************************************** int Socket::getState(void) const { if (!this) return (UNUSED_STATE); return (connState); } bool Socket::isConnected() const { return (connState < LOGIN_START && connState != CON_DISCONNECTING); } bool Player::isConnected() const { return (getSock()->isConnected()); } int Socket::getFd(void) const { return (fd); } bool Socket::getMxp(void) const { return (opts.mxp); } bool Socket::getMxpClientSecure() const { return(opts.mxpClientSecure); } void Socket::clearMxpClientSecure() { opts.mxpClientSecure = false; } int Socket::getMccp(void) const { return (opts.mccp); } bool Socket::getMsdp() const { return (opts.msdp); } ; bool Socket::getAtcp() const { return (opts.atcp); } ; bool Socket::getMsp() const { return (opts.msp); } ; bool Socket::getCharset() const { return (opts.charset); } ; bool Socket::getUtf8() const { return (opts.UTF8); } bool Socket::getEor(void) const { return (opts.eor); } bool Socket::isDumbClient(void) const { return(opts.dumb); } bool Socket::getNaws(void) const { return (opts.naws); } long Socket::getIdle(void) const { return (time(0) - ltime); } const bstring& Socket::getIp(void) const { return (host.ip); } const bstring& Socket::getHostname(void) const { return (host.hostName); } bstring Socket::getTermType() const { return (term.type); } int Socket::getColorOpt() const { return(opts.color); } void Socket::setColorOpt(int opt) { opts.color = opt; } int Socket::getTermCols() const { return (term.cols); } int Socket::getTermRows() const { return (term.rows); } int Socket::getParam() { return (fnparam); } void Socket::setParam(int newParam) { fnparam = newParam; } void Socket::setHostname(bstring pName) { host.hostName = pName; } void Socket::setIp(bstring pIp) { host.ip = pIp; } void Socket::setPlayer(Player* ply) { myPlayer = ply; } Player* Socket::getPlayer() const { if (!this) return (NULL); return (myPlayer); } // MCCP Hooks void *zlib_alloc(void *opaque, unsigned int items, unsigned int size) { return calloc(items, size); } void zlib_free(void *opaque, void *address) { free(address); } //******************************************************************** // nonBlock //******************************************************************** int nonBlock(int pFd) { int flags; flags = fcntl(pFd, F_GETFL, 0); flags |= O_NONBLOCK; if (fcntl(pFd, F_SETFL, flags) < 0) return (-1); return (0); } //********************************************************************* // showLoginScreen //********************************************************************* void Socket::showLoginScreen(bool dnsDone) { //********************************************************************* // As a part of the copyright agreement this section must be left intact //********************************************************************* print( "The Realms of Hell (RoH beta v" VERSION ")\n\tBased on Mordor by Brett Vickers, Brooke Paul.\n"); print("Programmed by: Jason Mitchell, Randi Mitchell and Tim Callahan.\n"); print("Contributions by: Jonathan Hseu."); char file[80]; sprintf(file, "%s/login_screen.txt", Path::Config); viewLoginFile(this, file); if (dnsDone) checkLockOut(); flush(); } //******************************************************************** // askFor //******************************************************************** void Socket::askFor(const char *str) { ASSERTLOG( str); if (getEor() == 1) { char eor_str[] = { (char) IAC, (char) EOR, '\0' }; printColor(str); print(eor_str); } else { char ga_str[] = { (char) IAC, (char) GA, '\0' }; printColor(str); print(ga_str); } } unsigned const char mssp_val[] = { MSSP_VAL, '\0' }; unsigned const char mssp_var[] = { MSSP_VAR, '\0' }; void addMSSPVar(std::ostringstream& msspStr, bstring var) { msspStr << mssp_var << var; } template<class T> void addMSSPVal(std::ostringstream& msspStr, T val) { msspStr << mssp_val << val; } int Socket::sendMSSP() { std::cout << "Sending MSSP string\n"; std::ostringstream msspStr; msspStr << telnet::sb_mssp_start; addMSSPVar(msspStr, "NAME"); addMSSPVal<bstring>(msspStr, "The Realms of Hell"); addMSSPVar(msspStr, "PLAYERS"); addMSSPVal<int>(msspStr, gServer->getNumPlayers()); addMSSPVar(msspStr, "UPTIME"); addMSSPVal<long>(msspStr, StartTime); addMSSPVar(msspStr, "HOSTNAME"); addMSSPVal<bstring>(msspStr, "mud.rohonline.net"); addMSSPVar(msspStr, "PORT"); addMSSPVal<bstring>(msspStr, "23"); addMSSPVal<bstring>(msspStr, "3333"); addMSSPVar(msspStr, "CODEBASE"); addMSSPVal<bstring>(msspStr, "RoH beta v" VERSION); addMSSPVar(msspStr, "VERSION"); addMSSPVal<bstring>(msspStr, "RoH beta v" VERSION); addMSSPVar(msspStr, "CREATED"); addMSSPVal<bstring>(msspStr, "1998"); addMSSPVar(msspStr, "LANGUAGE"); addMSSPVal<bstring>(msspStr, "English"); addMSSPVar(msspStr, "LOCATION"); addMSSPVal<bstring>(msspStr, "United States"); addMSSPVar(msspStr, "WEBSITE"); addMSSPVal<bstring>(msspStr, "http://www.rohonline.net"); addMSSPVar(msspStr, "FAMILY"); addMSSPVal<bstring>(msspStr, "Mordor"); addMSSPVar(msspStr, "GENRE"); addMSSPVal<bstring>(msspStr, "Fantasy"); addMSSPVar(msspStr, "GAMEPLAY"); addMSSPVal<bstring>(msspStr, "Roleplaying"); addMSSPVal<bstring>(msspStr, "Hack and Slash"); addMSSPVal<bstring>(msspStr, "Adventure"); addMSSPVar(msspStr, "STATUS"); addMSSPVal<bstring>(msspStr, "Live"); addMSSPVar(msspStr, "GAMESYSTEM"); addMSSPVal<bstring>(msspStr, "Custom"); addMSSPVar(msspStr, "AREAS"); addMSSPVal<int>(msspStr, -1); addMSSPVar(msspStr, "HELPFILES"); addMSSPVal<int>(msspStr, 1000); addMSSPVar(msspStr, "MOBILES"); addMSSPVal<int>(msspStr, 5100); addMSSPVar(msspStr, "OBJECTS"); addMSSPVal<int>(msspStr, 7500); addMSSPVar(msspStr, "ROOMS"); addMSSPVal<int>(msspStr, 15000); addMSSPVar(msspStr, "CLASSES"); addMSSPVal<int>(msspStr, gConfig->classes.size()); addMSSPVar(msspStr, "LEVELS"); addMSSPVal<int>(msspStr, MAXALVL); addMSSPVar(msspStr, "RACES"); addMSSPVal<int>(msspStr, gConfig->getPlayableRaceCount()); addMSSPVar(msspStr, "SKILLS"); addMSSPVal<int>(msspStr, gConfig->skills.size()); addMSSPVar(msspStr, "GMCP"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "ATCP"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "SSL"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "ZMP"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "PUEBLO"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "MSDP"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "MSP"); addMSSPVal<bstring>(msspStr, "1"); // TODO: UTF-8: Change to 1 addMSSPVar(msspStr, "UTF-8"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "VT100"); addMSSPVal<bstring>(msspStr, "0"); // TODO: XTERM 256: Change to 1 addMSSPVar(msspStr, "XTERM 256 COLORS"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "ANSI"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "MCCP"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "MXP"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "PAY TO PLAY"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "PAY FOR PERKS"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "HIRING BUILDERS"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "HIRING CODERS"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "MULTICLASSING"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "NEWBIE FRIENDLY"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "PLAYER CLANS"); addMSSPVal<bstring>(msspStr, "0"); addMSSPVar(msspStr, "PLAYER CRAFTING"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "PLAYER GUILDS"); addMSSPVal<bstring>(msspStr, "1"); addMSSPVar(msspStr, "EQUIPMENT SYSTEM"); addMSSPVal<bstring>(msspStr, "Both"); addMSSPVar(msspStr, "MULTIPLAYING"); addMSSPVal<bstring>(msspStr, "Restricted"); addMSSPVar(msspStr, "PLAYERKILLING"); addMSSPVal<bstring>(msspStr, "Restricted"); addMSSPVar(msspStr, "QUEST SYSTEM"); addMSSPVal<bstring>(msspStr, "Integrated"); addMSSPVar(msspStr, "ROLEPLAYING"); addMSSPVal<bstring>(msspStr, "Encouraged"); addMSSPVar(msspStr, "TRAINING SYSTEM"); addMSSPVal<bstring>(msspStr, "Both"); addMSSPVar(msspStr, "WORLD ORIGINALITY"); addMSSPVal<bstring>(msspStr, "All Original"); msspStr << telnet::sb_mssp_end; return (write(msspStr.str())); }