package net.matrix_rad.BPC_NO; // The standard says not to use the wildcard version of the import statement, but for // java.lang.* and java.net.* I use (or will use) most parts anyway. import java.lang.*; // Always needed, usually imported by default. import java.lang.reflect.*; // Needed for the new thread code. import java.io.ByteArrayInputStream; // Used for handling the in and out buffers. import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; // Needed for reading the password file. import java.io.EOFException; import java.io.FileNotFoundException; import java.io.InterruptedIOException; import java.io.IOException; import java.net.*; // Needed for obvious reasons B-). import java.security.MessageDigest; // Need MD5 for protocol. import java.util.Calendar; import java.util.*; // Temp, until I can figure out the date problem. import java.util.Date; // Need current time for protocol. import java.util.Properties; // So I can store config details in .properties files. import java.util.Vector; // Need arbitrary sized structure for the authorized server list and debug output. import java.util.NoSuchElementException; // Needed for Vector. /** * BigPond Cable Network Organiser, startup and login client. * * BPC_NO will login to a BIDS2 server, respond to server requests, and logout. * It has specific features for the BigPond Cable servers. * * Doesn't use any GUI stuff. Will try to find a GUI front end called * BPC_NO-GUI.class and run that, otherwise it a STDIN, STDOUT, STDERR, * command line login thing. * * @.requires Java 1.1.5. * @.example BPC_NO -u username -p password * @.example BPC_NO -logoff * @author David Seikel * @.copyright 1999 David Seikel * @.concurrency Starts a new thread for each server to login to. * @.bug Doesn't authenticate LOGIN_PARAM_HASH. * * @.todo Write a HTTP pinger that doesn't take so long to time out. * @.todo Use autopsy.txt for dontDie dumps, biopsy.txt for -dump dumps. * @.todo Add a properties file. * @.todo Add protocols for IP change, user server status, and usage stats. * @.todo Publish all protocols so that others can write to them. * @.todo Prepare it for a GUI front end and write a Java 2 GUI front end. * @.todo Publish the GUI protocol so that others can write GUI front ends. * @.todo Write a Java 1.1 GUI front end. * @.todo Write some OS specific wrapper programs for what ever OS's I can. * @.todo Start feature bloat campaign B-). * * @version 0.3 1999-07-17 * @.history dvs 2002-09-12 Updated javadoc tags. * @.history dvs 1999-08-03 Added basic network checking. * @.history dvs 1999-08-03 Added ProtocolException stuff. * @.history dvs 1999-08-02 Seperated out the new threads. * @.history dvs 1999-08-01 Updated to V2.0 BIDS2 spec B-). * @.history dvs 1999-08-01 Wrote new thread structure. * @.history dvs 1999-07-25 Added -v to report heartbeats (not reported otherwise). * @.history dvs 1999-07-25 Added -q to not report logins and logouts, error messages still reported. * @.history dvs 1999-07-25 Generalised output handling, and added output storage and dumping. * @.history dvs 1999-07-17 Cleaned up some more. * @.history dvs 1999-02-22 Complete rewrite to spec. Still showing signs of it's bids2login origins (I didn't rewrite everything). * * @see "BIDSauth.pdf version 2.0 1999-04-16" * @.fyi I have made a decision to try to squeeze all this into one class file, but not for any technical reasons. It does present some challenges to do it this way, and I may change this decision later. New structure : To start a thread, setup a Method object and an Object array to hold the method to be run as a thread and it's arguments. These threads should be daemons. BPC_NO localServer = new BPC_NO(aMethod, someObjects); Thread localLoop = new Thread(localServer, "localLoop"); localLoop.start(); Start the protocol in a seperate thread. Start the network checker in a seperate thread. Start the local loopback UC server in a seperate thread (by the GUI?). Start the local loopback GUI server. If a state requires a long process, start it in a seperate thread. Once we are logged into dce, log into free range IP server, etc. BIDS2 protocol specification. This class is based on the Hewlett-Packard IID, Telstra BigPond BIDS Session Manager Protocol Version 1.0, as specified in the document of the same name. I am unsure of the precise name, but it seems to generally be referred to as the BIDS2 protocol. The document I have is an Adobe Acrobat format file called BIDSauth.pdf and is dated December 16, 1997. I suspect that the actual protocol has changed very slightly since then, as I can not match one specific part of it as sent to me by the BigPond Cable server. IID stands for Internet Infrastructure Division. A later version (2.0, April 16, 1999) with the following differences is also in my hands : Removed "Commercial In Confidence". Replaced 'BIDS "Zero Client" java applet' with 'Telstra LuanchPad authentication client'. Replaced Status 101 "Login authenticate retry limit exceeded." with "Login attempts exceeded the max." Reformatted, Telstra BP logo added, and other cosmetic stuff. That's it, no corrections to the bad grammer and spelling mistakes, no clarification or correction of the problem hash parameter, no change to the protocol itself. The document is marked "Commercial In Confidence". Apart from the marketdroid influence in the excessive capitalization of that phrase (marketdroids have way to much influence on the world, more than you think), Hewlett-Packard and Telstra BigPond are serious about it, so I cannot make that document public or give it to anybody. The Telstra representative that gave me the document assured me (after I pressured him into giving me an answer one way or another about how secret the protocol is when separated from the document) that : "The HP document isn't for distribution. Lincoln's perl already documents enough to be dangerous... The protocol will have to stand up to peer review." It is this statement that I interpret to mean I can make my source code as public as I desire. I have heard from one of my alpha testers that there are almost identical protocols being used by cable operators all over the world, only differing in very minor details. He also tells me that he has been collecting source code of this protocol as implemented for these cable operators and he has a rather large collection of them. He has offered this collection to me, I may take him up on his offer, in the mean time, he is searching for examples of the bit I think has changed to send to me. From the information he has given me, I feel I can assume that the protocol itself is not really that big a secret. I will however, abide by the wishes of HP and BPC, and not give the document to anybody. My source I can now safely assume is mine to do with as I please. I choose to release it and the resulting executable under the terms of the following license :- Legal stuff For a change, a license that is not ten pages long and basically boils down to the fact that the user has no rights at all, but is short and written in simple English (at least until I can afford to get a lawyer to do it properly). I am unsure of the legality of using "BigPond Cable" in the name of this package, if it turns out to be a big no no, then I will change the name to "Big Perfect Coder's Network Organizer". BigPond Cable may wish to license this from me one day, they have expressed considerable excitement over the concept. If they do, they will no doubt wish to call it "BigPond Cable Network Organizer", this way I can call the class BPC_NO either way, Java is fussy about what you call it's classes. BigPond Cable Network Organizer (BPC_NO) is Copyright (C) 1999 by David Walter Seikel. BPC_NO currently includes the files BPC_NO.java, BPC_NO.class, and BPC_NO.txt, all of which are included in this package. All rights reserved. BPC_NO is completely free of charge for non commercial use, but commercial use and all distribution of BPC_NO requires an individually negotiated license agreement. This means that any commercial organization that wishes to use BPC_NO, and anybody at all that wishes to distribute BPC_NO must contact David Walter Seikel to negotiate a deal. For the alpha testers that also happen to be commercial organizations, the deal is that you can use BPC_NO for free as long as you test it each time I send you an update. All alpha testers must return test results to me as soon as possible, including the Operating System (OS) name and version, and the Java Virtual Machine (JVM) name and version, for as many different OS's and JVM's as you can. The source code for BPC_NO is included as part of this package for two reasons. The first reason is that the paranoid may wish to review the source code for deliberate security holes, and recompile BPC_NO if they want to. The second reason is for those that wish to contribute code and fixes to BPC_NO. All changes to the source code for BPC_NO must be sent to me for possible inclusion in a later revision. I will not get into ownership issues of contributed changes in this version of the license, except to say that I am open to negotiation, but I would prefer not to complicate this license. The contents of the examples directory are not covered by this license. Contributed code for the examples directory is owned by the authors of that code, and can be covered by whatever license they require, so long as I can distribute it. Coding standards used. The AmbySoft Inc. coding standards for Java v17.01c. I am actually trying out AmbySoft's coding standards for the first time. While they generally match what I would do anyway, there are some things that are different, and some things that I never bothered with before. Most of these differences sound like good ideas. Some of these differences sound like over documentation to me, I suspect that the documentation will be bigger than the rest of the source. Some of these differences make more sense in a team environment. This is not really a team environment, it is an individual coder environment with peer review of the source and extra bits by other programmers welcome. A subtle difference, but a difference all the same. I will generally be slavishly following the standard as time permits, just to see what it's like. At a later date I will think about it a bit more and see where I should deviate from the standard. Note the "as time permits", the current release is in a transitional state and has already taken too long. I will not bother to run a spelling checker over the source, it takes too long to ignore the actual source code bits. So you will have to put up with spelling errors in the documentation. Spelling errors in the source code bits should get caught during debugging. Standard abbreviations : ADMIN - administrator APP - application AUTH - authorization BILL - billable BPC - BigPond Cable CRED - credentials INFO - information INV - invalid MSG - message NEG - negotiation NO - Network Organiser NUM - number OLDSW - old software OS - operating system PARAM - parameter PROT - protocol RECV - receive RESP - response REQ - request TEMP - temporary Priorities : Ambysoft says that I should rank the following factors, without clearly defining what these factors mean. Simplicity can mean simplicity of code or simplicity of use, which are often mutually exclusive. Maintainability includes simplicity of code, so I think that simplicity in this context means simplicity of use for the user. Robustness and safety could be the same thing, but I guess safety refers to security issues, and robustness refers to crashing issues. In this instance, keeping the connection up and reconnecting if it goes down due to circumstances beyond our control (ei, ISP brings it down, or the power goes off) is clearly a robustness issue. Reusability can mean letting others use the class in a black-box manner, but I don't see that it could be useful that way. On the other hand, it could mean internal reusability of of this class and it's methods, which is very useful to my plans. Size should mean total resources consumed, including things like size of the executable, amount of ram and HD needed while running, and amount of storage required for data while not running. These can sometimes be in conflict. Size of the source is not important, it must be big enough to fulfill all the other requirements, and then there's the over documentation. Would debugability fall under testability or maintainability? All three have major areas of overlap. They also have a big impact on robustness and safety. Safety - Since this is a security program, much effort must be spent on security issues. Portability - This will need to run on as many different OS's as possible. Robustness - This must keep the connection up as much as possible. Simplicity - All software should be as simple to use as possible for the target audience. Reusability - The BIDS2 protocol is ideal for many other jobs that need to be done while you are logged on, so this will be extended to do those jobs. Testability - Impossible to test it without a BIDS2 server to connect to, with a server if you can login and stay logged in, then it works. When the protocol has been extended for the various purposes I plan on, then both sides of the protocol will exist in this class. Then test methods can be written that will exercise everything, but you would still need to login to a real BIDS2 server to test conformance to the specification. Maintainability - While I shall be the maintainer for as long as possible, my future is extremely uncertain at the moment and I may have to pass it on to some one else. Speed - There is no need for speed, only a need to keep CPU usage to a minimum while it is just ticking over. For the GUI, responsiveness is a major issue. Size - All else being equal, smaller is better. Notes Remember that Java Strings are Unicode, and BIDS2 is probably ASCII. Preliminary usage packet specification. This protocol is an extension of the BIDS2 protocol, which is why there is some strangeness in it. All integers are big endian (network order, high byte first). Since all parameters include a length, strings do not have an end byte (typically 0). A message consists of 16 unsigned bits of message type, 16 unsigned bits of message length, the session ID, and optional parameters. A parameter consists of 16 unsigned bits of message type, 16 unsigned bits of message length, and the actual parameter. There are two programs, the Login Client (LC) and the Usage Collector (UC). We can safely assume that LC and UC are running on the same computer, so these messages must go through the loopback device (127.0.0.1), defaulting to UDP port 5050 for the LC, but can be selected by the user. This means we do not have to authenticate the messages. This also means that the UC and the LC must have some means of the user specifying the LC's UDP port. When LC sees USAGE_UPDATE messages coming in, it assumes there is an UC on the port specified by the Session ID, and will send USAGE_FREE, USAGE_RESET, and USAGE_QUIT as needed. If the Session ID of a USAGE_UPDATE is different from the last one, LC will update the port it sends to. If there are no USAGE_UPDATE messages for one minute, LC will assume the UC has stopped. If LC is configured to automatically start an UC on login, then it should try to restart UC if it thinks UC has stopped. This implies that UC may have to handle being started twice, and should check if it is still running. LC can calculate the bytes per second based on the timestamps sent to LC. This means that UC can be lazy about sending us updates, so long as the bytes transfered is correct for the timestamp time (plus or minus a jiffy), things will be fine. Should I make the resolution of the timestamp finer? Updates to the actual on screen counters don't have to happen every microsecond, since updating any faster than a human can read is pointless. The free ranges list will be sent to LC from a central server (maybe run by BPC, maybe by Eric, Simon, and / or dvs1), and LC authenticates the list then sends it to UC. Eric has agreed to run this server and keep it up to date. I will send Eric a Java server for him to run when this is all implemented. Msg type - 16 bits unsigned USAGE_UPDATE = 0x0020 - sent by UC to inform LC of new usage figures. Must include at least one of the byte count parameters and the timestamp parameter. May include all of the byte count parameters, but see the NOTE below. USAGE_FREE = 0x0021 - sent by LC to inform UC about free ranges. Must include Free IP range parameter. This replaces the old parameter. USAGE_RESET = 0x0022 - sent by LC to tell UC to reset usage counts. No parameters. USAGE_QUIT = 0x0023 - sent by LC to tell UC to save usage and quit. No parameters. Msg length - 16 bits unsigned Number of total bytes in this message, from first byte of Msg type field, to final byte. Session ID - 32 bits unsigned port number sender is listening to. Zero or more parameters : Parameter type - 16 bits unsigned A type from the table below. Parameter length - 16 bits unsigned Total number of bytes in this parameter, from first byte of Parameter type to final byte. Parameter value - depends on parameter Parameter types : Timestamp - 0x0015 - 32 bits unsigned Number of seconds elapsed since 1970-01-01 00:00:00 GMT Free IP ranges - 0x0016 - Complete ASCII list of free IP's or domain names separated by commas. Can be : *.bigpond.net.au,24.192.0.0/18,24.192.1.*,24.192.0.0/255.255.192.0 Billable ICMP bytes sent - 0x0020 - 32 bits unsigned Billable ICMP bytes received - 0x0021 - 32 bits unsigned Billable UDP bytes sent - 0x0022 - 32 bits unsigned Billable UDP bytes received - 0x0023 - 32 bits unsigned Billable TCP bytes sent - 0x0024 - 32 bits unsigned Billable TCP bytes received - 0x0025 - 32 bits unsigned Free ICMP bytes sent - 0x0026 - 32 bits unsigned Free ICMP bytes received - 0x0027 - 32 bits unsigned Free UDP bytes sent - 0x0028 - 32 bits unsigned Free UDP bytes received - 0x0029 - 32 bits unsigned Free TCP bytes sent - 0x002A - 32 bits unsigned Free TCP bytes received - 0x002B - 32 bits unsigned Billable total bytes sent - 0x002C - 32 bits unsigned Billable total bytes received - 0x002D - 32 bits unsigned Free total bytes sent - 0x002E - 32 bits unsigned Free total bytes received - 0x002F - 32 bits unsigned NOTE : UC should either send total bytes, or ICMP, UDP, and TCP bytes, never both. Preliminary GUI packet specification. Most of the notes for the usage protocol apply to this protocol, as it is also an extension of the BIDS2 protocol. The protocol starts with the GUI logging in to the login client with an identical protocol to that used by the login client to login to the BIDS2 server. This is due to the fact that the GUI may be running on a different computer, and we don't want just anybody logging you off. I will modify this class so that it can be used to login to things in a more generic manner, then no one will have to worry about writing that part of the protocol, and I can forgo documenting it. The GUI host should be added to the list of trusted hosts. This is something that should be done later, so initially we should only just listen to the loopback device (127.0.0.1). The GUI will be the part that displays the usage meters, so that info must be transferred from the login client to the GUI, and can be done using the same protocol, after the GUI has logged in. Having the usage counter send the data to the login client, which then just sends it on to the GUI may seem a waste, but the UC and LC are both on the same computer, and requiring a login procedure for the UC won't work coz UC writers are not allowed to see the login protocol, and they won't use Java, so they can't use this class. Needed protocol bits : login - GUI -> LC - the RESTART_REQ can do this, so no need for anything else other than a new restart reason. RESTART_REASON_GUI = 0x0010 logout - GUI -> LC - can be similar to the STATUS_REQ, send the request, if we get no response try again a few times. If there is still no response, assume LC is already dead. Note that the connection will go down after this is processed, so respond to it before logging out. GUI_LOGOUT_REQ = 0x0024 notify of problem or progress - LC -> GUI - RESPONSE_TEXT and STATUS_CODE can be used for this, added to a STATUS_REQ style message. Some extra STATUS_CODES may be needed, but generally pass on STATUS_CODES from the BIDS2 server. Use sockets rather than datagram. GUI_STATUS_REQ = 0x0025 trigger debug dump - GUI -> LC - STATUS_REQ like yet again, and sockets also. GUI_DUMP_REQ = 0x0026 change of configuration details - GUI -> LC - Need to be ultra paranoid about this, maybe even to the point of not doing it at all. Leave it out until later. * */ public final class BPC_NO extends Object implements Runnable { /** * The main entry point for this class when run as an application, which it always should. * * @.precondition An IP address must have been assigned, probably via DHCP, and a route to * the login server must exist. * * @param String someArguments[] - The command line arguments, see usage(String) for details. * * @.modifies Parses all arguments and puts them in the appropriate fields, it * may then create some new BPC_NO objects and run them as threads. * * @.concurrency May start BIDS2() and networkChecker() as threads. * @.concurrency currentState is a synchronized field. * * @.postcondition I would like to logoff from the server before this finishes, * but that isn't always possible in practice, due to limitations in Java. * * @.example main({"-u", "username", "-p", "password"}); Create a new BPC_NO object and run it as a thread. * @.example main({"-logoff"}); Open a port to a currently running BPC_NO and tell it to logoff. */ public static void main(String someArguments[]) { String method = "main"; // Other arguments to support later : // -s server name Starts a new thread in the old instance that uses the same protocol // to inform my server tracker that this is a new server that is online, other details // will be needed. // What's this? Source code? It had to be here somewhere. System.runFinalizersOnExit(true); Properties temp = System.getProperties(); temp.put("user.timezone", "AET"); // Coz Australia/Melbourne doesn't work. System.setProperties(temp); // Get OS details. try { osVersion = System.getProperty("os.version"); osName = System.getProperty("os.name"); } catch (SecurityException e) {usage("Access to OS name or version not permitted: " + e.toString());} if (osVersion == "") osVersion = "unknown"; if (osName == "") osName = "unknown"; // Default to whatever properties there are. Properties BPC_Defaults = readProperties(method, "BPC_NO"); userName = BPC_Defaults.getProperty("-u"); passwordText = BPC_Defaults.getProperty("-p"); passwordFilename = BPC_Defaults.getProperty("-f"); String auth = BPC_Defaults.getProperty("-a"); if (auth != null) authServer = auth; try {debug = (new Integer(BPC_Defaults.getProperty("-d"))).shortValue();} catch (Exception e) {;} try { // Get any command line arguments. for (short i = 0; i < someArguments.length; i++) { if ((someArguments[i].equalsIgnoreCase("-u")) || (someArguments[i].equalsIgnoreCase("-l"))) userName = someArguments[++i]; else if (someArguments[i].equalsIgnoreCase("-p")) passwordText = someArguments[++i]; else if (someArguments[i].equalsIgnoreCase("-f")) passwordFilename = someArguments[++i]; else if (someArguments[i].equalsIgnoreCase("-d")) debug = (new Integer(someArguments[++i])).shortValue(); else if (someArguments[i].equalsIgnoreCase("-q")) debug = -1; else if (someArguments[i].equalsIgnoreCase("-v")) debug = 1; else if (someArguments[i].equalsIgnoreCase("-a")) authServer = someArguments[++i]; else if (someArguments[i].equalsIgnoreCase("-port")) clientPort = (new Integer(someArguments[++i])).shortValue(); else if (someArguments[i].equalsIgnoreCase("-gui")) gui = true; else if ((someArguments[i].equalsIgnoreCase("-dump")) || (someArguments[i].equalsIgnoreCase("-test")) || (someArguments[i].equalsIgnoreCase("-login")) || (someArguments[i].equalsIgnoreCase("-logoff")) || (someArguments[i].equalsIgnoreCase("-check")) || (someArguments[i].equalsIgnoreCase("-quit")) || (someArguments[i].equalsIgnoreCase("-poweroff")) || (someArguments[i].equalsIgnoreCase("-shutdown")) || (someArguments[i].equalsIgnoreCase("-suspend")) || (someArguments[i].equalsIgnoreCase("-resume")) ) logoffReason = someArguments[i]; } short pos = (short) authServer.indexOf(':'); if (pos != -1) { authPort = (new Integer(authServer.substring(pos + 1))).shortValue(); authServer = authServer.substring(0, pos); } // Check arguments. if (clientPort == 0) clientPort = DEFAULT_AUTH_PORT; if (authServer == null) authServer = DEFAULT_AUTH_SERVER; if (authPort == 0) authPort = DEFAULT_AUTH_PORT; // Send arguments if that is what we are doing. if (logoffReason != "") { sendUI(method, logoffReason); debugMsg(2, method, "Finished"); System.exit(0); } if (userName == null) usage("No username supplied."); if ((passwordText != null) && (passwordFilename != null)) usage("Only one of -p and -f can be supplied"); // Get password. if (passwordText == null) { byte buffer[] = new byte[16]; if (passwordFilename != null) // Get password from a file. { try { FileInputStream F = new FileInputStream(passwordFilename); try {F.read(buffer);} catch (SecurityException e) {usage("Access to " + passwordFilename + " not permitted: " + e.toString());} catch (IOException e) {usage("Unable to read " + passwordFilename + ": " + e.toString());} finally {F.close();} } catch (FileNotFoundException e) {usage("File " + passwordFilename + " not found: " + e.toString());} catch (SecurityException e) {usage("Access to " + passwordFilename + " not permitted: " + e.toString());} catch (IOException e) {usage("Unable to read " + passwordFilename + ": " + e.toString());} } else // Or get it from the user. { System.out.print("Enter password for " + userName + ": "); try {System.in.read(buffer);} catch (java.io.IOException e) {usage("No password supplied: " + e.toString());} } try {passwordText = new String(buffer, encoding);} catch (java.io.UnsupportedEncodingException e) {die(method, encoding + " encoding not known " + e.toString());} pos = (short) passwordText.indexOf('\n'); if (pos != -1) passwordText = passwordText.substring(0, pos); } /* // Only do this if /var/run/ exists. // attempt to write PID into /var/run/bids2login.pid. local($fileName); $fileName = "/var/run/$LOGIN_SOFTWARE.pid"; if (!(open(F,">$fileName"))) { errorMsg("WARNING: unable to write PID file $fileName:$!"); errorMsg("did you remember to run me as root?"); } else { printf F "%s",$$; close(F); } */ debugMsg(5, method, "Got username " + userName); debugMsg(5, method, "Got password ********"); debugMsg(5, method, "Got auth_server " + authServer); debugMsg(5, method, "Got auth_port " + authPort); debugMsg(5, method, "Got osName " + osName); debugMsg(5, method, "Got osVersion " + osVersion); Thread BIDS2Loop = new Thread(new BPC_NO("BIDS2"), "BIDS2Loop"); Thread networkCheckerLoop = new Thread(new BPC_NO("networkChecker"), "networkCheckerLoop"); if ((BIDS2Loop == null) || (networkCheckerLoop == null)) System.err.println("Failed to create threads."); else { try { BIDS2Loop.setDaemon(true); BIDS2Loop.start(); networkCheckerLoop.setDaemon(true); networkCheckerLoop.start(); if (!gui) UILoop(); // Wait for thread to finish. BIDS2Loop.join(); } catch (IllegalThreadStateException e) {debugMsg(3, method, "Can't make a daemon: " + e.toString());} catch (SecurityException e) {debugMsg(3, method, "Security exception: " + e.toString());} catch (InterruptedException e) {;} } } // It appears that Java has no concept of "things that NEED to be done when the program dies". // Despite the fact that there are plenty of places they can be hooked into, and Java comes // from a Unix environment. catch (ThreadDeath e) {System.err.println("Some nasty error: " + e.toString());} catch (UnknownError e) {System.err.println("Some nasty error: " + e.toString());} catch (InternalError e) {System.err.println("Some nasty error: " + e.toString());} catch (VirtualMachineError e) {System.err.println("Some nasty error: " + e.toString());} catch (Error e) {System.err.println("Some nasty error: " + e.toString());} finally { debugMsg(2, method, "Finished"); } } /** * Constructor. * * @.modifies ourThread and ourArgs */ public BPC_NO() { String method = "BPC_NO()"; debugMsg(2, method, "enter"); ourThread = null; ourArgs = null; debugMsg(2, method, "exit"); } /** * Constructor. * * @param String aThread - name of the method in this class to save as ourThread. * * @.modifies ourThread and ourArgs */ private BPC_NO(String aThread) { String method = "BPC_NO(aThread)"; debugMsg(2, method, "enter"); ourThread = null; ourArgs = null; try {ourThread = this.getClass().getDeclaredMethod(aThread, null);} catch (NoSuchMethodException e) {errorMsg(method, aThread + " no such method: " + e.toString());} catch (SecurityException e) {errorMsg(method, "not allowed to reflect: " + e.toString());} debugMsg(2, method, "exit"); } /** * Constructor. * * @param String aThread - name of the method in this class to save as ourThread. * * @param String someArgs[] - an array of argument Objects to pass to ourThread. * * @.modifies ourThread and ourArgs */ public BPC_NO(String aThread, Object someArgs[]) { String method = "BPC_NO(aMethod, someArgs[])"; debugMsg(2, method, "enter"); ourThread = null; ourArgs = someArgs; try { if (ourArgs == null) ourThread = this.getClass().getDeclaredMethod(aThread, null); else { int j = Array.getLength(ourArgs); Class ourClasses[] = new Class[j]; for (int i = 0; i < j; i++) ourClasses[i] = ourArgs[i].getClass(); try {ourThread = this.getClass().getDeclaredMethod(aThread, ourClasses);} catch (Exception e) { try {ourThread = this.getClass().getMethod(aThread, ourClasses);} catch (Exception e1) {;} } } } catch (NoSuchMethodException e) {errorMsg(method, "no such method: " + e.toString());} catch (SecurityException e) {errorMsg(method, "not allowed to reflect: " + e.toString());} debugMsg(2, method, "exit"); } /** * Cleanup. */ protected void finalize() { debugMsg(2, "BPC_NO", "Finalized"); } /** * Class wide cleanup. */ private static void classFinalize() { debugMsg(2, "classFinalize", "classFinalize"); } /** * Run this instances arbitrary method as a thread. * * @.concurrency Used to start an arbitrary method as a thread. * * @.postcondition A new thread is running. */ public void run() { String method = "run"; debugMsg(2, method, "enter"); try {ourThread.invoke(this, ourArgs);} catch (NullPointerException e) {errorMsg(method, "null pointer: " + e.toString());} catch (IllegalArgumentException e) {errorMsg(method, "illegal arg: " + e.toString());} catch (IllegalAccessException e) {errorMsg(method, "illegal access: " + e.toString());} catch (InvocationTargetException e) {;} debugMsg(2, method, "exit"); } /** * Do some more setup then run the protocol loop. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @.modifies Since this pretty much calls everything else... * * @.concurrency One of these will run for every server we are logged on to. */ private void BIDS2() { String method = "BIDS2"; debugMsg(2, method, "enter"); do { try { listenPort = -1; try {dceAddress = InetAddress.getByName(authServer);} catch (UnknownHostException e) {dontDie(method, "Could not find " + authServer + ": " + e.toString()); return;} try { ListenSocket = new DatagramSocket(); listenPort = (short) ListenSocket.getLocalPort(); debugMsg(3, method, "listening on port " + listenPort); } catch (SocketException e) {dontDie(method, "socket failed to open or bind: " + e.toString());} catch (SecurityException e) {dontDie(method, "not allowed to open UDP socket: " + e.toString());} } catch (ProtocolException e) {;} } while ((listenPort == -1) && !quitting); if (!quitting) { sessionId = listenPort; setState(method, INITIALIZE); } // Main loop. while (true) { try { try { if (quitting) throw(new ThreadDeath()); else { switch (getState()) { case INITIALIZE : protNegotiation(method); break; case LOGIN_REQ : loginRequest(method); break; case IDLE_LOGIN : idle(method); break; case START_LOGOUT : startLogout(method); break; case LOGOUT_REQ : logoutRequest(method); break; case IDLE_LOGOUT : idleLogout(method); break; default : die(method, "Unknown state encountered in state engine : " + getState()); break; } } } catch (ThreadDeath e) { if (quitting) { // Logoff first if needed. if (getState() == START_LOGOUT) startLogout(method); if (getState() == LOGOUT_REQ) logoutRequest(method); } finalize(); throw(e); } } catch (ProtocolException e) {;} // There may be a problem if a ProtocolException is thrown while we are quitting or logging off. } // while (true) // debugMsg(2, method, "exit"); } /** * Run the UI loop. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @.modifies Since this pretty much calls everything else... */ private static void UILoop() { String method = "mainLoop"; debugMsg(2, method, "enter"); ServerSocket controlSocket = null; { // Create the server socket try { // I should check to see if this is listening on 127.0.0.1 ONLY! controlSocket = new ServerSocket(clientPort, 20, null); debugMsg(3, method, "server socket created"); } catch (SecurityException e) {;} catch (SocketException e) {debugMsg(3, method, "socket exception: " + e.toString());} catch (IOException e) {debugMsg(3, method, "An I/O error occured: " + e.toString());} while (!quitting) // Listen to the server socket. { try { byte recvBuffer[] = new byte[1500]; Socket socket = controlSocket.accept(); if (socket.getInputStream().read(recvBuffer, 0, 1500) > 0) { ByteArrayInputStream recvBufferArray = new ByteArrayInputStream(recvBuffer); DataInputStream recvBufferStream = new DataInputStream(recvBufferArray); String message = (new String(recvBuffer, encoding)).trim(); command(message); } socket.close(); } catch (java.io.UnsupportedEncodingException e) {die(method, encoding + " encoding not known " + e.toString());} catch (SecurityException e) {debugMsg(3, method, "security exception: " + e.toString());} catch (SocketException e) {debugMsg(3, method, "socket exception: " + e.toString());} catch (InterruptedIOException e) {debugMsg(3, method, "timed out: " + e.toString());} catch (IOException e) {debugMsg(3, method, "I/O exception: " + e.toString());} } } debugMsg(2, method, "exit"); } public static void command(String message) { String method = "command"; debugMsg(2, method, "enter"); debugMsg(2, method, "server message recieved: " + message); if (message.equalsIgnoreCase("-login")) { setState(method, INITIALIZE); changeState(method); } else if (message.equalsIgnoreCase("-logoff")) logoffClientUser(method); else if (message.equalsIgnoreCase("-check")) ; // Do nothing. else if (message.equalsIgnoreCase("-quit")) logoffClientQuit(method); else if (message.equalsIgnoreCase("-poweroff")) logoffClientPower(method); else if (message.equalsIgnoreCase("-shutdown")) logoffClientOs(method); else if (message.equalsIgnoreCase("-suspend")) ; // Should write this. else if (message.equalsIgnoreCase("-resume")) ; // Should write this. else if (message.equalsIgnoreCase("-dump")) debugDumpMsgs("debug*.txt"); debugMsg(2, method, "exit"); } //********************************************************************** // constants public final static String LOGIN_OSINFO = "Java 1.1 (Linux)"; public final static String LOGIN_SOFTWARE = "BPC_NO.class"; public final static String LOGIN_VERSION = "0.3.6 alpha"; public final static String LOGIN_VERSION_NO = "0.3"; public final static String LOGIN_VERSION_DATE = "2001-09-01 00:37:00"; public final static String[] CLASS_SOURCE_FILES = { "BPC_NO.properties", }; // defaults public final static short DEFAULT_AUTH_PORT = 5050; public final static String DEFAULT_AUTH_SERVER = "dce-server"; public final static int HEARTBEAT_TIMEOUT = (16 * 60); // 16 minutes public final static int TIMEOUT = 10; // timeout for sockets // message types / states final static short INITIALIZE = 0; final static short PROT_NEG_REQ = 1; final static short PROT_NEG_RESP = 2; final static short LOGIN_REQ = 3; final static short LOGIN_AUTH_REQ = 4; final static short LOGIN_RESP = 5; final static short LOGOUT_REQ = 6; final static short LOGOUT_AUTH_REQ = 7; final static short LOGOUT_RESP = 8; final static short AUTH_RESP = 9; final static short AUTH_REQ = 10; // Not actually used in BIDS 2. final static short STATUS_REQ = 11; final static short STATUS_RESP = 12; final static short RESTART_REQ = 13; final static short IDLE_LOGIN = 14; final static short START_LOGOUT = 15; final static short IDLE_LOGOUT = 16; final static short M_RESERVED_1 = 17; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_2 = 18; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_3 = 19; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_4 = 20; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_5 = 21; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_6 = 22; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_7 = 23; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_8 = 24; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_9 = 25; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_10 = 26; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_11 = 27; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_12 = 28; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_13 = 29; // Reserved as a buffer between my message types, and any HP might want. final static short M_RESERVED_14 = 30; // Reserved as a buffer between my message types, and any HP might want. final static short STATE_CHANGED = 31; final static String[] STATES = {"INITIALIZE", "send PROT_NEG_REQ", "wait PROT_NEG_RESP", "send LOGIN_REQ", "send LOGIN_AUTH_REQ", "wait LOGIN_RESP", "send LOGOUT_REQ", "send LOGOUT_AUTH_REQ", "wait LOGOUT_RESP", "wait AUTH_RESP", "send AUTH_REQ", "received STATUS_REQ", "send STATUS_RESP", "received RESTART_REQ", "IDLE_LOGIN", "START_LOGOUT", "IDLE_LOGOUT", "M_RESERVED_1", "M_RESERVED_2", "M_RESERVED_3", "M_RESERVED_4", "M_RESERVED_5", "M_RESERVED_6", "M_RESERVED_7", "M_RESERVED_8", "M_RESERVED_9", "M_RESERVED_10", "M_RESERVED_11", "M_RESERVED_12", "M_RESERVED_13", "M_RESERVED_14", "STATE_CHANGED"}; // message parameters final static short RESERVED = 0; final static short PROT_LIST = 1; final static short PROT_SELECT = 2; final static short CLIENT_VERSION = 3; final static short OS_IDENTITY = 4; final static short OS_VERSION = 5; final static short REASON_CODE = 6; final static short USER_NAME = 7; final static short REQUEST_PORT = 8; final static short RESPONSE_TEXT = 9; final static short STATUS_CODE = 10; final static short AUTH_CRED = 11; final static short NONCE = 12; final static short SEQUENCE_NUM = 13; final static short HASH_METHOD = 14; final static short LOGIN_SERVICE_PORT = 15; final static short LOGOUT_SERVICE_PORT = 16; final static short STATUS_SERVICE_PORT = 17; final static short SUSPEND_INDICATOR = 18; final static short STATUS_AUTH = 19; final static short RESTART_AUTH = 20; final static short TIMESTAMP = 21; final static short TRUSTED_SERVERS = 22; final static short LOGIN_PARAM_HASH = 23; final static short LOGIN_SERVER_HOST = 24; final static short P_RESERVED_1 = 25; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_2 = 26; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_3 = 27; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_4 = 28; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_5 = 29; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_6 = 30; // Reserved as a buffer between my parameter types, and any HP might want. final static short P_RESERVED_7 = 31; // Reserved as a buffer between my parameter types, and any HP might want. final static String[] PARAMS = {"RESERVED", "PROT_LIST", "PROT_SELECT", "CLIENT_VERSION", "OS_IDENTITY", "OS_VERSION", "REASON_CODE", "USER_NAME", "REQUEST_PORT", "RESPONSE_TEXT", "STATUS_CODE", "AUTH_CRED", "NONCE", "SEQUENCE_NUM", "HASH_METHOD", "LOGIN_SERVICE_PORT", "LOGOUT_SERVICE_PORT", "STATUS_SERVICE_PORT", "SUSPEND_INDICATOR", "STATUS_AUTH", "RESTART_AUTH", "TIMESTAMP", "TRUSTED_SERVERS", "LOGIN_PARAM_HASH", "LOGIN_SERVER_HOST", "P_RESERVED_1", "P_RESERVED_2", "P_RESERVED_3", "P_RESERVED_4", "P_RESERVED_5", "P_RESERVED_6", "P_RESERVED_7"}; // protocols final static short PROT_RESERVED = 0; final static short PROT_CHALLENGE = 1; // login reasons final static short LOGIN_REASON_NORMAL = 0; final static short LOGIN_REASON_RESUME = 1; // restart reasons final static short RESTART_REASON_ADMIN = 0; final static short RESTART_REASON_RESERVED_1 = 1; final static short RESTART_REASON_RESERVED_2 = 2; final static short RESTART_REASON_RESERVED_3 = 3; final static short RESTART_REASON_UNKNOWN = 4; // logout reasons final static short LOGOUT_REASON_USER_INITIATED = 0; final static short LOGOUT_REASON_APP_SHUTDOWN = 1; final static short LOGOUT_REASON_OS_SHUTDOWN = 2; final static short LOGOUT_REASON_UNKNOWN = 3; // hash methods final static short HASH_NO = 0; final static short HASH_MD5 = 1; // status codes final static short SUCCESS = 0; final static short USER_NAME_NOT_FOUND = 1; final static short INCORRECT_PASSWORD = 2; final static short ACCOUNT_DISABLED = 3; final static short USER_DISABLED = 4; final static short ALREADY_LOGGED_IN = 100; final static short LOGIN_AUTH_RETRY_LIMIT = 101; final static short LOGIN_SUCCESS_OLDSW = 102; final static short LOGIN_FAIL_OLDSW = 103; final static short ALREADY_LOGGED_OUT = 200; final static short LOGOUT_AUTH_RETRY_LIMIT = 201; final static short PROT_SUCCESS_OLDSW = 300; final static short PROT_FAIL_OLDSW = 301; final static short PROT_FAIL_INV_PROT = 302; final static short UNKNOWN_ERROR = 500; final static short FAIL_USER_NAME_VALIDATE = 501; final static short FAIL_PASSWORD_VALIDATE = 502; /* if (code >= 500) code -= 486; else if (code >= 300) code -= 289; else if (code >= 200) code -= 191; else if (code >= 100) code -= 95; */ // Should use a hash for this, to avoid the above calculation. final static String[] STATUSES = {"success", "user not found", "incorrect password", "account disabled", "user disabled", "login successful, already logged in", "login attempts exceeded the max. Please try again later", "login successful, old software", "login failed, old software", "logout successful, already logged out", "logout authentication retry limit exceeded. Please try again later", "protocol negotiation successful, old software", "protocol negotiation failed, old software", "protocol negotiation failed, invalid protocol", "unknown server error. Please try again later", "server could not perform user authentication. Please try again later", "server could not perform password validation. Please try again later", "unknown status code received"}; //********************************************************************** /** * Negotiate the login protocol. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies State change. */ private void protNegotiation(String aMethod) throws ProtocolException { aMethod += ".protNegotiation"; debugMsg(2, aMethod, "enter"); Socket socket = openSocket(aMethod, authServer, authPort); startTransaction(aMethod, PROT_NEG_REQ); addParam(aMethod, CLIENT_VERSION, (short) ((new Float(LOGIN_VERSION_NO)).doubleValue() * 100.0)); addParam(aMethod, OS_IDENTITY, osName); addParam(aMethod, OS_VERSION, osVersion); // add supported protocols here, order most-preferable to least-preferable ... addParam(aMethod, PROT_LIST, PROT_CHALLENGE); sendTransaction(aMethod, socket, sendArray.toByteArray()); recvTransaction(aMethod, socket, PROT_NEG_RESP); try {socket.close();} catch (IOException e) {;} if (getMsgParams(aMethod) == SUCCESS) setState(aMethod, LOGIN_REQ); else dontDie(aMethod, "protocol negotiation didn't succeed"); debugMsg(2, aMethod, "exit"); } /** * Login to a server. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @.precondition Protocol must have been decided. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies numLogins is incrememnted. State change. * * @.postcondition User is now logged in to server. */ private void loginRequest(String aMethod) throws ProtocolException { aMethod += ".loginRequest"; debugMsg(2, aMethod, "enter"); Socket socket = openSocket(aMethod, authServer, authPort); startTransaction(aMethod, LOGIN_REQ); addParam(aMethod, USER_NAME, userName); addParam(aMethod, CLIENT_VERSION, (short) ((new Float(LOGIN_VERSION_NO)).doubleValue() * 100.0)); addParam(aMethod, OS_IDENTITY, osName); addParam(aMethod, OS_VERSION, osVersion); numLogins++; if (numLogins > 1) addParam(aMethod, REASON_CODE, LOGIN_REASON_RESUME); else addParam(aMethod, REASON_CODE, LOGIN_REASON_NORMAL); addParam(aMethod, REQUEST_PORT, listenPort); sendTransaction(aMethod, socket, sendArray.toByteArray()); if (recvTransaction(aMethod, socket, LOGIN_RESP) == AUTH_RESP) // Does server want our credentials { debugMsg(3, aMethod, "credentials needed"); if (getMsgParams(aMethod) == SUCCESS) { long authTimestamp = (new Date().getTime()) / 1000; ByteArrayOutputStream authArray = new ByteArrayOutputStream(); DataOutputStream authBefore = new DataOutputStream(authArray); startTransaction(aMethod, LOGIN_AUTH_REQ); try { authBefore.write(nonce, 0, 16); authBefore.write(sendPassword, 0, sendPassword.length); authBefore.writeInt((int) authTimestamp); authBefore.writeShort(LOGIN_AUTH_REQ); } catch (IOException e) {;} addParam(aMethod, AUTH_CRED, generateMd5(authArray.toByteArray()), 16); addParam(aMethod, TIMESTAMP, (int) authTimestamp); sendTransaction(aMethod, socket, sendArray.toByteArray()); recvTransaction(aMethod, socket, LOGIN_RESP); } } try {socket.close();} catch (IOException e) {;} if (getMsgParams(aMethod) == SUCCESS) { debugMsg(0, aMethod, userName + " login at " + (new Date().toString()) + " successful"); setState(aMethod, IDLE_LOGIN); } else dontDie(aMethod, "got to end of state without changing"); debugMsg(2, aMethod, "exit"); } /** * Wait for heartbeats, logoffs, and timeouts. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @.precondition User must be logged in to server. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies DESCRIPTION * * @.concurrency Will wait for datagrams to arrive at listenSocket and dispatch them. * * @.concurrency Have to Thread.sleep(1) to catch interupted thread, but that is probably the * only time it can be caught. Why can't Java wait on many things at once? * * @.postcondition Unknown, since any state change will cause idle() to exit. */ private void idle(String aMethod) throws ProtocolException { aMethod += ".idle"; debugMsg(2, aMethod, "enter"); while ((getState() == IDLE_LOGIN) && !quitting) { try { short amountRead = 0; byte buffer[] = new byte[1500]; DatagramPacket packet = new DatagramPacket(buffer, 1500); ByteArrayInputStream bufferArray = new ByteArrayInputStream(buffer); DataInputStream bufferStream = new DataInputStream(bufferArray); // ListenSocket.setSoTimeout(60 * 1000); ListenSocket.receive(packet); if ((amountRead = (short) packet.getLength()) > 0) { short recvMsgType = bufferStream.readShort(); short recvMsgLength = bufferStream.readShort(); InetAddress fromAddress = packet.getAddress(); debugMsg(3, aMethod, "received " + recvMsgLength + " bytes, transaction code " + recvMsgType); if (recvMsgLength != amountRead) debugMsg(0, aMethod, "WARNING: expected msg length " + recvMsgLength + ", got " + amountRead); debugDump(aMethod, "received packet", buffer, amountRead); // authenticate against server list if (!addresses.contains(fromAddress)) { errorMsg(aMethod, "RECEIVED STATUS PACKET FROM NON-TRUSTED HOST " + fromAddress.getHostName()); debugMsg(0, aMethod, "RECEIVED STATUS PACKET FROM NON-TRUSTED HOST " + fromAddress.getHostName()); } else { debugMsg(3, aMethod, "received status packet from authorised host " + fromAddress.getHostName()); if (recvMsgType == STATUS_REQ) { setState(aMethod, STATUS_REQ); processStatusReq(aMethod); } else if (recvMsgType == RESTART_REQ) { setState(aMethod, RESTART_REQ); processRestartReq(aMethod); } else if (recvMsgType == STATE_CHANGED) break; else { dontDie(aMethod, "unexpected response: got msg " + recvMsgType); break; } } } else { dontDie(aMethod, "got eof? - not possible"); break; } } catch (SecurityException e) {debugMsg(3, aMethod, "security exception: " + e.toString());} catch (SocketException e) {debugMsg(3, aMethod, "socket exception: " + e.toString());} // catch (InterruptedIOException e) // {debugMsg(2, aMethod, "timed out: " + e.toString());} catch (IOException e) {debugMsg(3, aMethod, "I/O exception: " + e.toString());} } debugMsg(2, aMethod, "exit"); } /** * Respond to heartbeats. * * @.precondition Heartbeat datagram must have been received, but it's contents are checked. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies sequenceNum is incremented. lastHeartbeat is updated to current time. State change. */ private void processStatusReq(String aMethod) throws ProtocolException { aMethod += ".processStatusReq"; ByteArrayOutputStream statusAuthArray = new ByteArrayOutputStream(); DataOutputStream statusAuthBefore = new DataOutputStream(statusAuthArray); debugMsg(2, aMethod, "enter"); getMsgParams(aMethod); // respond to status request ... startTransaction(aMethod, STATUS_RESP); addParam(aMethod, STATUS_CODE, SUCCESS); sequenceNum++; try { statusAuthBefore.write(nonce, 0, 16); statusAuthBefore.write(sendPassword, 0, sendPassword.length); statusAuthBefore.writeInt(sequenceNum); statusAuthBefore.writeShort(STATUS_RESP); } catch (IOException e) {;} addParam(aMethod, STATUS_AUTH, generateMd5(statusAuthArray.toByteArray()), 16); addParam(aMethod, SEQUENCE_NUM, sequenceNum); sendUdpTransaction(aMethod, sendArray.toByteArray(), dceAddress, statusServerPort); lastHeartbeat = (new Date().getTime()) / 1000; debugMsg(1, aMethod, "Heartbeat for " + userName + " at " + (new Date().toString())); setState(aMethod, IDLE_LOGIN); debugMsg(2, aMethod, "exit"); } /** * Authenticate restart requests. * * @.precondition restart request datagram must have been received, but it's contents are checked. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies State change. */ private void processRestartReq(String aMethod) throws ProtocolException { aMethod += ".processRestartReq"; ByteArrayOutputStream hashArray = new ByteArrayOutputStream(); DataOutputStream hashVerify = new DataOutputStream(hashArray); debugMsg(2, aMethod, "enter"); getMsgParams(aMethod); // if ((restartAuth != "") && (restartTimestamp > 0)) if ((restartAuth[0] != 0) && (restartTimestamp > 0)) { // okay to restart try { hashVerify.write(nonce, 0, 16); hashVerify.write(sendPassword, 0, sendPassword.length); hashVerify.writeInt((int) restartTimestamp); hashVerify.writeShort(RESTART_REQ); } catch (IOException e) {;} if (generateMd5(hashArray.toByteArray()) == restartAuth) setState(aMethod, INITIALIZE); } debugMsg(2, aMethod, "exit"); } /** * Logout from a server. * * @.precondition All the class static fields must hold correct values, either command * line or default. * * @.precondition logoutReason must contain a valid code. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies State change. * * @.postcondition User is logged out of the server. */ private void logoutRequest(String aMethod) throws ProtocolException { aMethod += ".logoutRequest"; debugMsg(2, aMethod, "enter"); Socket socket = openSocket(aMethod, authServer, authPort); startTransaction(aMethod, LOGOUT_REQ); addParam(aMethod, USER_NAME, userName); addParam(aMethod, CLIENT_VERSION, (short) ((new Float(LOGIN_VERSION_NO)).doubleValue() * 100.0)); addParam(aMethod, OS_IDENTITY, osName); addParam(aMethod, OS_VERSION, osVersion); addParam(aMethod, REASON_CODE, logoutReason); sendTransaction(aMethod, socket, sendArray.toByteArray()); if (recvTransaction(aMethod, socket, LOGOUT_RESP) == AUTH_RESP) // Does server want our credentials { if (getMsgParams(aMethod) == SUCCESS) { long authTimestamp = (new Date().getTime()) / 1000; ByteArrayOutputStream authArray = new ByteArrayOutputStream(); DataOutputStream authBefore = new DataOutputStream(authArray); startTransaction(aMethod, LOGOUT_AUTH_REQ); try { authBefore.write(nonce, 0, 16); authBefore.write(sendPassword, 0, sendPassword.length); authBefore.writeInt((int) authTimestamp); authBefore.writeShort(LOGOUT_AUTH_REQ); } catch (IOException e) {;} addParam(aMethod, AUTH_CRED, generateMd5(authArray.toByteArray()), 16); addParam(aMethod, TIMESTAMP, (int) authTimestamp); sendTransaction(aMethod, socket, sendArray.toByteArray()); recvTransaction(aMethod, socket, LOGOUT_RESP); } } try {socket.close();} catch (IOException e) {;} if (getMsgParams(aMethod) == SUCCESS) { debugMsg(0, aMethod, userName + " logoff at " + (new Date().toString()) + " successful"); setState(aMethod, IDLE_LOGOUT); } else dontDie(aMethod, "got to end of state without changing"); debugMsg(2, aMethod, "exit"); } /** * Do nothing after a logout. * * @.precondition User must be logged out. * * @param String aMethod - Name of method to print in error and debug messages. */ private void idleLogout(String aMethod) { aMethod += ".idleLogout"; debugMsg(2, aMethod, "enter"); while (getState() == IDLE_LOGOUT) { try {Thread.sleep(1000);} catch (InterruptedException e) {debugMsg(2, aMethod, "thread interrupted: " + e.toString());} } debugMsg(2, aMethod, "exit"); } //********************************************************************** /** * Store a debugging message. * * @param String aString - The debug message. * * @.modifies debugOutput - adds new aString, removes old ones. * * @.example addDebug("creating socket, protocol TCP"); */ private static void addDebug(String aString) { long rightNow = new Date().getTime(); // Add message. debugOutput.addElement(rightNow + "|" + aString); // Remove old messages. rightNow -= 1000*60*30; // 30 minutes. try { while (true) { String output = (String) debugOutput.firstElement(); if (rightNow > Long.valueOf(output.substring(0, output.indexOf("|"))).longValue()) debugOutput.removeElementAt(0); else break; } } catch (NoSuchElementException e) {;} } /** * Add an array of bytes to the send buffer. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aType - The type of parameter to append. * * @param byte[] aParam - The array of bytes to append. * * @param int aLength - the number of bytes to append. * * @.modifies sendBuffer. * * @.example addBytesParam(method, AUTH_CRED, generateMd5(authArray.toByteArray()), 16); * * @.todo Make a version of this without the aLength parameter, if needed. * * @.todo Add MD5 generation here, since it is always used that way. */ private void addParam(String aMethod, short aType, byte[] aParam, int aLength) { debugDump(aMethod + ".addParam", "set " + PARAMS[aType] + " to", aParam, aLength); try { sendBuffer.writeShort(aType); sendBuffer.writeShort((short) (aLength + 4)); sendBuffer.write(aParam, 0, aLength); } catch (IOException e) {;} } /** * Add an integer to the send buffer. Also handles the special case of starting * the transaction. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aType - The type of parameter to append. * * @param int aParam - The integer to apend. * * @.modifies sendBuffer. * * @.example addParam(method, SEQUENCE_NUM, sequenceNum); */ private void addParam(String aMethod, short aType, int aParam) { if (aMethod.endsWith(".startTransaction")) debugMsg(4, aMethod + ".addParam", "transaction " + STATES[aType] + ", SessionId " + aParam); else debugMsg(4, aMethod + ".addParam", "set " + PARAMS[aType] + " to " + aParam); try { sendBuffer.writeShort(aType); sendBuffer.writeShort(8); sendBuffer.writeInt(aParam); } catch (IOException e) {;} } /** * Add a short to the send buffer. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aType - The type of parameter to append. * * @param short aParam - The short to append. * * @.modifies sendBuffer. * * @.example addParam(method, REQUEST_PORT, listenPort); */ private void addParam(String aMethod, short aType, short aParam) { debugMsg(4, aMethod + ".addParam", "set " + PARAMS[aType] + " to " + aParam); try { sendBuffer.writeShort(aType); sendBuffer.writeShort(6); sendBuffer.writeShort(aParam); } catch (IOException e) {;} } /** * Add a string to the send buffer. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aType - The type of parameter to append. * * @param String aParam - The string to append. * * @,modifies sendBuffer. * * @,example addParam(method, OS_VERSION, osVersion); */ private void addParam(String aMethod, short aType, String aParam) { debugMsg(4, aMethod + ".addParam", "set " + PARAMS[aType] + " to " + aParam); try { sendBuffer.writeShort(aType); sendBuffer.writeShort(aParam.length() + 4); sendBuffer.writeBytes(aParam); } catch (IOException e) {;} } /** * Send STATE_CHANGED message to ourselves. * * @param String aMethod - Name of method to print in error and debug messages. */ private static void changeState(String aMethod) { aMethod += ".changeState"; ByteArrayOutputStream sendArray = new ByteArrayOutputStream(); DataOutputStream sendBuffer = new DataOutputStream(sendArray); sendArray.reset(); if (listenPort != -1) { try { sendBuffer.writeShort(STATE_CHANGED); sendBuffer.writeShort(8); sendBuffer.writeInt((int) sessionId); sendUdpTransaction(aMethod, sendArray.toByteArray(), InetAddress.getLocalHost(), listenPort); } catch (UnknownHostException e) {;} catch (IOException e) {;} } } /** * Dump an array of bytes as hex. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aMessage - The title of this hex dump. * * @param byte values[] - The array of bytes to dump. * * @param int aLength - The length of bytes to dump, or zero to dump all. * * @.example debugDump(method, "received packet", buffer, amountRead); * * @.todo Make another version of this without the aLength parameter. * * @.todo Rename this to debugMsg. */ private static void debugDump(String aMethod, String aMessage, byte values[], int aLength) { String output = ":"; if (aLength == 0) aLength = values.length; if (aLength > 1500) aLength = 1500; for (short item = 0; item < aLength; item++) { String hex = "0" + Integer.toHexString((int) values[item]); output += " " + item + ":" + hex.substring(hex.length() - 2, hex.length()); } debugMsg(4, aMethod, aMessage + output); } /** * Output the last few debugging message. * * @param String aFile - The file to dump into, an asterix means to add * the next available integer, otherwise don't overwrite an existing file. * * @.example debugDumpMsgs("debugDump*.txt"); */ private static void debugDumpMsgs(String aFile) { testJVM(); try { for (Enumeration msgs = debugOutput.elements(); msgs.hasMoreElements(); ) { String output = (String) msgs.nextElement(); System.out.println(output.substring(output.indexOf("|") + 1, output.length())); } } catch (NoSuchElementException e) {;} } /** * Output a debugging message. * * @param int aLevel - The debug level to print this message at. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aString - The debug message. * * @.modifies debugOutput - adds new aString, removes old ones. * * @.example debugMsg(2, method, "creating socket, protocol TCP"); */ private static void debugMsg(int aLevel, String aMethod, String aString) { String output = aString; output += "."; if (aLevel > 1) // Login, logoff, and heartbeat messages don't need the function trace. output = aLevel + " " + aMethod + ": " + output; if (debug >= aLevel) { System.out.println(output); System.out.flush(); } addDebug(output); } /** * Die with an excuse. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aString - The reason for dieing. * * @.postcondition JVM is stopped. * * @.example die(method, "An I/O error occured: " + e.toString()); */ private static void die(String aMethod, String aString) { errorMsg(aMethod, aString); System.exit(1); } /** * Don't die, restart with an excuse. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aString - The reason for dieing. * * @.postcondition login is restarted. * * @.modifies currentState * * @.example dontDie(method, "An I/O error occured: " + e.toString()); */ private static void dontDie(String aMethod, String aString) throws ProtocolException { errorMsg(aMethod, aString); try {Thread.sleep(5000);} // Sleep for 5 seconds. catch (InterruptedException e) {;} setState(aMethod, INITIALIZE); throw(new ProtocolException(aString)); } /** * Output an error message. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aString - The error message. * * @.example errorMsg(method, "RECEIVED STATUS PACKET FROM NON-TRUSTED HOST " + fromAddress.getHostName()); */ private static void errorMsg(String aMethod, String aString) { String output; if (gui) output = aString + "!"; else output = (new Date().toString()) + " [" + LOGIN_SOFTWARE + "] " + aMethod + ": " + aString + "!"; System.err.println(output); System.err.flush(); addDebug(output); } /** * generate an MD5 hash from a byte array. * * @param bytes[] inBytes - Bytes to perform MD5 digest on. * * @return byte[] - MD5 digest of the input, or null. * * @.example if (generateMd5(hashArray.toByteArray()) == restartAuth) */ private byte[] generateMd5(byte[] inBytes) { try {return(MessageDigest.getInstance("MD5").digest(inBytes));} catch (java.security.NoSuchAlgorithmException e) {return(null);} } /** * Unpack all message parameters. * * Be very paranoid about the contents of this method. * * @.precondition DESCRIPTION * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies DESCRIPTION * * @.concurrency DESCRIPTION * * @return DESCRIPTION * * @.postcondition DESCRIPTION * * @bug doesn't authenticate LOGIN_PARAM_HASH. * * @example if (getMsgParams(method) == SUCCESS) */ private short getMsgParams(String aMethod) throws ProtocolException { // Should make this more secure. short msgParamCode = 0; short paramLength = 0; byte msgParamValue[] = new byte[1500]; ByteArrayInputStream msgParamArray = new ByteArrayInputStream(msgParamValue); DataInputStream msgParamStream = new DataInputStream(msgParamArray); byte hashCode[] = new byte[16]; short statusCode = SUCCESS; short recvBufferAvailable = 0; ByteArrayOutputStream hashArray = new ByteArrayOutputStream(); DataOutputStream hashVerify = new DataOutputStream(hashArray); short tempCurrentState = getState(); short tempLogoutServerPort = -1; short tempStatusServerPort = -1; String tempServerList = ""; aMethod += ".getMsgParams"; msgParamCode = -1; msgParamValue[0] = 0; recvBufferAvailable = (short) (recvMsgLength - 8); for (short i = 0; i < recvBufferAvailable; i++) { // get next parameter in message response try { msgParamCode = recvBufferStream.readShort(); paramLength = recvBufferStream.readShort(); if (paramLength < 4) { msgParamCode = -1; msgParamValue[0] = 0; break; } else { recvBufferStream.read(msgParamValue, 0, paramLength - 4); recvBufferPos += paramLength; msgParamArray = new ByteArrayInputStream(msgParamValue); msgParamStream = new DataInputStream(msgParamArray); i += paramLength - 1; debugDump(aMethod, "message parameter " + msgParamCode + " len " + (paramLength - 4), msgParamValue, paramLength - 4); } } catch (EOFException e) {;} catch (IOException e) {;} switch (msgParamCode) { case RESERVED : break; case PROT_LIST : break; case PROT_SELECT : { try {loginProt = msgParamStream.readShort();} catch (IOException e) {;} if ( loginProt == PROT_CHALLENGE) debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " PROT_CHALLENGE"); else dontDie(aMethod, "received unknown " + PARAMS[msgParamCode] + " " + loginProt); break; } case CLIENT_VERSION : break; case OS_IDENTITY : break; case OS_VERSION : break; case REASON_CODE : { try {restartReason = msgParamStream.readShort();} catch (IOException e) {;} if (restartReason == RESTART_REASON_ADMIN) debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " ADMINISTRATION INTERFACE"); else debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " unknown reason"); break; } case USER_NAME : break; case REQUEST_PORT : break; case RESPONSE_TEXT : { String reason = ""; try {reason = new String(msgParamValue, encoding);} catch (java.io.UnsupportedEncodingException e) {die(aMethod, encoding + " encoding not known " + e.toString());} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " '" + reason + "'"); errorMsg(aMethod, "MSG: " + reason); break; } case STATUS_CODE : { try {statusCode = handleStatus(aMethod, msgParamStream.readShort());} catch (IOException e) {;} break; } case AUTH_CRED : break; case NONCE : { try {msgParamStream.read(nonce, 0, 16);} catch (IOException e) {;} debugDump(aMethod, "received " + PARAMS[msgParamCode], nonce, 0); break; } case SEQUENCE_NUM : break; case HASH_METHOD : { try {hashMethod = msgParamStream.readShort();} catch (IOException e) {;} if (hashMethod == HASH_NO) { debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " NONE"); try {sendPassword = passwordText.getBytes(encoding);} catch (java.io.UnsupportedEncodingException e) {die(aMethod, encoding + " encoding not known " + e.toString());} } else if (hashMethod == HASH_MD5) { debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " MD5"); try {sendPassword = generateMd5(passwordText.getBytes(encoding));} catch (java.io.UnsupportedEncodingException e) {die(aMethod, encoding + " encoding not known " + e.toString());} } else dontDie(aMethod, "received unkown " + PARAMS[msgParamCode] + " " + hashMethod); break; } case LOGIN_SERVICE_PORT : { try {loginServerPort = msgParamStream.readShort();} catch (IOException e) {;} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " " + loginServerPort); break; } case LOGOUT_SERVICE_PORT : { try {tempLogoutServerPort = msgParamStream.readShort();} catch (IOException e) {;} debugMsg(3, aMethod, "recevied " + PARAMS[msgParamCode] + " " + tempLogoutServerPort); break; } case STATUS_SERVICE_PORT : { try {tempStatusServerPort = msgParamStream.readShort();} catch (IOException e) {;} debugMsg(3, aMethod, "recevied " + PARAMS[msgParamCode] + " " + tempLogoutServerPort); break; } case SUSPEND_INDICATOR : { try // {suspendIndicator = msgParamStream.readUnsignedByte();} {suspendIndicator = msgParamStream.readShort();} catch (IOException e) {;} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " " + suspendIndicator); break; } case STATUS_AUTH : break; case RESTART_AUTH : { try {msgParamStream.read(restartAuth, 0, 16);} catch (IOException e) {;} debugDump(aMethod, "received " + PARAMS[msgParamCode], restartAuth, 0); break; } case TIMESTAMP : { try {restartTimestamp = msgParamStream.readInt();} catch (IOException e) {;} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " " + restartTimestamp); break; } case TRUSTED_SERVERS : { try {tempServerList = new String(msgParamValue, encoding);} catch (java.io.UnsupportedEncodingException e) {die(aMethod, encoding + " encoding not known " + e.toString());} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " " + tempServerList); break; } case LOGIN_PARAM_HASH : { try {msgParamStream.read(hashCode, 0, 16);} catch (IOException e) {;} debugDump(aMethod, "received " + PARAMS[msgParamCode], hashCode, 0); //for (int j = 0; j < (recvBufferPos + 16); j++) { //for (int k = j + 1; k <= (recvBufferPos + 16); k++) { try { byte pad[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; hashArray.reset(); hashVerify.write(nonce, 0, 16); hashVerify.write(sendPassword, 0, sendPassword.length); hashVerify.writeShort(LOGIN_RESP); // the buffer until just before this msgParam including the header of this parameter, // not including the common header. // Maybe not including the header for this parameter. hashVerify.write(recvBuffer, 8, recvBufferPos - (paramLength)); // hashVerify.write(recvBuffer, j, k); // hashVerify.write(pad, 0, 16); } catch (IOException e) {;} // XXX TODO: haven't yet worked this out ... if (generateMd5(hashArray.toByteArray()) == hashCode) { // in the words of denzel ... debugMsg(3, aMethod, "parameter hash: i concur ... message is AUTHENTIC" /*+ " j=" + j + " k=" + k*/); //System.exit(0); } else { debugMsg(0, aMethod, "Message may be NON-AUTHENTIC, Either the specification or the server is broken"); } } } // Get this started. lastHeartbeat = (new Date().getTime()) / 1000; // install received parameters .. setState(aMethod, tempCurrentState); logoutServerPort = tempLogoutServerPort; statusServerPort = tempStatusServerPort; parseServerList(tempServerList); sequenceNum = 0; // } // else // { // if not authentic, don't install any parameters received .. // dontDie(aMethod, "WARNING: received message is NOT authentic"); // statusCode = -1; // } break; } case LOGIN_SERVER_HOST : { try {msgParamStream.read(loginServerHost, 0, 1500);} catch (IOException e) {;} debugMsg(3, aMethod, "received " + PARAMS[msgParamCode] + " " + new String(loginServerHost)); break; } default : { debugMsg(0, aMethod, "received unknown parameter " + msgParamCode); if (msgParamCode == -1) // Trying to catch this bug. { debugDumpMsgs("NegOneBug.txt"); setState(aMethod, INITIALIZE); throw(new ProtocolException()); } break; } } } return(statusCode); } /** * Get the state. * * @.concurrency All access to currentState must be syncronized. * * @return short - currentState * * @.example switch (getState()) */ private static synchronized short getState() { return(currentState); } /** * Print generic status messages, and massage the status code. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aStatusCode - Status code to handle. * * @return short - modified aStatusCode, all successful codes are changed to SUCCESS. * * @.example statusCode = handleStatus(method, msgParamStream.readShort()); */ private static short handleStatus(String aMethod, short aStatusCode) throws ProtocolException { short code = aStatusCode; aMethod += ".handleStatus"; if (aStatusCode > 502) code = 17; else if (aStatusCode >= 500) code -= 486; else if (aStatusCode >= 300) code -= 289; else if (aStatusCode >= 200) code -= 191; else if (aStatusCode >= 100) code -= 95; if (code == 17) dontDie(aMethod, "received " + STATUSES[code] + ": " + aStatusCode); else if (aStatusCode == SUCCESS) debugMsg(3, aMethod, "received " + STATUSES[code]); else if ((aStatusCode == ALREADY_LOGGED_IN) || (aStatusCode == ALREADY_LOGGED_OUT) ) { aStatusCode = SUCCESS; debugMsg(3, aMethod, "received " + STATUSES[code]); } else if ((aStatusCode == PROT_SUCCESS_OLDSW) || (aStatusCode == LOGIN_SUCCESS_OLDSW) ) { aStatusCode = SUCCESS; debugMsg(3, aMethod, STATUSES[code]); errorMsg(aMethod, "NOTICE: Your login software is out of date. A newer version exists"); } else dontDie(aMethod, "received " + STATUSES[code]); return(aStatusCode); } /** * Logoff due to OS being shutdown. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies logoutReason set. State change. * * @.example dceServer.logoffClientOs(method); */ private static void logoffClientOs(String aMethod) { // logoff forced due to OS shutdown aMethod += ".logoffClientOs"; quitting = true; logoutReason = LOGOUT_REASON_OS_SHUTDOWN; setState(aMethod, START_LOGOUT); debugMsg(2, aMethod, "received signal TERM"); changeState(aMethod); } /** * Logoff due to UPS shutdown. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies logoutReason set. State change. * * @.bug This should actually be a logoff that doesn't fit the other functions. * * @.example dceServer.logoffClientPower(method); */ private static void logoffClientPower(String aMethod) { // logoff forced due to power failure aMethod += ".logoffClientPower"; quitting = true; logoutReason = LOGOUT_REASON_UNKNOWN; setState(aMethod, START_LOGOUT); debugMsg(2, aMethod, "received signal PWR"); changeState(aMethod); } /** * Logoff due to user quitting. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies logoutReason set. State change. * * @.example dceServer.logoffClientQuit(method); */ private static void logoffClientQuit(String aMethod) { // logoff forced due to power failure aMethod += ".logoffClientQuit"; quitting = true; logoutReason = LOGOUT_REASON_USER_INITIATED; setState(aMethod, START_LOGOUT); debugMsg(2, aMethod, "Quitting application"); changeState(aMethod); } /** * Logoff due to user request. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies logoutReason set. State change. * * @.example dceServer.logoffClientUser(method); */ private static void logoffClientUser(String aMethod) { // logoff initiated by user aMethod += ".logoffClientUser"; logoutReason = LOGOUT_REASON_USER_INITIATED; setState(aMethod, START_LOGOUT); debugMsg(2, aMethod, "received signal HUP"); changeState(aMethod); } /** * Check to see if the network is still up. * * @.modifies State change. * * @.concurrency A Daemon thread that runs all the time. */ private static void networkChecker() { String method = "networkChecker"; debugMsg(2, method, "enter"); boolean connected = true; while (!quitting) { if (connected) { try {Thread.sleep(1000 * 60);} // Wait sixty seconds. catch (InterruptedException e) {debugMsg(4, method, "thread interrupted: " + e.toString());} debugMsg(3, method, "checking network connection"); } else { debugMsg(2, method, "network is probably down, restarting"); // errorMsg(method, "WARNING: the network is probably down, restarting"); try {Thread.sleep(1000 * 3);} catch (InterruptedException e) {;} changeState(method); setState(method, INITIALIZE); } // Check to see if we're expecting to receive heartbeats. // Actually, the sending of these at regular intervals is optional according to the spec, // they can be sent only when needed, or skipped if the network is busy. if ((getState() == IDLE_LOGIN) && (suspendIndicator > 0) && (lastHeartbeat > 0) && (((new Date().getTime()) / 1000) > (lastHeartbeat + HEARTBEAT_TIMEOUT)) && !quitting) { debugMsg(2, method, "haven't seen a heartbeat for " + (HEARTBEAT_TIMEOUT / 60) + " minutes, restarting"); errorMsg(method, "WARNING: may have been logged off. No heartbeat for " + (HEARTBEAT_TIMEOUT / 60) + " minutes, restarting"); try {Thread.sleep(1000);} catch (InterruptedException e) {;} setState(method, INITIALIZE); changeState(method); continue; } // I need to ping something at reglular intervals to really tell if the network is up. // DON'T ping login server - since we can get to it if logged off. // DON'T ping DNS server - also available to us if logged off. // ping POP server - check for waiting mail. // ping SMTP server - get server capabilities. // ping news server - check for new messages in favourite newsgroup/s. // ping internet - try an actual ping of a list of external servers. // // ping proxy server - as for http, but via the proxy. // ping usage - login to usage page and parse the data (including date). connected = false; // ping http server - check if http:/www/ has been modified. try { URL web = new URL("http", "www", -1, "/"); HttpURLConnection webConnection = (HttpURLConnection) web.openConnection(); webConnection.setUseCaches(false); webConnection.setAllowUserInteraction(false); webConnection.setFollowRedirects(false); webConnection.setRequestMethod("HEAD"); webConnection.connect(); debugMsg(2, method, web.getProtocol() + "://" + web.getHost() + web.getFile() + " was last modified on " + webConnection.getLastModified()); webConnection.disconnect(); connected = true; } catch (Exception e) // It's only important that there was an error. {debugMsg(2, method, "problem checking web page: " + e.toString());} // ping http server - check if http:/www.nsw.bigpond.net.au/ has been modified. try { URL web = new URL("http", "www.nsw.bigpond.net.au", -1, "/"); HttpURLConnection webConnection = (HttpURLConnection) web.openConnection(); webConnection.setUseCaches(false); webConnection.setAllowUserInteraction(false); webConnection.setFollowRedirects(false); webConnection.setRequestMethod("HEAD"); webConnection.connect(); debugMsg(2, method, web.getProtocol() + "://" + web.getHost() + web.getFile() + " was last modified on " + webConnection.getLastModified()); webConnection.disconnect(); connected = true; } catch (Exception e) // It's only important that there was an error. {debugMsg(2, method, "problem checking web page: " + e.toString());} // ping http server - check if http:/www.vic.bigpond.net.au/ has been modified. try { URL web = new URL("http", "www.vic.bigpond.net.au", -1, "/"); HttpURLConnection webConnection = (HttpURLConnection) web.openConnection(); webConnection.setUseCaches(false); webConnection.setAllowUserInteraction(false); webConnection.setFollowRedirects(false); webConnection.setRequestMethod("HEAD"); webConnection.connect(); debugMsg(2, method, web.getProtocol() + "://" + web.getHost() + web.getFile() + " was last modified on " + webConnection.getLastModified()); webConnection.disconnect(); connected = true; } catch (Exception e) // It's only important that there was an error. {debugMsg(2, method, "problem checking web page: " + e.toString());} } debugMsg(2, method, "exit"); } /** * Open a TCP socket. * * @param String aMethod - Name of method to print in error and debug messages. */ private Socket openSocket(String aMethod, String aServer, short aPort) throws ProtocolException { Socket socket = null; aMethod += ".openSocket"; // make the socket filehandle debugMsg(3, aMethod, "creating socket, protocol TCP"); try {socket = new Socket(aServer, aPort);} catch (UnknownHostException e) {dontDie(aMethod, "Could not connect to " + aServer + ": " + e.toString());} catch (IOException e) {;}// {dontDie(aMethod, "An I/O error occured trying to create a socket: " + e.toString());} return(socket); } /** * Parse the list of servers into a Vector. * * @param aServerList - A list of server addresses seperated by commas. * * @.modifies Clears, then fills addresses. * * @.example parseServerList(tempServerList); */ private void parseServerList(String aServerList) { String method = "parseServerList"; InetAddress address[] = new InetAddress[0]; addresses = new Vector(); debugMsg(5, method, "cleared host list"); try {address = InetAddress.getAllByName(authServer);} catch (UnknownHostException e) {;} // Add our address. try { addresses.addElement(InetAddress.getLocalHost()); InetAddress last = (InetAddress) addresses.lastElement(); debugMsg(5, method, "we have ip address " + last.getHostAddress()); } catch (UnknownHostException e) {;} catch (NoSuchElementException e) {;} // Add authServer's addresses. try {address = InetAddress.getAllByName(authServer);} catch (UnknownHostException e) {;} for (short k = 0; k < address.length; k++) { addresses.addElement(address[k]); try { InetAddress last = (InetAddress) addresses.lastElement(); debugMsg(5, method, "host " + authServer + " has ip address " + last.getHostAddress()); } catch (NoSuchElementException e) {;} } // Add aServerList addresses. int i, j; for (i = 0; (j = aServerList.indexOf(',', i)) != -1; i = j) { String host = aServerList.substring(i, j); try {address = InetAddress.getAllByName(host);} catch (UnknownHostException e) {;} for (short k = 0; k < address.length; k++) { addresses.addElement(address[k]); debugMsg(5, method, "host " + host + " has ip address " + address[k].getHostAddress()); } } } /** * Read a properties file. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aString - The file name, without the .properties extension. * * @.example readProperties(method, "BPC_NO"); */ private static Properties readProperties(String aMethod, String aFile) { Properties aProp = new Properties(); aMethod += ".readProperties"; try { FileInputStream F = new FileInputStream(aFile + ".properties"); try {aProp.load(F);} catch (SecurityException e) {debugMsg(0, aMethod, "Access to " + aFile + ".properties" + " not permitted: " + e.toString());} catch (IOException e) {debugMsg(0, aMethod, "Unable to read " + aFile + ".properties" + ": " + e.toString());} finally {F.close();} } catch (FileNotFoundException e) {debugMsg(0, aMethod, "File " + aFile + ".properties" + " not found: " + e.toString());} catch (SecurityException e) {debugMsg(0, aMethod, "Access to " + aFile + ".properties" + " not permitted: " + e.toString());} catch (IOException e) {debugMsg(0, aMethod, "Unable to open " + aFile + ".properties" + ": " + e.toString());} return(aProp); } /** * Receive a TCP message. * * @param String aMethod - Name of method to print in error and debug messages. * * @param Socket aSocket - The socket to use. * * @param short aTransaction - The transaction we are expecting. * * @.modifies recvMsgType, recvMsgLength, recvBuffer, recvBufferArray, recvBufferStream recvBuferPos. * * @.concurrency Waits for TCP message to arrive at Socket, or times out. * * @return short - received message type, or 0. * * @.example recvTransaction(method, socket, PROT_NEG_RESP); */ private short recvTransaction(String aMethod, Socket aSocket, short aTransaction) throws ProtocolException { short amountRead = 0; aMethod += ".recvTransaction"; recvMsgType = -1; recvMsgLength = 0; debugMsg(3, aMethod, "waiting " + TIMEOUT + " seconds for transaction response .."); try { aSocket.setSoTimeout(TIMEOUT * 1000); if ((amountRead = (short) aSocket.getInputStream().read(recvBuffer, 0, 1500)) == 0) /* aSocket.close()*/; recvBufferArray = new ByteArrayInputStream(recvBuffer); recvBufferStream = new DataInputStream(recvBufferArray); } catch (SocketException e) {debugMsg(3, aMethod, "socket exception: " + e.toString());} catch (InterruptedIOException e) {debugMsg(3, aMethod, "timed out: " + e.toString());} catch (IOException e) {debugMsg(3, aMethod, "I/O exception: " + e.toString());} if (amountRead == 0) { dontDie(aMethod, "socket was closed on us!"); return(0); } else { try { recvBufferArray.reset(); recvBufferPos = 0; recvMsgType = recvBufferStream.readShort(); recvBufferPos += 2; recvMsgLength = recvBufferStream.readShort(); recvBufferPos += 2; } catch (EOFException e) {;} catch (IOException e) {;} debugMsg(3, aMethod, "received " + recvMsgLength + " bytes, transaction code " + recvMsgType + ", " + STATES[recvMsgType]); if (recvMsgLength != amountRead) debugMsg(0, aMethod, "WARNING: expected msg length " + recvMsgLength + ", got " + amountRead); debugDump(aMethod, "received packet", recvBuffer, amountRead); // move buffer contents, less header try {recvBufferStream.skip(4);} catch (EOFException e) {;} catch (IOException e) {;} if (recvMsgType != aTransaction) { if ((recvMsgType == AUTH_RESP) && ((aTransaction == LOGIN_RESP) || (aTransaction == LOGOUT_RESP))) ; // This is OK. else dontDie(aMethod, "unexpected response: wanted msg " + aTransaction + ", got " + recvMsgType); } return (recvMsgType); } } /** * Send a TCP message. * * @param String aMethod - Name of method to print in error and debug messages. * * @param Socket aSocket - The socket to use. * * @param byte aPacket[] - The TCP message to send. * * @.precondition sendArray (sendBuffer) must contain a message to send. * * @.modifies sendArray (sendBuffer). */ private static void sendTransaction(String aMethod, Socket aSocket, byte aPacket[]) throws ProtocolException { short length = (short) aPacket.length; aMethod += ".sendTransaction"; // fixup transaction length field .. aPacket[2] = (byte) ((0xff00 & length) >> 8); aPacket[3] = (byte) (0x00ff & length); // send transaction debugDump(aMethod, "send TCP packet", aPacket, 0); debugMsg(3, aMethod, "sending transaction: " + length + " bytes"); try { aSocket.getOutputStream().write(aPacket, 0, length); aSocket.getOutputStream().flush(); } catch (IOException e) {dontDie(aMethod, "Could not send TCP message: " + e.toString() + "/n");} } /** * Send an UDP message. * * @precondition sendArray (sendBuffer) must contain a message to send. dceAddress * must be a valid address we have a route to. * * @param String aMethod - Name of method to print in error and debug messages. * * @param byte aPacket[] - The UDP message to send. * * @param InetAddress aHost - host to send the message to. * * @param short aSendPort - port to send the message to. * * @.modifies sendArray (sendBuffer). * * @.example sendUdpTransaction(method, sendArray.toByteArray(), dceAddress, statusServerPort); */ private static void sendUdpTransaction(String aMethod, byte aPacket[], InetAddress aHost, short aSendPort) { aMethod += ".sendUdpTransaction"; short length = (short) aPacket.length; // fixup transaction length field .. aPacket[2] = (byte) ((0xff00 & length) >> 8); aPacket[3] = (byte) (0x00ff & length); debugDump(aMethod, "send UDP packet", aPacket, 0); // send transaction debugMsg(3, aMethod, "sending transaction: " + length + " bytes"); try { DatagramPacket packet = new DatagramPacket(aPacket, length, aHost, aSendPort); ListenSocket.send(packet); } catch (SecurityException e) {debugMsg(0, aMethod, "warning: Security violation sending packet: " + e.toString());} catch (IOException e) {debugMsg(0, aMethod, "warning: I/O problem sending packet: " + e.toString());} catch (IllegalArgumentException e) {debugMsg(0, aMethod, "warning: dodgy port number sending packet: " + e.toString());} } /** * Send a User Interface command to another instance. * * @param String aMethod - Name of method to print in error and debug messages. * * @param String aUI - Name the UI command to send. */ private static void sendUI(String aMethod, String aUI) { if (aUI.equalsIgnoreCase("-test")) { testJVM(); return; } debugMsg(3, aMethod, "creating socket, protocol TCP"); try // Sending a packet to the loopback port. { Socket Sock = new Socket(clientServer, clientPort); // try to use DataOutputStream.writeBytes(). Sock.getOutputStream().write(aUI.getBytes(encoding)); Sock.getOutputStream().flush(); System.out.println("Message sent."); } catch (java.io.UnsupportedEncodingException e) {System.out.println(encoding + " encoding not known " + e.toString());} catch (UnknownHostException e) {System.out.println("Could not connect to " + clientServer + ": " + e.toString());} catch (IOException e) {System.out.println("An I/O error occured: " + e.toString());} return; } /** * Set the state. * * @.precondition currentState must already be a valid state. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aCurrentState - State to change to. * * @.modifies currentState * * @.concurrency All access to currentState must be synchronized. * * @.example setState(aMethod, IDLE_LOGIN) */ private static synchronized void setState(String aMethod, short aCurrentState) { if (currentState != START_LOGOUT) { currentState = aCurrentState; debugMsg(2, aMethod + ".setState", "set state to " + STATES[currentState]); } } /** * Trigger a logout. * * @param String aMethod - Name of method to print in error and debug messages. * * @.modifies currentState * * @.concurrency All access to currentState must be synchronized. */ private static synchronized void startLogout(String aMethod) { currentState = LOGOUT_REQ; debugMsg(2, aMethod + ".startLogout", "set state to " + STATES[currentState]); } /** * Initialize a message. * * @param String aMethod - Name of method to print in error and debug messages. * * @param short aMsgType - The type of message to start. * * @.modifies Clears sendArray (sendBuffer) and appends aMsgType to it. * * @.example startTransaction(PROT_NEG_REQ); */ private void startTransaction(String aMethod, short aMsgType) { sendArray.reset(); addParam(aMethod + ".startTransaction", aMsgType, (int) sessionId); } /** * Output usage details. * * @param String aReason - The reason you are being shown the usage instructions. * * @.example usage("No username supplied."); */ private static void usage(String aReason) { System.err.println("ERROR: " + aReason); System.err.println(); System.err.println(LOGIN_SOFTWARE + " " + LOGIN_VERSION + ":"); System.err.println("[RCS $Id: " + LOGIN_SOFTWARE + ",v " + LOGIN_VERSION + " " + LOGIN_VERSION_DATE + " dvs1 Exp dvs1 $]"); System.err.println(); System.err.println("Usage: " + LOGIN_SOFTWARE + " -u username [-f pw_file | -p password] [-d level] \\"); System.err.println(" [-a authentication_server[:authentication_port]] [-port port] [-v] [-q]"); System.err.println(); System.err.println("Usage: " + LOGIN_SOFTWARE + "-logoff_reason [-d level] [-port port]"); System.err.println(); System.err.println(" '-u (username)' or '-l (username)'"); System.err.println(" is required. it is your login name."); System.err.println(); System.err.println(" your password for authentication can be supplied in one of"); System.err.println(" three ways:"); System.err.println(" 1. on the command line via '-p' parameter,"); System.err.println(" 2. be supplied within a filename, referenced by the '-f'"); System.err.println(" parameter, or"); System.err.println(" 3. if none of the above, the program will prompt you for it."); System.err.println(); System.err.println(" '-p (password)' (optional)"); System.err.println(" is used to supply the password associated with your username."); System.err.println(); System.err.println(" '-f (filename)' (optional)"); System.err.println(" is used to supply a filename that contains your password."); System.err.println(); System.err.println(" '-d (number)' (optional)"); System.err.println(" set debugging level at (number)"); System.err.println(" -1 - errors and warnings only (-q)"); System.err.println(" 0 - login and logout messages"); System.err.println(" 1 - heartbeats (-v)"); System.err.println(" 2 - trace functions"); System.err.println(" 3 - track protocol"); System.err.println(" 4 - dump packets + stuff"); System.err.println(" 5 - detail"); System.err.println(); System.err.println(" '-a authentication_server'"); System.err.println(" is the session-management host. default of '" + DEFAULT_AUTH_SERVER + "'"); System.err.println(" applies if not supplied."); System.err.println(); System.err.println(" 'authentication_port'"); System.err.println(" is the session-management host port. default of " + DEFAULT_AUTH_PORT); System.err.println(" applies if not supplied."); System.err.println(); System.err.println(" '-port (number)' (optional)"); System.err.println(" set the clients control port to (number)"); System.err.println(); System.err.println(" '-dump'"); System.err.println(" dump the last thirty minutes worth of debugging information"); System.err.println(); System.err.println(" '(-logoff_reason)'"); System.err.println(" cause a currently running " + LOGIN_SOFTWARE + " to logoff."); System.err.println(" (-logoff_reason) must be one of the following :"); System.err.println(" '-logoff' - User wants to log off."); System.err.println(" '-quit' - User wants to quit."); System.err.println(" '-poweroff' - UPS power is going down."); System.err.println(" '-shutdown' - OS is being shutdown."); System.err.println(" '-suspend' - A laptop is about to go into suspend mode."); System.err.println(" '-resume' - A laptop has come out of suspend mode."); System.exit(1); } private static void testJVM() { System.err.println(LOGIN_SOFTWARE + " version " + LOGIN_VERSION); System.err.println(); Properties prop = System.getProperties(); // prop.list(System.out); // System.out.println(); System.out.println("OS = " + System.getProperty("os.name") + ", " + System.getProperty("os.arch") + ", " + System.getProperty("os.version") + "."); System.out.println("JRE = " + System.getProperty("java.vendor") + ", " + System.getProperty("java.vendor.url") + ", " + System.getProperty("java.version") + "."); System.out.println("JREs = " + System.getProperty("java.specification.vendor") + ", " + System.getProperty("java.specification.name") + ", " + System.getProperty("java.specification.version") + "."); System.out.println("JVM = " + System.getProperty("java.vm.vendor") + ", " + System.getProperty("java.vm.name") + ", " + System.getProperty("java.vm.version") + "."); System.out.println("JVMs = " + System.getProperty("java.vm.specification.vendor") + ", " + System.getProperty("java.vm.specification.name") + ", " + System.getProperty("java.vm.specification.version") + "."); System.out.println("Class = " + System.getProperty("java.class.version") + ", " + System.getProperty("java.class.path") + "."); System.out.println(); Runtime rt = Runtime.getRuntime(); System.out.println("Total memory " + rt.totalMemory() + " Free " + rt.freeMemory()); System.out.println(); Calendar rightNow = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()); System.out.println((rightNow.toString()) + " " + TimeZone.getDefault() + " " + Locale.getDefault() + " " + System.getProperty("user.timezone", "AET")); System.out.println((rightNow.getTime().toString()) + " [" + rightNow.get(Calendar.YEAR) + "-" + (rightNow.get(Calendar.MONTH) + 1) + "-" + rightNow.get(Calendar.DATE) + " " + rightNow.get(Calendar.HOUR_OF_DAY) + ":" + rightNow.get(Calendar.MINUTE) + ":" + rightNow.get(Calendar.SECOND) + "]"); rightNow.setTime(new Date()); System.out.println((rightNow.getTime().toString()) + " [" + rightNow.get(Calendar.YEAR) + "-" + (rightNow.get(Calendar.MONTH) + 1) + "-" + rightNow.get(Calendar.DATE) + " " + rightNow.get(Calendar.HOUR_OF_DAY) + ":" + rightNow.get(Calendar.MINUTE) + ":" + rightNow.get(Calendar.SECOND) + "]"); System.out.println((new Date().toString()) + " [" + LOGIN_SOFTWARE + "] "); System.out.println(); } //********************************************************************** private static boolean gui = false; private static short authPort = 5050; private static String authServer = "dce-server"; private static short clientPort = 5050; private static String clientServer = "127.0.0.1"; private static short currentState = 0; private static short debug = 0; private static Vector debugOutput = new Vector(); private static String encoding = "UTF8"; // try ASCII, UTF8, US-ASCII, ISO-8859-1, SingleByte, Default, UnicodeBig, UnicodeLittle. private static String logoffReason = ""; private static String osName = ""; private static String osVersion = ""; private static String passwordFilename = null; private static String passwordText = null; private static String userName = null; private static long lastHeartbeat = -1; private static short listenPort = -1; private static DatagramSocket ListenSocket = null; private static short logoutReason = 0; private static boolean quitting = false; private static long sessionId = 0; private static short suspendIndicator = 1; private Vector addresses; private InetAddress dceAddress; private short hashMethod = -1; private short loginProt = -1; private byte loginServerHost[] = new byte[1500]; private short loginServerPort = -1; private short logoutServerPort = -1; private byte nonce[] = new byte[16]; private short numLogins = 0; // I hate these next three lines, too clumsy, and I have to do it several times with variations. private byte recvBuffer[] = new byte[1500]; private ByteArrayInputStream recvBufferArray = new ByteArrayInputStream(recvBuffer); private DataInputStream recvBufferStream = new DataInputStream(recvBufferArray); private short recvBufferPos = 0; private short recvMsgLength = 0; private short recvMsgType = -1; private byte restartAuth[] = new byte[16]; private short restartReason = -1; private long restartTimestamp = 0; private ByteArrayOutputStream sendArray = new ByteArrayOutputStream(); private DataOutputStream sendBuffer = new DataOutputStream(sendArray); private byte sendPassword[] = new byte[16]; private int sequenceNum = 0; private Socket Socket; private short statusServerPort = -1; private Method ourThread = null; private Object ourArgs[] = null; } // So I can test Distribute 1234567890