package com.planet_ink.coffee_mud.Common; import com.planet_ink.coffee_mud.core.interfaces.*; import com.planet_ink.coffee_mud.core.threads.CMRunnable; import com.planet_ink.coffee_mud.core.*; import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag; import com.planet_ink.coffee_mud.core.CMSecurity.DisFlag; import com.planet_ink.coffee_mud.core.collections.*; import com.planet_ink.coffee_mud.Abilities.interfaces.*; import com.planet_ink.coffee_mud.Areas.interfaces.*; import com.planet_ink.coffee_mud.Behaviors.interfaces.*; import com.planet_ink.coffee_mud.CharClasses.interfaces.*; import com.planet_ink.coffee_mud.Commands.interfaces.*; import com.planet_ink.coffee_mud.Common.interfaces.*; import com.planet_ink.coffee_mud.Exits.interfaces.*; import com.planet_ink.coffee_mud.Items.interfaces.*; import com.planet_ink.coffee_mud.Libraries.interfaces.*; import com.planet_ink.coffee_mud.Libraries.interfaces.CharCreationLibrary.LoginResult; import com.planet_ink.coffee_mud.Libraries.interfaces.CharCreationLibrary.LoginSession; import com.planet_ink.coffee_mud.Libraries.interfaces.ColorLibrary.ColorState; import com.planet_ink.coffee_mud.Locales.interfaces.*; import com.planet_ink.coffee_mud.MOBS.interfaces.*; import com.planet_ink.coffee_mud.Races.interfaces.*; import com.jcraft.jzlib.*; import java.awt.Color; import java.io.*; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.sql.*; import java.net.*; import java.nio.charset.Charset; /* Copyright 2005-2016 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ @SuppressWarnings({"unchecked","rawtypes"}) public class DefaultSession implements Session { protected static final int SOTIMEOUT = 300; protected static final int PINGTIMEOUT = 30000; protected static final int MSDPPINGINTERVAL= 1000; protected static final char[] PINGCHARS = {0}; protected final Set<Integer> telnetSupportSet= new HashSet<Integer>(); protected final Set<String> mxpSupportSet = new HashSet<String>(); protected final Map<String,String> mxpVersionInfo = new Hashtable<String,String>(); protected final Map<Object, Object> msdpReportables = new TreeMap<Object,Object>(); protected final Map<String, Double> gmcpSupports = new TreeMap<String,Double>(); protected final Map<String, Long> gmcpPings = new TreeMap<String,Long>(); protected final String[] mcpKey = new String[1]; protected final Map<String,String> mcpKeyPairs = new TreeMap<String,String>(); protected final boolean mcpDisabled = CMSecurity.isDisabled(DisFlag.MCP); protected final Map<String,float[]> mcpSupported = new TreeMap<String,float[]>(); private static final String TIMEOUT_MSG = "Timed Out."; private volatile Thread runThread = null; private volatile Thread writeThread = null; protected String groupName = null; protected SessionStatus status = SessionStatus.HANDSHAKE_OPEN; protected long lastStateChangeMs = System.currentTimeMillis(); protected int snoopSuspensionStack= 0; protected final Socket[] sock = new Socket[1]; protected SesInputStream charWriter; protected int inMaxBytesPerChar = 1; protected BufferedReader in; protected PrintWriter out; protected InputStream rawin; protected OutputStream rawout; protected MOB mob; protected PlayerAccount acct = null; protected boolean killFlag = false; protected boolean needPrompt = false; protected boolean afkFlag = false; protected String afkMessage = null; protected StringBuffer input = new StringBuffer(""); protected StringBuffer fakeInput = null; protected boolean waiting = false; protected List<String> previousCmd = new Vector<String>(); protected String[] clookup = null; protected String lastColorStr = ""; protected String lastStr = null; protected int spamStack = 0; protected List<Session> snoops = new Vector<Session>(); protected List<String> prevMsgs = new Vector<String>(); protected StringBuffer curPrevMsg = null; protected boolean lastWasCR = false; protected boolean lastWasLF = false; protected boolean suspendCommandLine = false; protected boolean[] serverTelnetCodes = new boolean[256]; protected boolean[] clientTelnetCodes = new boolean[256]; protected String terminalType = "UNKNOWN"; protected int terminalWidth = -1; protected int terminalHeight = -1; protected long writeStartTime = 0; protected boolean bNextByteIs255 = false; protected boolean connectionComplete = false; protected ReentrantLock writeLock = new ReentrantLock(true); protected LoginSession loginSession = null; protected boolean[] loggingOutObj = new boolean[]{false}; protected byte[] promptSuffix = new byte[0]; protected ColorState currentColor = null; protected ColorState lastColor = null; protected long lastStart = System.currentTimeMillis(); protected long lastStop = System.currentTimeMillis(); protected long lastLoopTop = System.currentTimeMillis(); protected long nextMsdpPing = System.currentTimeMillis(); protected long userLoginTime = System.currentTimeMillis(); protected long onlineTime = System.currentTimeMillis(); protected long activeMillis = 0; protected long lastPKFight = 0; protected long lastNPCFight = 0; protected long lastBlahCheck = 0; protected long milliTotal = 0; protected long tickTotal = 0; protected long lastKeystroke = 0; protected long lastIACIn = System.currentTimeMillis(); protected long promptLastShown = 0; protected char threadGroupChar = '\0'; protected volatile long lastWriteTime = System.currentTimeMillis(); protected boolean debugStrInput = true; protected boolean debugBinOutput = false; protected boolean debugBinInput = false; protected StringBuffer debugBinInputBuf = new StringBuffer(""); protected volatile InputCallback inputCallback = null; @Override public String ID() { return "DefaultSession"; } @Override public String name() { return ID(); } @Override public CMObject newInstance() { try { return getClass().newInstance(); } catch (final Exception e) { return new DefaultSession(); } } @Override public void initializeClass() { } @Override public boolean isFake() { return false; } @Override public CMObject copyOf() { try { final Object O = this.clone(); return (CMObject) O; } catch (final Exception e) { return newInstance(); } } @Override public int compareTo(CMObject o) { return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o)); } public DefaultSession() { threadGroupChar=Thread.currentThread().getThreadGroup().getName().charAt(0); } @Override public void setStatus(SessionStatus newStatus) { synchronized(status) { if(status!=newStatus) { status=newStatus; lastStateChangeMs=System.currentTimeMillis(); } } } @Override public long getStartTime() { return this.lastStart; } @Override public int getGroupID() { return this.threadGroupChar; } @Override public boolean isAllowedMcp(String packageName, float version) { float[] chk = mcpSupported.get(packageName); if((chk == null)||(version < chk[0])||(version>chk[1])||(mcpKey[0] == null)) { return false; } return true; } @Override public boolean sendMcpCommand(String packageCommand, String parms) { if(mcpKey[0] != null) { rawPrintln("#$#"+packageCommand+" "+mcpKey[0]+" "+parms); return true; } return false; } @Override public void initializeSession(final Socket s, final String groupName, final String introTextStr) { this.groupName=groupName; sock[0]=s; promptSuffix = CMProps.getPromptSuffix(); currentColor = CMLib.color().getNormalColor(); lastColor = CMLib.color().getNormalColor(); try { setStatus(SessionStatus.HANDSHAKE_OPEN); debugStrInput = CMSecurity.isDebugging(CMSecurity.DbgFlag.INPUT); debugBinOutput = CMSecurity.isDebugging(CMSecurity.DbgFlag.BINOUT); debugBinInput = CMSecurity.isDebugging(CMSecurity.DbgFlag.BININ); if(debugBinInput) { CMLib.threads().startTickDown(new Tickable() { @Override public String ID() { return "SessionTicker";} @Override public CMObject newInstance() { return null; } @Override public CMObject copyOf() { return null; } @Override public void initializeClass() {} @Override public int compareTo(CMObject o) { return 0;} @Override public String name() { return ID(); } @Override public int getTickStatus() { return 0; } @Override public boolean tick(Tickable ticking, int tickID) { if(debugBinInputBuf.length()>0) { Log.sysOut("BINPUT: "+(mob==null?"":mob.Name())+": '"+debugBinInputBuf.toString()+"'"); debugBinInputBuf.setLength(0); } return !killFlag; } }, 0, 100, 1); } sock[0].setSoTimeout(SOTIMEOUT); rawout=new BufferedOutputStream(sock[0].getOutputStream()); rawin=new BufferedInputStream(sock[0].getInputStream()); if(!mcpDisabled) { rawBytesOut(rawout,("\n\r#$#mcp version: 2.1 to: 2.1\n\r").getBytes(CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); } rawBytesOut(rawout,("\n\rConnecting to "+CMProps.getVar(CMProps.Str.MUDNAME)+"...\n\r").getBytes("US-ASCII")); rawout.flush(); setServerTelnetMode(TELNET_ANSI,true); setClientTelnetMode(TELNET_ANSI,true); setClientTelnetMode(TELNET_TERMTYPE,true); negotiateTelnetMode(rawout,TELNET_TERMTYPE); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.MCCP)) changeTelnetMode(rawout,TELNET_COMPRESS2,true); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.MXP)) changeTelnetMode(rawout,TELNET_MXP,true); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.GMCP)) changeTelnetMode(rawout,TELNET_GMCP,true); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.MSP)) changeTelnetMode(rawout,TELNET_MSP,true); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.MSDP)) changeTelnetMode(rawout,TELNET_MSDP,true); //changeTelnetMode(rawout,TELNET_SUPRESS_GO_AHEAD,true); changeTelnetMode(rawout,TELNET_NAWS,true); //changeTelnetMode(rawout,TELNET_BINARY,true); if(mightSupportTelnetMode(TELNET_GA)) rawBytesOut(rawout,TELNETGABYTES); rawout.flush(); final Charset charSet=Charset.forName(CMProps.getVar(CMProps.Str.CHARSETINPUT)); inMaxBytesPerChar=(int)Math.round(Math.ceil(charSet.newEncoder().maxBytesPerChar())); charWriter=new SesInputStream(inMaxBytesPerChar); in=new BufferedReader(new InputStreamReader(charWriter,charSet)); out=new PrintWriter(new OutputStreamWriter(rawout,CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); prompt(new TickingCallback(250) { private final long firstIACIn=lastIACIn; @Override public boolean tick(int counter) { try { if(out!=null) { out.flush(); rawout.flush(); } else { killFlag=true; return false; } switch(status) { case HANDSHAKE_OPEN: { if((!terminalType.equalsIgnoreCase("ANSI"))&&(getClientTelnetMode(TELNET_ECHO))) changeTelnetModeBackwards(rawout,TELNET_ECHO,false); rawout.flush(); setStatus(SessionStatus.HANDSHAKE_MCCP); break; } case HANDSHAKE_MCCP: { if(((lastIACIn>firstIACIn)&&((System.currentTimeMillis()-lastIACIn)>500)) ||((System.currentTimeMillis()-lastIACIn)>5000)) { if(getClientTelnetMode(TELNET_COMPRESS2)) { negotiateTelnetMode(rawout,TELNET_COMPRESS2); rawout.flush(); if(getClientTelnetMode(TELNET_COMPRESS2)) { final ZOutputStream zOut=new ZOutputStream(rawout, JZlib.Z_DEFAULT_COMPRESSION); rawout=zOut; zOut.setFlushMode(JZlib.Z_SYNC_FLUSH); out = new PrintWriter(new OutputStreamWriter(zOut,CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("MCCP compression started"); } } setStatus(SessionStatus.HANDSHAKE_MXP); } break; } case HANDSHAKE_MXP: { if(!getClientTelnetMode(Session.TELNET_MXP)) setStatus(SessionStatus.HANDSHAKE_DONE); else { rawOut("\n\033[6z\n\033[6z<SUPPORT IMAGE IMAGE.URL>\n"); rawout.flush(); rawOut("\n\033[6z\n\033[6z<SUPPORT>\n"); rawout.flush(); setStatus(SessionStatus.HANDSHAKE_MXPPAUSE); } break; } case HANDSHAKE_MXPPAUSE: { if(((System.currentTimeMillis()-lastStateChangeMs)>2000) ||(mxpSupportSet.contains("+IMAGE.URL")||mxpSupportSet.contains("+IMAGE")||mxpSupportSet.contains("-IMAGE.URL"))) setStatus(SessionStatus.HANDSHAKE_DONE); break; } case HANDSHAKE_DONE: { if(introTextStr!=null) print(introTextStr); if(out!=null) { out.flush(); rawout.flush(); if((getClientTelnetMode(Session.TELNET_MXP)) &&((mxpSupportSet.contains("+IMAGE.URL")) ||((mxpSupportSet.contains("+IMAGE"))&&(!mxpSupportSet.contains("-IMAGE.URL"))))) { // also the intro page final String[] paths=CMLib.protocol().mxpImagePath("intro.jpg"); if(paths[0].length()>0) { final CMFile introDir=new CMFile("/web/pub/images/mxp",null,CMFile.FLAG_FORCEALLOW); String introFilename=paths[1]; if(introDir.isDirectory()) { final CMFile[] files=introDir.listFiles(); final Vector<String> choices=new Vector<String>(); for (final CMFile file : files) { if(file.getName().toLowerCase().startsWith("intro") &&file.getName().toLowerCase().endsWith(".jpg")) choices.addElement(file.getName()); } if(choices.size()>0) introFilename=choices.elementAt(CMLib.dice().roll(1,choices.size(),-1)); } println("\n\r\n\r\n\r^<IMAGE '"+introFilename+"' URL='"+paths[0]+"' H=400 W=400^>\n\r\n\r"); if(out!=null) { out.flush(); rawout.flush(); } } } } } //$FALL-THROUGH$ default: connectionComplete=true; status=SessionStatus.LOGIN; if(collectedInput.length()>0) fakeInput=new StringBuffer(collectedInput.toString()); return false; } return (out!=null); } catch(final Exception e) { if(e.getMessage()==null) Log.errOut(e); else Log.errOut(e.getMessage()); } connectionComplete=true; status=SessionStatus.LOGIN; return false; } }); } catch(final Exception e) { if(e.getMessage()==null) Log.errOut(e); else Log.errOut(e.getMessage()); } } protected void compress2Off() throws IOException { changeTelnetMode(rawout,TELNET_COMPRESS2,false); out.flush(); rawout.flush(); rawout=new BufferedOutputStream(sock[0].getOutputStream()); out = new PrintWriter(new OutputStreamWriter(rawout,CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); CMLib.s_sleep(50); changeTelnetMode(rawout,TELNET_COMPRESS2,false); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("MCCP compression stopped"); } @Override public String getGroupName() { return groupName; } @Override public void setGroupName(String group) { groupName=group; } @Override public void setFakeInput(String input) { if(fakeInput!=null) fakeInput.append(input); else fakeInput=new StringBuffer(input); } private void negotiateTelnetMode(OutputStream out, int optionCode) throws IOException { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Sent sub-option: "+Session.TELNET_DESCS[optionCode]); if(optionCode==TELNET_TERMTYPE) { final byte[] stream={(byte)TELNET_IAC,(byte)TELNET_SB,(byte)optionCode,(byte)1,(byte)TELNET_IAC,(byte)TELNET_SE}; rawBytesOut(out, stream); } else { final byte[] stream={(byte)TELNET_IAC,(byte)TELNET_SB,(byte)optionCode,(byte)TELNET_IAC,(byte)TELNET_SE}; rawBytesOut(out, stream); } out.flush(); } private boolean mightSupportTelnetMode(int telnetCode) { if(telnetSupportSet.size()==0) { telnetSupportSet.add(Integer.valueOf(Session.TELNET_MXP)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_MSP)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_MSDP)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_GMCP)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_TERMTYPE)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_BINARY)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_ECHO)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_LOGOUT)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_TERMTYPE)); telnetSupportSet.add(Integer.valueOf(Session.TELNET_NAWS)); //telnetSupportSet.add(Integer.valueOf(Session.TELNET_GA)); //telnetSupportSet.add(Integer.valueOf(Session.TELNET_SUPRESS_GO_AHEAD)); //telnetSupportSet.add(Integer.valueOf(Session.TELNET_COMPRESS2)); //telnetSupportSet.add(Integer.valueOf(Session.TELNET_LINEMODE)); } return telnetSupportSet.contains(Integer.valueOf(telnetCode)); } @Override public void setServerTelnetMode(int telnetCode, boolean onOff) { serverTelnetCodes[telnetCode]=onOff; } @Override public boolean getServerTelnetMode(int telnetCode) { return serverTelnetCodes[telnetCode]; } @Override public void setClientTelnetMode(int telnetCode, boolean onOff) { clientTelnetCodes[telnetCode]=onOff; } @Override public boolean getClientTelnetMode(int telnetCode) { return clientTelnetCodes[telnetCode]; } private void changeTelnetMode(OutputStream out, int telnetCode, boolean onOff) throws IOException { final byte[] command={(byte)TELNET_IAC,onOff?(byte)TELNET_WILL:(byte)TELNET_WONT,(byte)telnetCode}; rawBytesOut(out, command); out.flush(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Sent: "+(onOff?"Will":"Won't")+" "+Session.TELNET_DESCS[telnetCode]); setServerTelnetMode(telnetCode,onOff); } @Override public boolean isAllowedMxp(final String tagString) { if(tagString.startsWith("^<")) { if((!getClientTelnetMode(TELNET_MXP))||(mxpSupportSet.size()==0)) return false; int x=tagString.indexOf(' '); if(x<0) x=tagString.indexOf('>'); if((x>0)&&(this.mxpSupportSet.contains("-"+tagString.substring(2,x)))) return false; } else if(tagString.startsWith("&")) { if(terminalType.equalsIgnoreCase("mushclient")) return true; } return true; } @Override public void sendGMCPEvent(final String eventName, final String json) { if((!getClientTelnetMode(TELNET_GMCP))||(gmcpSupports.size()==0)) return; try { final String lowerEventName=eventName.toLowerCase().trim(); final int x=lowerEventName.lastIndexOf('.'); if((x<0)&&(!gmcpSupports.containsKey(lowerEventName))) return; if((!gmcpSupports.containsKey(lowerEventName)) && (!gmcpSupports.containsKey(lowerEventName.substring(0, x)))) return; if(CMSecurity.isDebugging(DbgFlag.TELNET)) Log.debugOut("GMCP Sent: "+(lowerEventName+" "+json)); rawBytesOut(rawout,TELNETBYTES_GMCP_HEAD); rawBytesOut(rawout,(lowerEventName+" "+json).getBytes()); rawBytesOut(rawout,TELNETBYTES_END_SB); } catch(final IOException e) { killFlag=true; } catch(final Exception e) { Log.errOut(e); } } // this is stupid, but a printwriter can not be cast as an outputstream, so this dup was necessary @Override public void changeTelnetMode(int telnetCode, boolean onOff) { try { final byte[] command={(byte)TELNET_IAC,onOff?(byte)TELNET_WILL:(byte)TELNET_WONT,(byte)telnetCode}; out.flush(); rawBytesOut(rawout, command); rawout.flush(); } catch(final Exception e){} if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Sent: "+(onOff?"Will":"Won't")+" "+Session.TELNET_DESCS[telnetCode]); setServerTelnetMode(telnetCode,onOff); } public void changeTelnetModeBackwards(int telnetCode, boolean onOff) throws IOException { final byte[] command={(byte)TELNET_IAC,onOff?(byte)TELNET_DO:(byte)TELNET_DONT,(byte)telnetCode}; out.flush(); rawBytesOut(rawout, command); rawout.flush(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Back-Sent: "+(onOff?"Do":"Don't")+" "+Session.TELNET_DESCS[telnetCode]); setServerTelnetMode(telnetCode,onOff); } public void changeTelnetModeBackwards(OutputStream out, int telnetCode, boolean onOff) throws IOException { final byte[] command={(byte)TELNET_IAC,onOff?(byte)TELNET_DO:(byte)TELNET_DONT,(byte)telnetCode}; rawBytesOut(out, command); out.flush(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Back-Sent: "+(onOff?"Do":"Don't")+" "+Session.TELNET_DESCS[telnetCode]); setServerTelnetMode(telnetCode,onOff); } @Override public void negotiateTelnetMode(int telnetCode) { try { out.flush(); if(telnetCode==TELNET_TERMTYPE) { final byte[] command={(byte)TELNET_IAC,(byte)TELNET_SB,(byte)telnetCode,(byte)1,(byte)TELNET_IAC,(byte)TELNET_SE}; rawBytesOut(rawout, command); } else { final byte[] command={(byte)TELNET_IAC,(byte)TELNET_SB,(byte)telnetCode,(byte)TELNET_IAC,(byte)TELNET_SE}; rawBytesOut(rawout, command); } rawout.flush(); } catch(final Exception e){} if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Negotiate-Sent: "+Session.TELNET_DESCS[telnetCode]); } private void mushClientTurnOffMXPFix() { try { // i don't know why this works, but it does clearInputBuffer(500); Command C=CMClass.getCommand("MXP"); C.execute(mob, new XVector<String>("MXP","QUIET"), 0); clearInputBuffer(500); C=CMClass.getCommand("NOMXP"); C.execute(mob, new XVector<String>("NOMXP","QUIET"), 0); } catch(Exception e) { } } @Override public void initTelnetMode(int attributesBitmap) { setServerTelnetMode(TELNET_ANSI,CMath.bset(attributesBitmap,MOB.Attrib.ANSI.getBitCode())); setClientTelnetMode(TELNET_ANSI,CMath.bset(attributesBitmap,MOB.Attrib.ANSI.getBitCode())); boolean changedSomething=false; final boolean mxpSet=(!CMSecurity.isDisabled(CMSecurity.DisFlag.MXP))&&CMath.bset(attributesBitmap,MOB.Attrib.MXP.getBitCode()); if(mxpSet!=getClientTelnetMode(TELNET_MXP)) { changeTelnetMode(TELNET_MXP,!getClientTelnetMode(TELNET_MXP)); changedSomething=true; if((!mxpSet)&&(getTerminalType()!=null)&&(getTerminalType().equals("mushclient"))) mushClientTurnOffMXPFix(); } final boolean mspSet=(!CMSecurity.isDisabled(CMSecurity.DisFlag.MSP))&&CMath.bset(attributesBitmap,MOB.Attrib.SOUND.getBitCode()); if(mspSet!=getClientTelnetMode(TELNET_MSP)) { changeTelnetMode(TELNET_MSP,!getClientTelnetMode(TELNET_MSP)); changedSomething=true; } if(changedSomething) clearInputBuffer(500); } @Override public ColorState getCurrentColor() { return currentColor; } @Override public void setCurrentColor(final ColorState newColor) { if(newColor!=null) currentColor=newColor; } @Override public ColorState getLastColor() { return lastColor; } @Override public void setLastColor(final ColorState newColor) { if(newColor!=null) lastColor=newColor; } @Override public long getTotalMillis() { return milliTotal; } @Override public long getIdleMillis() { return System.currentTimeMillis() - lastKeystroke; } @Override public long getTotalTicks() { return tickTotal; } @Override public long getMillisOnline() { return System.currentTimeMillis() - onlineTime; } @Override public long getInputLoopTime() { return lastLoopTop; } @Override public void setInputLoopTime() { lastLoopTop = System.currentTimeMillis(); } @Override public long getLastPKFight() { return lastPKFight; } @Override public void setLastPKFight() { lastPKFight = System.currentTimeMillis(); } @Override public long getLastNPCFight() { return lastNPCFight; } @Override public void setLastNPCFight() { lastNPCFight = System.currentTimeMillis(); } @Override public List<String> getLastMsgs() { return new XVector(prevMsgs); } @Override public String getTerminalType() { return terminalType; } @Override public MOB mob() { return mob; } @Override public void setMob(MOB newmob) { mob=newmob; } @Override public void setAccount(PlayerAccount account) { acct=account; } @Override public int getWrap() { if(terminalWidth>5) return terminalWidth; return ((mob!=null)&&(mob.playerStats()!=null))?mob.playerStats().getWrap():78; } public int getPageBreak() { if(((mob!=null)&&(mob.playerStats()!=null))) { final int pageBreak=mob.playerStats().getPageBreak(); if(pageBreak <= 0) return pageBreak; if(terminalHeight>3) return terminalHeight; return pageBreak; } return -1; } @Override public boolean isStopped() { return killFlag; } public void setKillFlag(boolean truefalse) { killFlag=truefalse; } @Override public List<String> getPreviousCMD() { return previousCmd; } @Override public void setBeingSnoopedBy(Session session, boolean onOff) { if(onOff) { if(!snoops.contains(session)) snoops.add(session); } else { while(snoops.contains(session)) snoops.remove(session); } } @Override public boolean isBeingSnoopedBy(Session S) { if(S==null) return snoops.size()==0; return(snoops.contains(S)); } @Override public synchronized int snoopSuspension(int change) { snoopSuspensionStack+=change; return snoopSuspensionStack; } private int metaFlags() { return ((snoops.size()>0)?MUDCmdProcessor.METAFLAG_SNOOPED:0) |(((mob!=null)&&(mob.soulMate()!=null))?MUDCmdProcessor.METAFLAG_POSSESSED:0); } public void setPreviousCmd(List<String> cmds) { if(cmds==null) return; if(cmds.size()==0) return; if((cmds.size()>0)&&(cmds.get(0).trim().startsWith("!"))) return; previousCmd.clear(); for(int i=0;i<cmds.size();i++) previousCmd.add((cmds.get(i))); } @Override public boolean isAfk() { return afkFlag; } @Override public void setAfkFlag(boolean truefalse) { if(afkFlag==truefalse) return; afkFlag=truefalse; if(afkFlag) println("\n\rYou are now listed as AFK."); else { afkMessage=null; println("\n\rYou are no longer AFK."); } } @Override public String getAfkMessage() { if(mob==null) return ""; if((afkMessage==null)||(CMStrings.removeColors(afkMessage).trim().length()==0)) return mob.name()+" is AFK at the moment."; return afkMessage; } @Override public void setAFKMessage(String str){afkMessage=str;} protected void errorOut(Exception t) { Log.errOut(t); CMLib.sessions().remove(this); setKillFlag(true); } protected long getWriteStartTime(){return writeStartTime;} @Override public boolean isLockedUpWriting() { final long time=writeStartTime; if(time==0) return false; return ((System.currentTimeMillis()-time)>10000); } public final void rawBytesOut(final OutputStream out, final byte[] bytes) throws IOException { try { if(debugBinOutput && Log.debugChannelOn()) { final StringBuilder str=new StringBuilder("OUTPUT: '"); for(final byte c : bytes) str.append(c).append(" "); Log.debugOut( str.toString()+"'"); } out.write(bytes); } finally { lastWriteTime=System.currentTimeMillis(); } } @Override public void rawCharsOut(char[] chars) { rawCharsOut(out,chars); } public void rawCharsOut(final PrintWriter out, char[] chars) { if((out==null)||(chars==null)||(chars.length==0)) return; try { if(writeLock.tryLock(10000, TimeUnit.MILLISECONDS)) { try { writeThread=Thread.currentThread(); writeStartTime=System.currentTimeMillis(); if(debugBinOutput && Log.debugChannelOn()) { final StringBuilder str=new StringBuilder("OUTPUT: '"); for(final char c : chars) str.append(c); Log.debugOut( str.toString()+"'"); } out.write(chars); if(out.checkError()) stopSession(true,true,false); } finally { writeThread=null; writeStartTime=0; lastWriteTime=System.currentTimeMillis(); writeLock.unlock(); } } else if(!killFlag) { final String name=(mob!=null)?mob.Name():getAddress(); Log.errOut("DefaultSession","Kicked out "+name+" due to write-lock ("+out.getClass().getName()+"."); stopSession(true,true,true); final Thread killThisThread=writeThread; if(killThisThread!=null) CMLib.killThread(killThisThread,500,1); } } catch(final Exception ioe){ setKillFlag(true);} } public void rawCharsOut(String c) { if(c!=null) rawCharsOut(c.toCharArray()); } public void rawCharsOut(char c) { final char[] cs={c}; rawCharsOut(cs); } public void snoopSupportPrint(final String msg, final boolean noCache) { try { if((snoops.size()>0)&&(snoopSuspensionStack<=0)) { String msgColored; final String preFix=CMLib.coffeeFilter().colorOnlyFilter("^Z"+((mob==null)?"?":mob.Name())+":^N ",this); final int crCheck=msg.indexOf('\n'); if((crCheck>=0)&&(crCheck<msg.length()-2)) { final StringBuffer buf=new StringBuffer(msg); for(int i=buf.length()-1;i>=0;i--) if((buf.charAt(i)=='\n')&&(i<buf.length()-2)&&(buf.charAt(i+1)=='\r')) buf.insert(i+2, preFix); msgColored=buf.toString(); } else msgColored=msg; for(int s=0;s<snoops.size();s++) snoops.get(s).onlyPrint(preFix+msgColored,noCache); } }catch(final IndexOutOfBoundsException x){} } @Override public void onlyPrint(String msg) { onlyPrint(msg,false); } @Override public void onlyPrint(String msg, boolean noCache) { if((out==null)||(msg==null)) return; try { snoopSupportPrint(msg,noCache); final String newMsg=CMLib.lang().finalTranslation(msg); if(newMsg!=null) msg=newMsg; if(msg.endsWith("\n\r") &&(msg.equals(lastStr)) &&(msg.length()>2) &&(msg.indexOf("\n")==(msg.length()-2))) { spamStack++; return; } else if(spamStack>0) { if(spamStack>1) lastStr=lastStr.substring(0,lastStr.length()-2)+"("+spamStack+")"+lastStr.substring(lastStr.length()-2); rawCharsOut(lastStr.toCharArray()); } spamStack=0; if(msg.startsWith("\n\r")&&(msg.length()>2)) lastStr=msg.substring(2); else lastStr=msg; if(runThread==Thread.currentThread()) { final int pageBreak=getPageBreak(); int lines=0; int last=0; if(pageBreak>0) for(int i=0;i<msg.length();i++) { if(msg.charAt(i)=='\n') { lines++; if(lines>=pageBreak) { lines=0; if((i<(msg.length()-1)&&(msg.charAt(i+1)=='\r'))) i++; rawCharsOut(msg.substring(last,i+1).toCharArray()); last=i+1; rawCharsOut("<pause - enter>".toCharArray()); try { String s=blockingIn(-1, true); if(s!=null) { s=s.toLowerCase(); if(s.startsWith("qu")||s.startsWith("ex")||s.equals("x")) return; } }catch(final Exception e){return;} } } } } // handle line cache -- if(!noCache) for(int i=0;i<msg.length();i++) { if(curPrevMsg==null) curPrevMsg=new StringBuffer(""); if(msg.charAt(i)=='\r') continue; if(msg.charAt(i)=='\n') { if(curPrevMsg.toString().trim().length()>0) { synchronized(prevMsgs) { while(prevMsgs.size()>=MAX_PREVMSGS) prevMsgs.remove(0); prevMsgs.add(curPrevMsg.toString()); curPrevMsg.setLength(0); } } continue; } curPrevMsg.append(msg.charAt(i)); } rawCharsOut(msg.toCharArray()); } catch(final java.lang.NullPointerException e){} } @Override public void rawOut(String msg) { rawCharsOut(msg); } @Override public void rawPrint(String msg) { if(msg==null) return; onlyPrint((needPrompt?"":"\n\r")+msg,false); needPrompt=true; } @Override public void safeRawPrint(String msg) { if(msg==null) return; onlyPrint((needPrompt?"":"\n\r")+CMLib.coffeeFilter().mxpSafetyFilter(msg, this),false); needPrompt=true; } @Override public void print(String msg) { onlyPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,mob,mob,null,msg,false),false); } @Override public void promptPrint(String msg) { print(msg); if(promptSuffix.length>0) { try { rawBytesOut(rawout, promptSuffix); } catch (IOException e) { } } if((!getClientTelnetMode(TELNET_SUPRESS_GO_AHEAD)) && (!killFlag) && mightSupportTelnetMode(TELNET_GA)) { try { rawBytesOut(rawout, TELNETGABYTES); } catch(final Exception e){} } } @Override public void rawPrintln(String msg) { if(msg!=null) rawPrint(msg+"\n\r"); } @Override public void safeRawPrintln(String msg) { if(msg!=null) safeRawPrint(msg+"\n\r"); } @Override public void stdPrint(String msg) { rawPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,mob,mob,null,msg,false)); } @Override public void print(Physical src, Environmental trg, Environmental tol, String msg) { onlyPrint((CMLib.coffeeFilter().fullOutFilter(this,mob,src,trg,tol,msg,false)),false); } @Override public void stdPrint(Physical src, Environmental trg, Environmental tol, String msg) { rawPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,src,trg,trg,msg,false)); } @Override public void println(String msg) { if(msg!=null) print(msg+"\n\r"); } @Override public void wraplessPrintln(String msg) { if(msg!=null) onlyPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,mob,mob,null,msg,true)+"\n\r",false); needPrompt=true; } @Override public void wraplessPrint(String msg) { if(msg!=null) onlyPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,mob,mob,null,msg,true),false); needPrompt=true; } @Override public void colorOnlyPrintln(String msg) { colorOnlyPrint(msg+"\n\r",false); } @Override public void colorOnlyPrintln(String msg, boolean noCache) { if(msg!=null) onlyPrint(CMLib.coffeeFilter().colorOnlyFilter(msg,this)+"\n\r",noCache); needPrompt=true; } @Override public void colorOnlyPrint(String msg) { colorOnlyPrint(msg,false); } @Override public void colorOnlyPrint(String msg, boolean noCache) { if(msg!=null) onlyPrint(CMLib.coffeeFilter().colorOnlyFilter(msg,this),noCache); needPrompt=true; } @Override public void stdPrintln(String msg) { if(msg!=null) rawPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,mob,mob,null,msg,false)+"\n\r"); } @Override public void println(Physical src, Environmental trg, Environmental tol, String msg) { if(msg!=null) onlyPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,src,trg,tol,msg,false)+"\n\r",false); } @Override public void stdPrintln(Physical src,Environmental trg, Environmental tol, String msg) { if(msg!=null) rawPrint(CMLib.coffeeFilter().fullOutFilter(this,mob,src,trg,tol,msg,false)+"\n\r"); } @Override public void setPromptFlag(boolean truefalse) { needPrompt=truefalse; } @Override public String prompt(String Message, String Default, long maxTime) throws IOException { final String Msg=prompt(Message,maxTime).trim(); if(Msg.equals("")) return Default; return Msg; } @Override public String prompt(String Message, String Default) throws IOException { final String Msg=prompt(Message,-1).trim(); if(Msg.equals("")) return Default; return Msg; } @Override public void prompt(InputCallback callBack) { if(callBack!=null) callBack.showPrompt(); if(this.inputCallback!=null) this.inputCallback.timedOut(); this.inputCallback=callBack; } @Override public String prompt(String Message, long maxTime) throws IOException { promptPrint(Message); final String input=blockingIn(maxTime, true); if(input==null) return ""; if((input.length()>0)&&(input.charAt(input.length()-1)=='\\')) return input.substring(0,input.length()-1); return input; } @Override public String prompt(String Message) throws IOException { promptPrint(Message); final String input=blockingIn(-1, true); if(input==null) return ""; if((input.length()>0)&&(input.charAt(input.length()-1)=='\\')) return input.substring(0,input.length()-1); return input; } @Override public String[] getColorCodes() { if(clookup==null) clookup=CMLib.color().standardColorLookups(); if(mob()==null) return clookup; PlayerStats pstats=mob().playerStats(); if((mob.soulMate()!=null)&&(mob.soulMate().playerStats()!=null)) pstats=mob.soulMate().playerStats(); if(pstats==null) return clookup; if(!pstats.getColorStr().equals(lastColorStr)) { lastColorStr=pstats.getColorStr(); if(pstats.getColorStr().length()==0) clookup=CMLib.color().standardColorLookups(); else { clookup=CMLib.color().standardColorLookups().clone(); List<String> changesList = CMParms.parseAny(pstats.getColorStr(), '#', true); for(String change : changesList) { int subChar; String subColor; if(change.startsWith("(") && (change.indexOf(')')>0)) { int x=change.indexOf(')'); subChar=CMath.s_int(change.substring(1,x)); subColor = change.substring(x+1); } else { subChar=change.charAt(0); subColor=change.substring(1); } clookup[subChar]=CMLib.color().translateCMCodeToANSI(subColor); } for(int i=0;i<clookup.length;i++) { final String s=clookup[i]; if((s!=null)&&(s.startsWith("^"))&&(s.length()>1)) clookup[i]=clookup[s.charAt(1)]; } } } return clookup; } public void handleSubOption(int optionCode, char[] suboptionData, int dataSize) throws IOException { switch(optionCode) { case TELNET_TERMTYPE: if(dataSize >= 1) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("For suboption "+Session.TELNET_DESCS[optionCode]+", got code "+((int)suboptionData[0])+": "+new String(suboptionData, 1, dataSize - 1)); if(suboptionData[0] == 0) { terminalType = new String(suboptionData, 1, dataSize - 1); if(terminalType.equalsIgnoreCase("ZMUD") ||terminalType.equalsIgnoreCase("CMUD") ||terminalType.equalsIgnoreCase("XTERM")) { if(mightSupportTelnetMode(TELNET_ECHO)) telnetSupportSet.remove(Integer.valueOf(TELNET_ECHO)); changeTelnetMode(rawout,TELNET_ECHO,false); } else if(terminalType.equalsIgnoreCase("ANSI")) changeTelnetMode(rawout,TELNET_ECHO,true); else if(terminalType.startsWith("GIVE-WINTIN.NET-A-CHANCE")) { rawOut("\n\r\n\r**** Your MUD Client is Broken! Please use another!!****\n\r\n\r"); rawout.flush(); CMLib.s_sleep(1000); rawout.close(); } else if(terminalType.toLowerCase().startsWith("mushclient")&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.MXP))) { negotiateTelnetMode(rawout,TELNET_MXP); mxpSupportSet.remove("+IMAGE"); mxpSupportSet.remove("+IMAGE.URL"); mxpSupportSet.add("-IMAGE.URL"); } else if(terminalType.toLowerCase().equals("simplemu")) { if(CMParms.indexOf(this.promptSuffix, (byte)'\n')<0) { promptSuffix = Arrays.copyOf(promptSuffix, promptSuffix.length+1); promptSuffix[promptSuffix.length-1] = (byte)'\n'; } if(CMParms.indexOf(this.promptSuffix, (byte)'\r')<0) { promptSuffix = Arrays.copyOf(promptSuffix, promptSuffix.length+1); promptSuffix[promptSuffix.length-1] = (byte)'\r'; } if((!this.mightSupportTelnetMode(Session.TELNET_GA)) &&(CMParms.indexOf(this.promptSuffix, Session.TELNETGABYTES)<0)) { final int pos = promptSuffix.length; promptSuffix = Arrays.copyOf(promptSuffix, promptSuffix.length + Session.TELNETGABYTES.length); System.arraycopy(Session.TELNETGABYTES, 0, promptSuffix, pos, Session.TELNETGABYTES.length); } } } else if (suboptionData[0] == 1) // Request for data. {/* No idea how to handle this, ignore it for now.*/} } break; case TELNET_NAWS: if (dataSize == 4) // It should always be 4. { terminalWidth = ((suboptionData[0] << 8) | suboptionData[1])-2; terminalHeight = (suboptionData[2] << 8) | suboptionData[3]; if(terminalWidth > CMStrings.SPACES.length()) terminalWidth=CMStrings.SPACES.length(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("For suboption "+Session.TELNET_DESCS[optionCode]+", got: "+terminalWidth+"x"+terminalHeight); } break; case TELNET_MSDP: { final byte[] resp=CMLib.protocol().processMsdp(this, suboptionData, dataSize, this.msdpReportables); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("For suboption "+Session.TELNET_DESCS[optionCode]+", got "+dataSize+" bytes, sent "+((resp==null)?0:resp.length)); if(resp!=null) rawBytesOut(rawout, resp); } break; case TELNET_GMCP: { final byte[] resp=CMLib.protocol().processGmcp(this, new String(suboptionData), this.gmcpSupports); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) { Log.debugOut("For suboption "+Session.TELNET_DESCS[optionCode]+", got "+dataSize+" bytes, sent "+((resp==null)?0:resp.length)); Log.debugOut(new String(suboptionData)); } if(resp!=null) rawBytesOut(rawout, resp); } break; default: // Ignore it. break; } } public void handleEscape() throws IOException, InterruptedIOException { if((in==null)||(out==null)) return; int c=readByte(); if((char)c!='[') return; boolean quote=false; final StringBuffer escapeStr=new StringBuffer(""); while(((c=readByte())!=-1) &&(!killFlag) &&((quote)||(!Character.isLetter((char)c)))) { escapeStr.append((char)c); if(c=='"') quote=!quote; } if(c==-1) return; escapeStr.append((char)c); String esc=escapeStr.toString().trim(); // at the moment, we only handle mxp escapes // everything else is effectively EATEN if(!esc.endsWith("z")) return; esc=esc.substring(0,esc.length()-1); if(!CMath.isNumber(esc)) return; final int escNum=CMath.s_int(esc); // only LINE-based mxp escape sequences are respected if(escNum>3) return; sock[0].setSoTimeout(30000); final StringBuffer line=new StringBuffer(""); while(((c=readByte())!=-1)&&(!killFlag)) { if(c=='\n') break; line.append((char)c); } sock[0].setSoTimeout(SOTIMEOUT); String l=line.toString().toUpperCase().trim(); // now we have the line, so parse out tags -- only tags matter! while(l.length()>0) { final int tagStart=l.indexOf('<'); if(tagStart<0) return; final int tagEnd=l.indexOf('>'); if(tagEnd<0) return; String tag=l.substring(tagStart+1,tagEnd).trim(); l=l.substring(tagEnd+1).trim(); // now we have a tag, and its parameters (space delimited) final List<String> parts=CMParms.parseSpaces(tag,true); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Got secure MXP tag: "+tag); if(parts.size()>1) { tag=parts.get(0); if(tag.equals("VERSION")) { for(int p=1;p<parts.size();p++) { final String pp=parts.get(p); final int x=pp.indexOf('='); if(x<0) continue; mxpVersionInfo.remove(pp.substring(0,x).trim()); mxpVersionInfo.put(pp.substring(0,x).trim(),pp.substring(x+1).trim()); } } else if(tag.equals("SUPPORTS")) { for(int p=1;p<parts.size();p++) { final String s=parts.get(p).toUpperCase(); final int x=s.indexOf('.'); if(s.startsWith("+")) { if((terminalType == null) ||(!terminalType.toLowerCase().startsWith("mushclient")) ||(s.indexOf("IMAGE")<0)) { mxpSupportSet.add(s); if(x>0) mxpSupportSet.add(s.substring(0, x)); } } else if(s.startsWith("-")) { mxpSupportSet.add(s); mxpSupportSet.remove("+"+s.substring(1)); } } } else if(tag.equals("SHUTDOWN")) { final MOB M=CMLib.players().getLoadPlayer(parts.get(1)); if((M!=null) &&(M.playerStats().matchesPassword(parts.get(2))) &&(CMSecurity.isASysOp(M))) { final boolean keepDown=parts.size()>3?CMath.s_bool(parts.get(3)):true; final String externalCmd=(parts.size()>4)?CMParms.combine(parts,4):null; final Vector<String> cmd=new XVector<String>("SHUTDOWN","NOPROMPT"); if(!keepDown) { cmd.add("RESTART"); if((externalCmd!=null)&&(externalCmd.length()>0)) cmd.add(externalCmd); } final Command C=CMClass.getCommand("Shutdown"); l=""; setKillFlag(true); rawCharsOut(out,"\n\n\033[1z<Executing Shutdown...\n\n".toCharArray()); M.setSession(this); if(C!=null) C.execute(M,cmd,0); } } } } } public void handleIAC() throws IOException, InterruptedIOException { if((in==null)||(out==null)) return; lastIACIn=System.currentTimeMillis(); int c=readByte(); if(c>255) c=c&0xff; switch(c) { case TELNET_IAC: bNextByteIs255=true; break; case TELNET_SB: { CharArrayWriter subOptionStream=new CharArrayWriter(); final int subOptionCode = readByte(); int last = 0; if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Reading sub-option "+subOptionCode); while(((last = readByte()) != -1) &&(!killFlag)) { if(subOptionStream.size()>1024*1024*5) { killFlag=true; return; } else if (last == TELNET_IAC) { last = readByte(); if (last == TELNET_IAC) subOptionStream.write(TELNET_IAC); else if (last == TELNET_SE) break; } else subOptionStream.write((char)last); } final char[] subOptionData=subOptionStream.toCharArray(); subOptionStream=null; handleSubOption(subOptionCode, subOptionData, subOptionData.length); break; } case TELNET_DO: { final int last=readByte(); setClientTelnetMode(last,true); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Got DO "+Session.TELNET_DESCS[last]); if((terminalType.equalsIgnoreCase("zmud")||terminalType.equalsIgnoreCase("cmud"))&&(last==Session.TELNET_ECHO)) setClientTelnetMode(Session.TELNET_ECHO,false); if((last==TELNET_COMPRESS2)&&(getServerTelnetMode(last))) { setClientTelnetMode(last,true); if(connectionComplete) { prompt(new TickingCallback(250) { @Override public boolean tick(int counter) { try { if((out==null)||(killFlag)) return false; out.flush(); rawout.flush(); switch(counter) { case 3: { if(getClientTelnetMode(TELNET_COMPRESS2)) { negotiateTelnetMode(rawout,TELNET_COMPRESS2); rawout.flush(); final ZOutputStream zOut=new ZOutputStream(rawout, JZlib.Z_DEFAULT_COMPRESSION); rawout=zOut; zOut.setFlushMode(JZlib.Z_SYNC_FLUSH); out = new PrintWriter(new OutputStreamWriter(zOut,CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("MCCP compression started"); } break; } case 10: return false; default: break; } return true; } catch(final IOException e) { if(e.getMessage()==null) Log.errOut(e); else Log.errOut(e.getMessage()); } return false; } }); } } else if(!mightSupportTelnetMode(last)) changeTelnetMode(last,false); else if(!getServerTelnetMode(last)) changeTelnetMode(last,true); if(serverTelnetCodes[TELNET_LOGOUT]) setKillFlag(true); break; } case TELNET_DONT: { final int last=readByte(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Got DONT "+Session.TELNET_DESCS[last]); setClientTelnetMode(last,false); if((last==TELNET_COMPRESS2)&&(getServerTelnetMode(last))) { setClientTelnetMode(last,false); rawout=new BufferedOutputStream(sock[0].getOutputStream()); out = new PrintWriter(new OutputStreamWriter(rawout,CMProps.getVar(CMProps.Str.CHARSETOUTPUT))); } if((mightSupportTelnetMode(last)&&(getServerTelnetMode(last)))) changeTelnetMode(last,false); break; } case TELNET_WILL: { final int last=readByte(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Got WILL "+Session.TELNET_DESCS[last]); setClientTelnetMode(last,true); if((terminalType.equalsIgnoreCase("zmud")||terminalType.equalsIgnoreCase("cmud"))&&(last==Session.TELNET_ECHO)) setClientTelnetMode(Session.TELNET_ECHO,false); if(!mightSupportTelnetMode(last)) changeTelnetModeBackwards(last,false); else if(!getServerTelnetMode(last)) changeTelnetModeBackwards(last,true); if(serverTelnetCodes[TELNET_LOGOUT]) setKillFlag(true); break; } case TELNET_WONT: { final int last=readByte(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("Got WONT "+Session.TELNET_DESCS[last]); setClientTelnetMode(last,false); if((mightSupportTelnetMode(last))&&(getServerTelnetMode(last))) changeTelnetModeBackwards(last,false); break; } case TELNET_AYT: rawCharsOut(" \b"); break; default: return; } } public int readByte() throws IOException { if(bNextByteIs255) return (byte)255; bNextByteIs255 = false; if(fakeInput!=null) throw new java.io.InterruptedIOException("."); if((rawin!=null) && (rawin.available()>0)) { final int read = rawin.read(); if(read==-1) throw new java.io.InterruptedIOException("."); if(debugBinInput && Log.debugChannelOn()) debugBinInputBuf.append(read).append(" "); return read; } throw new java.io.InterruptedIOException("."); } public int readChar() throws IOException { if(bNextByteIs255) return 255; bNextByteIs255 = false; if(fakeInput!=null) { if(fakeInput.length()>0) { final char c=fakeInput.charAt(0); fakeInput.delete(0, 1); return c; } fakeInput=null; } int b = readByte(); if(1==inMaxBytesPerChar) return b; if((b==TELNET_IAC)||((b&0xff)==TELNET_IAC)||(b=='\033')||(b==27)||(in==null)) return b; charWriter.write(b); int maxBytes=inMaxBytesPerChar; while((in!=null) && !in.ready() && !killFlag && (rawin!=null) &&(rawin.available()>0) && (--maxBytes>=0)) { try { return in.read(); } catch(final java.io.InterruptedIOException e) { b = readByte(); charWriter.write(b); } } if(in==null) throw new java.io.InterruptedIOException(); return in.read(); } @Override public char hotkey(long maxWait) { if((in==null)||(out==null)) return '\0'; input=new StringBuffer(""); final long start=System.currentTimeMillis(); try { suspendCommandLine=true; char c='\0'; while((!killFlag) &&((maxWait<=0)||((System.currentTimeMillis()-start)<maxWait))) { c=(char)nonBlockingIn(false); if((c==(char)0)||(c==(char)1)||(c==(char)-1)) continue; return c; } suspendCommandLine=false; if((maxWait>0)&&((System.currentTimeMillis()-start)>=maxWait)) throw new java.io.InterruptedIOException(TIMEOUT_MSG); } catch(final java.io.IOException e) { } return '\0'; } protected int nonBlockingIn(boolean appendInputFlag) throws IOException { try { int c=readChar(); if(c<0) throw new IOException("reset by peer"); else if((c==TELNET_IAC)||((c&0xff)==TELNET_IAC)) handleIAC(); else if(c=='\033') handleEscape(); else { boolean rv = false; switch (c) { case 0: { c=-1; lastWasCR = false; lastWasLF = false; } break; case 10: { c=-1; if(!lastWasCR) { lastWasLF = true; rv = true; } else lastWasLF = false; lastWasCR = false; if (getClientTelnetMode(TELNET_ECHO)) rawCharsOut(""+(char)13+(char)10); // CR break; } case 13: { c=-1; if(!lastWasLF) { lastWasCR = true; rv = true; } else lastWasCR = false; lastWasLF = false; if (getClientTelnetMode(TELNET_ECHO)) rawCharsOut(""+(char)13+(char)10); // CR break; } case 27: { lastWasCR = false; lastWasLF = false; // don't let them enter ANSI escape sequences... c = -1; break; } default: { if(((c>>8)&0xff)>241) c=-1; lastWasCR = false; lastWasLF = false; break; } } if(c>0) { lastKeystroke=System.currentTimeMillis(); if(appendInputFlag) input.append((char)c); if (getClientTelnetMode(TELNET_ECHO)) rawCharsOut((char)c); if(!appendInputFlag) return c; } if(rv) return 0; } } catch(final InterruptedIOException e) { return -1; } return 1; } protected void clearInputBuffer(long maxTime) { try { blockingIn(maxTime, true); } catch(Exception e) { } } @Override public String blockingIn(long maxTime, boolean filter) throws IOException { if((in==null)||(out==null)) return ""; this.input.setLength(0); final long start=System.currentTimeMillis(); final long timeoutTime= (maxTime<=0) ? Long.MAX_VALUE : (start + maxTime); long nextPingAtTime=start + PINGTIMEOUT; try { suspendCommandLine=true; long now; long lastC; StringBuilder inStr = null; while((!killFlag) &&((now=System.currentTimeMillis())<timeoutTime)) { if((lastC=nonBlockingIn(true))==0) { inStr=new StringBuilder(input); this.input.setLength(0); if(sessionMCPCheck(inStr)) break; } else if(lastC == -1) { if(now > nextPingAtTime) { rawCharsOut(PINGCHARS); nextPingAtTime=now +PINGTIMEOUT; } CMLib.s_sleep(100); // if they entered nothing, make sure we dont do a busy poll } } if(inStr == null) inStr = new StringBuilder(); suspendCommandLine=false; if(System.currentTimeMillis()>=timeoutTime) throw new java.io.InterruptedIOException(TIMEOUT_MSG); final String str; if(filter) str=CMLib.coffeeFilter().simpleInFilter(inStr,CMSecurity.isAllowed(mob,(mob!=null)?mob.location():null,CMSecurity.SecFlag.MXPTAGS)); else str=inStr.toString(); if(str==null) return null; snoopSupportPrint(str+"\n\r",true); if(debugStrInput) Log.sysOut("INPUT: "+(mob==null?"":mob.Name())+": '"+inStr.toString()+"'"); return str; } finally { suspendCommandLine=false; } } public String blockingIn() throws IOException { return blockingIn(-1, true); } protected boolean sessionMCPCheck(final StringBuilder inStr) { if((inStr.length()>3)&&(inStr.substring(0, 2).equals("#$"))) { if(inStr.substring(0, 3).equals("#$#")) { if(debugStrInput) Log.sysOut("INPUT: "+(mob==null?"":mob.Name())+": '"+inStr.toString()+"'"); if(CMLib.protocol().mcp(this,inStr,mcpKey,mcpSupported,mcpKeyPairs)) return false; } else if(inStr.substring(0, 3).equals("#$\"")) inStr.delete(0, 3); } return true; } @Override public String readlineContinue() throws IOException, SocketException { if((in==null)||(out==null)) return ""; int code=-1; while(!killFlag) { synchronized(sock) { if(sock[0].isClosed() || (!sock[0].isConnected())) { setKillFlag(true); return null; } } code=nonBlockingIn(true); if(code==1) continue; if(code==0) break; if(code==-1) return null; } final StringBuilder inStr=new StringBuilder(input); input.setLength(0); if(!sessionMCPCheck(inStr)) return null; final String str=CMLib.coffeeFilter().simpleInFilter(inStr,CMSecurity.isAllowed(mob,(mob!=null)?mob.location():null,CMSecurity.SecFlag.MXPTAGS)); if(str==null) return null; snoopSupportPrint(str+"\n\r",true); if(debugStrInput) Log.sysOut("INPUT: "+(mob==null?"":mob.Name())+": '"+inStr.toString()+"'"); return str; } @Override public boolean confirm(final String Message, String Default, long maxTime) throws IOException { if(Default.toUpperCase().startsWith("T")) Default="Y"; final String YN=choose(Message,"YN",Default,maxTime); if(YN.equals("Y")) return true; return false; } @Override public boolean confirm(final String Message, String Default) throws IOException { if(Default.toUpperCase().startsWith("T")) Default="Y"; final String YN=choose(Message,"YN",Default,-1); if(YN.equals("Y")) return true; return false; } @Override public String choose(final String Message, final String Choices, String Default) throws IOException { return choose(Message,Choices,Default,-1,null); } @Override public String choose(final String Message, final String Choices, final String Default, long maxTime) throws IOException { return choose(Message,Choices,Default,maxTime,null); } @Override public String choose(final String Message, final String Choices, final String Default, long maxTime, List<String> paramsOut) throws IOException { String YN=""; String rest=null; while((YN.equals(""))||(Choices.indexOf(YN)<0)&&(!killFlag)) { promptPrint(Message); YN=blockingIn(maxTime, true); if(YN==null){ return Default.toUpperCase(); } YN=YN.trim(); if(YN.equals("")){ return Default.toUpperCase(); } if(YN.length()>1) { if(paramsOut!=null) rest=YN.substring(1).trim(); YN=YN.substring(0,1).toUpperCase(); } else YN=YN.toUpperCase(); } if((rest!=null)&&(paramsOut!=null)&&(rest.length()>0)) paramsOut.addAll(CMParms.cleanParameterList(rest)); return YN; } @Override public void stopSession(boolean removeMOB, boolean dropSession, boolean killThread) { setKillFlag(true); setStatus(SessionStatus.LOGOUT5); if(dropSession) { preLogout(mob); } if(removeMOB) { removeMOBFromGame(false); } if(dropSession) logoutFinal(); if(killThread) { // this is a really really bad idea, because any other sessions // on the same thread get killed also! Thread killThisThread=null; synchronized(this) { if(runThread==Thread.currentThread()) setKillFlag(true); else if(runThread!=null) killThisThread=runThread; } if(killThisThread!=null) killThisThread.interrupt(); killThisThread=writeThread; if(killThisThread!=null) killThisThread.interrupt(); } } public void showPrompt() { promptLastShown=System.currentTimeMillis(); final MOB mob=mob(); if(mob==null) return; if(mob.playerStats()==null) return; final StringBuilder buf=new StringBuilder(""); if(getClientTelnetMode(Session.TELNET_MXP)) { buf.append("^<!ENTITY Hp '").append(mob.curState().getHitPoints()) .append("'^>^<!ENTITY MaxHp '").append(mob.maxState().getHitPoints()) .append("'^>^<!ENTITY Mana '").append(mob.curState().getMana()) .append("'^>^<!ENTITY MaxMana '").append(mob.maxState().getMana()) .append("'^>^<!ENTITY Move '").append(mob.curState().getMovement()) .append("'^>^<!ENTITY MaxMove '").append(mob.maxState().getMovement()) .append("'^>^<!ENTITY Exp '").append(mob.getExperience()) .append("'^>^<!ENTITY ExpNeed '").append(mob.getExpNeededLevel()) .append("'^>^\n\r\n\r"); } buf.append(CMLib.utensils().builtPrompt(mob)); promptPrint("^<Prompt^>"+buf.toString()+"^</Prompt^>^.^N"); } protected void closeSocks(String finalMsg) { if(sock[0]!=null) { synchronized(sock) { if(sock[0]!=null) { try { Log.sysOut("Disconnect: "+finalMsg+getAddress()+" ("+CMLib.time().date2SmartEllapsedTime(getMillisOnline(),true)+")"); setStatus(SessionStatus.LOGOUT7); sock[0].shutdownInput(); setStatus(SessionStatus.LOGOUT8); if(out!=null) { try { if(!out.checkError()) { out.write(PINGCHARS); out.checkError(); } } catch(final Exception t){} out.close(); } setStatus(SessionStatus.LOGOUT9); sock[0].shutdownOutput(); setStatus(SessionStatus.LOGOUT10); sock[0].close(); setStatus(SessionStatus.LOGOUT11); } catch(final IOException e) { } catch(final java.lang.ArrayIndexOutOfBoundsException e) { } finally { rawin=null; in=null; out=null; sock[0]=null; } } } } } @Override public String getAddress() { try { return sock[0].getInetAddress().getHostAddress(); } catch (final Exception e) { return "Unknown (Excpt "+e.getMessage() + ")"; } } private void preLogout(MOB M) { if(M==null) return; synchronized(loggingOutObj) { if(loggingOutObj[0]) return; try { loggingOutObj[0]=true; final boolean inTheGame=CMLib.flags().isInTheGame(M,true); if(inTheGame && (M.location()!=null) &&((!CMProps.getBoolVar(CMProps.Bool.MUDSHUTTINGDOWN)) ||(CMLib.sessions().getCountAll()>1))) { List<Room> rooms=new ArrayList<Room>(1); rooms.add(M.location()); for(MOB M2 : M.getGroupMembers(new HashSet<MOB>())) { if((M2.location()!=null)&&(!rooms.contains(M2.location()))) rooms.add(M2.location()); } for(Room R : rooms) { try { R.send(M, CMClass.getMsg(mob, CMMsg.MSG_QUIT, null)); } catch(Throwable t) { /* and eat it */ } } } while((getLastPKFight()>0) &&((System.currentTimeMillis()-getLastPKFight())<(2*60*1000)) &&(mob!=null)) CMLib.s_sleep(1000); String name=M.Name(); if(name.trim().length()==0) name="Unknown"; if((M.isInCombat())&&(M.location()!=null)) { CMLib.commands().postFlee(mob,"NOWHERE"); M.makePeace(false); } if(!CMLib.flags().isCloaked(M)) { final List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.LOGOFFS); for(int i=0;i<channels.size();i++) CMLib.commands().postChannel(channels.get(i),M.clans(),L("@x1 has logged out",name),true); } CMLib.login().notifyFriends(M,L("^X@x1 has logged off.^.^?",M.Name())); // the player quit message! CMLib.threads().executeRunnable(groupName,new LoginLogoutThread(M,CMMsg.MSG_QUIT)); if(M.playerStats()!=null) M.playerStats().setLastDateTime(System.currentTimeMillis()); Log.sysOut("Logout: "+name+" ("+CMLib.time().date2SmartEllapsedTime(System.currentTimeMillis()-userLoginTime,true)+")"); if(inTheGame) CMLib.database().DBUpdateFollowers(M); } finally { loggingOutObj[0]=false; } } } private void removeMOBFromGame(boolean killSession) { final MOB M=mob; if(M!=null) { final boolean inTheGame=CMLib.flags().isInTheGame(M,true); final PlayerStats pstats=M.playerStats(); if(pstats!=null) pstats.setLastDateTime(System.currentTimeMillis()); if(inTheGame) CMLib.database().DBUpdateFollowers(M); if(!CMSecurity.isDisabled(CMSecurity.DisFlag.LOGOUTS)) { if(pstats!=null) // cant do this when logouts are suspended -- folks might get killed! CMLib.threads().suspendResumeRecurse(M, false, true); M.removeFromGame(true,killSession); } } } @Override public SessionStatus getStatus() { return status; } @Override public boolean isWaitingForInput() { return (inputCallback!=null); } @Override public void logout(boolean removeMOB) { final MOB M = this.mob; if((M==null)||(M.playerStats()==null)) stopSession(false,false,false); else { preLogout(M); if(removeMOB) removeMOBFromGame(false); M.setSession(null); this.mob=null; } } @Override public boolean isRunning() { return runThread!=null; } @Override public boolean isPendingLogin(final String otherLoginName) { switch(status) { case LOGIN: case LOGIN2: case HANDSHAKE_OPEN: case HANDSHAKE_MCCP: case HANDSHAKE_MXP: case HANDSHAKE_MXPPAUSE: case HANDSHAKE_DONE: break; default: return false; } if(loginSession==null) return false; final String myLogin=loginSession.login(); if((otherLoginName==null)||(myLogin==null)) return false; return otherLoginName.equalsIgnoreCase(myLogin); } @Override public void run() { synchronized(this) { if(runThread!=null) { // one at a time, thank you. return; } runThread=Thread.currentThread(); } activeMillis=System.currentTimeMillis(); if((activeMillis>=nextMsdpPing)&&(connectionComplete)) { nextMsdpPing=activeMillis+MSDPPINGINTERVAL; if(getClientTelnetMode(TELNET_MSDP)) { final byte[] msdpPingBuf=CMLib.protocol().pingMsdp(this, msdpReportables); if(msdpPingBuf!=null) { try { rawBytesOut(rawout, msdpPingBuf);}catch(final IOException e){} if(CMSecurity.isDebugging(CMSecurity.DbgFlag.TELNET)) Log.debugOut("MSDP Reported: "+msdpPingBuf.length+" bytes"); } } if(getClientTelnetMode(TELNET_GMCP)) { final byte[] gmcpPingBuf=CMLib.protocol().pingGmcp(this, gmcpPings, gmcpSupports); if(gmcpPingBuf!=null) { try { rawBytesOut(rawout, gmcpPingBuf);}catch(final IOException e){} } } } try { if(killFlag) setStatus(SessionStatus.LOGOUT); final InputCallback callBack=this.inputCallback; if(callBack!=null) { try { final String input=readlineContinue(); if(input != null) { callBack.setInput(input); if(!callBack.waitForInput()) { CMLib.threads().executeRunnable(groupName,new Runnable() { @Override public void run() { try { callBack.callBack(); } catch(final Throwable t) { Log.errOut(t); } } }); } } else if(callBack.isTimedOut()) { callBack.timedOut(); } } catch(final Exception e) { } if(!callBack.waitForInput()) inputCallback=null; } else switch(status) { case IDLE: case HANDSHAKE_OPEN: case HANDSHAKE_MCCP: case HANDSHAKE_MXP: case HANDSHAKE_MXPPAUSE: case HANDSHAKE_DONE: break; case MAINLOOP: mainLoop(); break; case LOGIN: case ACCOUNT_MENU: case CHARCREATE: case LOGIN2: loginSystem(); break; case LOGOUT: case LOGOUT1: case LOGOUT2: case LOGOUT3: case LOGOUT4: case LOGOUT5: case LOGOUT6: case LOGOUT7: case LOGOUT8: case LOGOUT9: case LOGOUT10: case LOGOUT11: case LOGOUT12: case LOGOUTFINAL: { inputCallback=null; preLogout(mob); logoutFinal(); break; } } } catch(final Throwable t) { Log.errOut(t); } finally { synchronized(this) { runThread=null; } activeMillis=0; } } public void loginSystem() { try { if((loginSession==null)||(loginSession.reset())) { loginSession = CMLib.login().createLoginSession(this); setStatus(SessionStatus.LOGIN); } else if(!loginSession.skipInputThisTime()) { String lastInput = loginSession.acceptInput(this); if(lastInput==null) { if((System.currentTimeMillis()-lastWriteTime)>PINGTIMEOUT) rawCharsOut(PINGCHARS); return; } if(!killFlag) setInputLoopTime(); } if(loginSession == null) killFlag = true; else if(!killFlag) { final CharCreationLibrary.LoginResult loginResult=loginSession.loginSystem(this); switch(loginResult) { case INPUT_REQUIRED: return; case NO_LOGIN: { mob=null; setStatus(SessionStatus.LOGIN); return; } case NORMAL_LOGIN: { setStatus(SessionStatus.LOGIN2); if((mob!=null)&&(mob.playerStats()!=null)) acct=mob.playerStats().getAccount(); if((!killFlag)&&((mob!=null))) { if(mob.playerStats()!=null) CMLib.threads().suspendResumeRecurse(mob, false, false); userLoginTime=System.currentTimeMillis(); final StringBuilder loginMsg=new StringBuilder(""); loginMsg.append(getAddress()).append(" "+terminalType) .append(((mob.isAttributeSet(MOB.Attrib.MXP)&&getClientTelnetMode(Session.TELNET_MXP)))?" MXP":"") .append(getClientTelnetMode(Session.TELNET_MSDP)?" MSDP":"") .append(getClientTelnetMode(Session.TELNET_ATCP)?" ATCP":"") .append(getClientTelnetMode(Session.TELNET_GMCP)?" GMCP":"") .append((getClientTelnetMode(Session.TELNET_COMPRESS)||getClientTelnetMode(Session.TELNET_COMPRESS2))?" CMP":"") .append(((mob.isAttributeSet(MOB.Attrib.ANSI)&&getClientTelnetMode(Session.TELNET_ANSI)))?" ANSI":"") .append(", character login: "+mob.Name()); Log.sysOut(loginMsg.toString()); if(loginResult != CharCreationLibrary.LoginResult.NO_LOGIN) { final CMMsg msg = CMClass.getMsg(mob,null,CMMsg.MSG_LOGIN,null); if(!CMLib.map().sendGlobalMessage(mob,CMMsg.TYP_LOGIN,msg)) setKillFlag(true); else CMLib.commands().monitorGlobalMessage(mob.location(), msg); } } needPrompt=true; if((!killFlag)&&(mob!=null)) { setStatus(SessionStatus.MAINLOOP); return; } } } setStatus(SessionStatus.LOGIN); return; } else { loginSession=null; } setStatus(SessionStatus.LOGOUT); } catch(final SocketException e) { synchronized(sock) { if(!Log.isMaskedErrMsg(e.getMessage())&&((!killFlag)||((sock[0]!=null)&&sock[0].isConnected()))) errorOut(e); } setStatus(SessionStatus.LOGOUT); preLogout(mob); setStatus(SessionStatus.LOGOUT1); } catch(final Exception t) { synchronized(sock) { if(!Log.isMaskedErrMsg(t.getMessage()) &&((!killFlag) ||(sock[0]!=null&&sock[0].isConnected()))) errorOut(t); } setStatus(SessionStatus.LOGOUT); preLogout(mob); setStatus(SessionStatus.LOGOUT1); } } public void logoutFinal() { try { final MOB M=mob(); final PlayerAccount acct=this.acct; final String finalMsg; if(M!=null) finalMsg=M.Name()+": "; else if(acct!=null) finalMsg=acct.getAccountName()+": "; else finalMsg=""; previousCmd.clear(); // will let system know you are back in login menu if(M!=null) { try { if(CMSecurity.isDisabled(CMSecurity.DisFlag.LOGOUTS)) { M.setSession(null); if(M.location()==null) M.setLocation(M.getStartRoom()); if(M.location()==null) M.setLocation(CMLib.map().getStartRoom(M)); if(M.location()==null) M.setLocation(CMLib.map().getRandomRoom()); CMLib.commands().postSleep(M); M.setSession(this); M.basePhyStats().setDisposition(mob.basePhyStats().disposition()|PhyStats.IS_SLEEPING); M.phyStats().setDisposition(mob.phyStats().disposition()|PhyStats.IS_SLEEPING); } else { M.removeFromGame(true,true); M.setSession(null); mob=null; } } catch(final Exception e) { Log.errOut(e); } finally { } } setStatus(SessionStatus.LOGOUT4); setKillFlag(true); waiting=false; needPrompt=false; this.acct=null; snoops.clear(); closeSocks(finalMsg); setStatus(SessionStatus.LOGOUT5); } finally { CMLib.sessions().remove(this); setStatus(SessionStatus.LOGOUTFINAL); } } private String L(final String str, final String... xs) { return CMLib.lang().fullSessionTranslation(str, xs); } public void mainLoop() { try { setInputLoopTime(); waiting=true; String input; if(suspendCommandLine) { input=null; return; } else input=readlineContinue(); if(input==null) { if((System.currentTimeMillis()-lastWriteTime)>PINGTIMEOUT) rawCharsOut(PINGCHARS); } else { lastKeystroke=System.currentTimeMillis(); if(input.trim().length()>0) prevMsgs.add(input); setAfkFlag(false); List<String> CMDS=CMParms.parse(input); final MOB mob=mob(); if((CMDS.size()>0)&&(mob!=null)) { waiting=false; final String firstWord=CMDS.get(0); final PlayerStats pstats=mob.playerStats(); final String alias=(pstats!=null)?pstats.getAlias(firstWord):""; final Vector<List<String>> ALL_CMDS=new Vector<List<String>>(); boolean echoOn=false; if(alias.length()>0) { CMDS.remove(0); final List<String> all_stuff=CMParms.parseSquiggleDelimited(alias,true); for(final String stuff : all_stuff) { final List THIS_CMDS=new XVector(CMDS); ALL_CMDS.addElement(THIS_CMDS); final List<String> preCommands=CMParms.parse(stuff); for(int v=preCommands.size()-1;v>=0;v--) THIS_CMDS.add(0,preCommands.get(v)); } echoOn=true; } else ALL_CMDS.addElement(CMDS); for(int v=0;v<ALL_CMDS.size();v++) { CMDS=ALL_CMDS.elementAt(v); setPreviousCmd(CMDS); milliTotal+=(lastStop-lastStart); lastStart=System.currentTimeMillis(); if(echoOn) rawPrintln(CMParms.combineQuoted(CMDS,0)); final List<List<String>> MORE_CMDS=CMLib.lang().preCommandParser(CMDS); for(int m=0;m<MORE_CMDS.size();m++) mob.enqueCommand(MORE_CMDS.get(m),metaFlags(),0); lastStop=System.currentTimeMillis(); } } needPrompt=true; } if(mob==null) { if(loginSession != null) loginSession.logoutLoginSession(); setStatus(SessionStatus.LOGIN); return; } while((!killFlag)&&(mob!=null)&&(mob.dequeCommand())) {} if(((System.currentTimeMillis()-lastBlahCheck)>=60000) &&(mob()!=null)) { lastBlahCheck=System.currentTimeMillis(); final Vector<String> V=CMParms.parse(CMProps.getVar(CMProps.Str.IDLETIMERS)); if((V.size()>0) &&(!CMSecurity.isAllowed(mob(),mob().location(),CMSecurity.SecFlag.IDLEOK)) &&(CMath.s_int(V.firstElement())>0)) { final int minsIdle=(int)(getIdleMillis()/60000); if(minsIdle>=CMath.s_int(V.firstElement())) { println(CMLib.lang().L("\n\r^ZYou are being logged out!^?")); setKillFlag(true); } else if(minsIdle>=CMath.s_int(V.lastElement())) { final int remain=CMath.s_int(V.firstElement())-minsIdle; println(mob(),null,null,CMLib.lang().L("\n\r^ZIf you don't do something, you will be logged out in @x1 minute(s)!^?",""+remain)); } } if(!isAfk()) { if(getIdleMillis()>=600000) setAfkFlag(true); } else if((getIdleMillis()>=10800000)&&(!isStopped())) { if((!CMLib.flags().isSleeping(mob)) &&(mob().fetchEffect("Disease_Blahs")==null) &&(!CMSecurity.isDisabled(CMSecurity.DisFlag.AUTODISEASE))) { final Ability A=CMClass.getAbility("Disease_Blahs"); if(A!=null) A.invoke(mob,mob,true,0); } else if((CMLib.flags().isSleeping(mob)) &&(mob().fetchEffect("Disease_Narcolepsy")==null) &&(!CMSecurity.isDisabled(CMSecurity.DisFlag.AUTODISEASE))) { final Ability A=CMClass.getAbility("Disease_Narcolepsy"); if(A!=null) A.invoke(mob,mob,true,0); } } } if((needPrompt)&&(waiting)) { if((mob!=null)&&(mob.isInCombat())) { if(((System.currentTimeMillis()-promptLastShown)>=CMProps.getTickMillis()) ||(input!=null)) { showPrompt(); needPrompt=false; } } else { showPrompt(); needPrompt=false; } } } catch(final SocketException e) { synchronized(sock) { if(!Log.isMaskedErrMsg(e.getMessage())&&((!killFlag)||((sock[0]!=null)&&sock[0].isConnected()))) errorOut(e); } setStatus(SessionStatus.LOGOUT); preLogout(mob); setStatus(SessionStatus.LOGOUT1); } catch(final Exception t) { synchronized(sock) { if((!Log.isMaskedErrMsg(t.getMessage())) &&((!killFlag) ||(sock[0]!=null&&sock[0].isConnected()))) errorOut(t); } setStatus(SessionStatus.LOGOUT); preLogout(mob); setStatus(SessionStatus.LOGOUT1); } } @Override public long activeTimeMillis() { if(activeMillis==0) return 0; return System.currentTimeMillis()-activeMillis; } public static class LoginLogoutThread implements CMRunnable, Tickable { private final long startTime=System.currentTimeMillis(); @Override public String name() { return (theMOB == null) ? "Dead LLThread" : "LLThread for " + theMOB.Name(); } @Override public boolean tick(Tickable ticking, int tickID) { return false; } @Override public String ID() { return name(); } @Override public CMObject newInstance() { try { return getClass().newInstance(); } catch (final Exception e) { return new LoginLogoutThread(); } } @Override public void initializeClass() { } @Override public CMObject copyOf() { try { return (CMObject) this.clone(); } catch (final Exception e) { return newInstance(); } } @Override public int compareTo(CMObject o) { return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o)); } @Override public int getTickStatus() { return 0; } @Override public long getStartTime() { return startTime; } @Override public int getGroupID() { return '0'; } private MOB theMOB=null; private int msgCode=-1; private final HashSet<Room> skipRooms=new HashSet<Room>(); private long activeMillis=0; private LoginLogoutThread(){} public LoginLogoutThread(MOB mob, int msgC) { theMOB=mob; msgCode=msgC; } public void initialize() { final Set<MOB> group=theMOB.getGroupMembers(new HashSet<MOB>()); skipRooms.clear(); for (final Object element : group) { final MOB M=(MOB)element; if((M.location()!=null)&&(!skipRooms.contains(M.location()))) skipRooms.add(M.location()); } if((!CMProps.getBoolVar(CMProps.Bool.MUDSHUTTINGDOWN)) &&(CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))) { final CMMsg msg=CMClass.getMsg(theMOB,null,msgCode,null); Room R=theMOB.location(); if(R!=null) skipRooms.remove(R); try { if((R!=null)&&(theMOB.location()!=null)) R.send(theMOB,msg); for(final Iterator i=skipRooms.iterator();i.hasNext();) { R=(Room)i.next(); if(theMOB.location()!=null) R.sendOthers(theMOB,msg); } if(R!=null) skipRooms.add(R); }catch(final Exception e){} } } @Override public void run() { activeMillis=System.currentTimeMillis(); if((!CMProps.getBoolVar(CMProps.Bool.MUDSHUTTINGDOWN)) &&(CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))) { final CMMsg msg=CMClass.getMsg(theMOB,null,msgCode,null); Room R=null; try { for(final Enumeration e=CMLib.map().rooms();e.hasMoreElements();) { R=(Room)e.nextElement(); if((!skipRooms.contains(R))&&(theMOB.location()!=null)) R.sendOthers(theMOB,msg); } } catch(final Exception e) {} theMOB=null; } } @Override public long activeTimeMillis() { return (activeMillis>0)?System.currentTimeMillis()-activeMillis:0; } } @Override public void setIdleTimers() { this.lastStr=""; // also resets spam counter this.spamStack=0; lastKeystroke=System.currentTimeMillis(); lastWriteTime=System.currentTimeMillis(); } private static enum SESS_STAT_CODES {PREVCMD,ISAFK,AFKMESSAGE,ADDRESS,IDLETIME, LASTMSG,LASTNPCFIGHT,LASTPKFIGHT,TERMTYPE, TOTALMILLIS,TOTALTICKS,WRAP,LASTLOOPTIME} @Override public int getSaveStatIndex() { return SESS_STAT_CODES.values().length; } @Override public String[] getStatCodes() { return CMParms.toStringArray(SESS_STAT_CODES.values()); } @Override public boolean isStat(String code) { return getStatIndex(code) != null; } private SESS_STAT_CODES getStatIndex(String code) { return (SESS_STAT_CODES) CMath.s_valueOf(SESS_STAT_CODES.values(), code); } @Override public String getStat(String code) { final SESS_STAT_CODES stat = getStatIndex(code); if (stat == null) { return ""; } switch (stat) { case PREVCMD: return CMParms.combineQuoted(getPreviousCMD(), 0); case ISAFK: return "" + isAfk(); case AFKMESSAGE: return getAfkMessage(); case ADDRESS: return getAddress(); case IDLETIME: return CMLib.time().date2String(System.currentTimeMillis() - getIdleMillis()); case LASTMSG: return CMParms.combineQuoted(getLastMsgs(), 0); case LASTNPCFIGHT: return CMLib.time().date2String(getLastNPCFight()); case LASTPKFIGHT: return CMLib.time().date2String(getLastPKFight()); case TERMTYPE: return getTerminalType(); case TOTALMILLIS: return CMLib.time().date2String(System.currentTimeMillis() - getTotalMillis()); case TOTALTICKS: return "" + getTotalTicks(); case WRAP: return "" + getWrap(); case LASTLOOPTIME: return CMLib.time().date2String(getInputLoopTime()); default: Log.errOut("Session", "getStat:Unhandled:" + stat.toString()); break; } return null; } @Override public void setStat(String code, String val) { final SESS_STAT_CODES stat = getStatIndex(code); if (stat == null) { return; } switch (stat) { case PREVCMD: previousCmd = CMParms.parse(val); break; case ISAFK: afkFlag = CMath.s_bool(val); break; case AFKMESSAGE: afkMessage = val; break; case ADDRESS: return; case IDLETIME: lastKeystroke = CMLib.time().string2Millis(val); break; case LASTMSG: prevMsgs = CMParms.parse(val); break; case LASTNPCFIGHT: lastNPCFight = CMLib.time().string2Millis(val); break; case LASTPKFIGHT: lastPKFight = CMLib.time().string2Millis(val); break; case TERMTYPE: terminalType = val; break; case TOTALMILLIS: milliTotal = System.currentTimeMillis() - CMLib.time().string2Millis(val); break; case TOTALTICKS: tickTotal = CMath.s_int(val); break; case WRAP: if ((mob != null) && (mob.playerStats() != null)) mob.playerStats().setWrap(CMath.s_int(val)); break; case LASTLOOPTIME: lastLoopTop = CMLib.time().string2Millis(val); break; default: Log.errOut("Session", "setStat:Unhandled:" + stat.toString()); break; } } private static class SesInputStream extends InputStream { private final int[] bytes; private int start=0; private int end=0; protected SesInputStream(int maxBytesPerChar) { bytes=new int[maxBytesPerChar+1]; } @Override public int read() throws IOException { if(start==end) throw new java.io.InterruptedIOException(); final int b=bytes[start]; if(start==bytes.length-1) start=0; else start++; return b; } public void write(int b) { bytes[end]=b; if(end==bytes.length-1) end=0; else end++; } } }