added applet mode, terminal window
[fleet.git] / src / de / mud / telnet / telnet.java
1 package de.mud.telnet;
2 /* "In case you would like to use the packages as libraries please
3  *  apply the GNU Library General Public License as documented in the
4  *  file COPYING.LIB." (from Telnet/Documentation/index.html)
5  */
6
7 /**
8  * telnet -- implements a simple telnet
9  * --
10  * $Id: telnet.java,v 1.19 1998/02/09 10:22:15 leo Exp $
11  * $timestamp: Mon Aug  4 13:11:14 1997 by Matthias L. Jugel :$
12  *
13  * This file is part of "The Java Telnet Applet".
14  *
15  * This is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2, or (at your option)
18  * any later version.
19  *
20  * "The Java Telnet Applet" is distributed in the hope that it will be 
21  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  * 
25  * You should have received a copy of the GNU General Public License
26  * along with this software; see the file COPYING.  If not, write to the
27  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28  * Boston, MA 02111-1307, USA.
29  */
30
31 import java.applet.Applet;
32 import java.awt.Frame;
33 import java.awt.Component;
34 import java.awt.Container;
35 import java.awt.BorderLayout;
36 import java.awt.Dimension;
37 import java.awt.Panel;
38 import java.awt.Event;
39 import java.util.Vector;
40 import java.util.Hashtable;
41 import java.util.Enumeration;
42 import java.io.IOException;
43
44 import de.mud.telnet.socket.*;
45 import de.mud.telnet.display.*;
46 import de.mud.telnet.modules.*;
47
48 /**
49  * A telnet implementation that supports different terminal emulations.
50  * @version $Id: telnet.java,v 1.19 1998/02/09 10:22:15 leo Exp $
51  * @author  Matthias L. Jugel, Marcus Meißner
52  */
53 public class telnet extends Applet implements Runnable, TerminalHost, StatusPeer
54 {
55   /**
56    * The telnet io methods.
57    * @see socket.TelnetIO
58    */
59   protected TelnetIO tio;
60
61   /**
62    * The terminal emulation (dynamically loaded).
63    * @see emulation
64    * @see display.Terminal
65    * @see display.TerminalHost
66    */
67   protected Terminal term;
68
69   /**
70    * The host address to connect to. This is retrieved from the PARAM tag
71    * "address".
72    */
73   protected String address;
74
75   /**
76    * The port number (default ist 23). This can be specified as the PARAM tag
77    * "port".
78    */
79   protected int port = 23;
80
81   /**
82    * The proxy ip address. If this variable is set telnet will try to connect
83    * to this address and then send a string to tell the relay where the
84    * target host is.
85    * @see address
86    */
87   protected String proxy = null;
88   /**
89    * The proxy port number. This is the port where the relay is expected to
90    * listen for incoming connections.
91    * @see proxy
92    * @see port
93    */
94   protected int proxyport;
95   
96   /**
97    * Emulation type (default is vt320). This can be specified as the PARAM
98    * tag "emulation".
99    * @see term
100    * @see display.Terminal
101    * @see display.TerminalHost
102    */
103   protected String emulation = "vt320";
104
105   /**
106    * Dynamically loaded modules are stored here.
107    */
108   protected Vector modules = null;
109
110   // some state variables;
111   private boolean localecho = true;
112   private boolean connected = false;
113
114   private Thread t;
115   private Container parent;
116
117   /**
118    * This Hashtable contains information retrievable by getParameter() in case
119    * the program is run as an application and the AppletStub is missing.
120    */
121   public Hashtable params;
122
123   /** 
124    * Retrieve the current version of the applet.
125    * @return String a string with the version information.
126    */
127   public String getAppletInfo()
128   {
129     String info = "The Java(tm) Telnet Applet\n$Id: telnet.java,v 1.19 1998/02/09 10:22:15 leo Exp $\n";
130     info += "Terminal emulation: "+term.getTerminalType()+
131       " ["+term.toString()+"]\n";
132     info += "Terminal IO version: "+tio.toString()+"\n";
133     if(modules != null && modules.size() > 0) {
134       info += "Resident modules loaded: ("+modules.size()+")";
135       for(int i = 0; i < modules.size(); i++)
136         info += "   + "+(modules.elementAt(i)).toString()+"\n";
137     }
138     
139     return info;
140   }
141     
142   /**
143    * Retrieve parameter tag information. This includes the tag information from
144    * terminal and loaded modules.
145    * @return String an array of array of string with tag information
146    * @see java.applet.Applet#getParameterInfo
147    */
148   public String[][] getParameterInfo()
149   {
150     String pinfo[][];
151     String info[][] = {
152       {"address",  "String",   "IP address"},
153       {"port",     "Integer",  "Port number"},
154       {"proxy",    "String",   "IP address of relay"},
155       {"proxyport","Integer",  "Port number of relay"},
156       {"emulation","String",   "Emulation to be used (standard is vt320)"},
157     };
158     String tinfo[][] = (term != null ? term.getParameterInfo() : null);
159     if(tinfo != null) pinfo = new String[tinfo.length + 3][3];
160     else pinfo = new String[3][3];
161     System.arraycopy(info, 0, pinfo, 0, 3);
162     System.arraycopy(tinfo, 0, pinfo, 3, tinfo.length);
163     return pinfo;
164   }
165
166   /**
167    * We override the Applet method getParameter() to be able to handle 
168    * parameters even as application.
169    * @param name The name of the queried parameter.
170    * @return the value of the parameter
171    * @see java.applet.Applet#getParameter
172    */ 
173   public String getParameter(String name)
174   {
175     if(params == null) return super.getParameter(name);
176     return (String)params.get(name);
177   }
178
179   /**
180    * The main function is called on startup of the application.
181    */
182   public static void main(String args[]) 
183   {
184     // an application has to create a new instance of itself.
185     telnet applet = new telnet();
186
187     // create params from command line arguments
188     applet.params = new Hashtable();
189     switch(args.length) 
190     {
191     case 2: applet.params.put("port", args[1]);
192     case 1: applet.params.put("address", args[0]); 
193       break;
194     default: 
195       System.out.println("Usage: java telnet host [port]");
196       System.exit(0);
197     } 
198     applet.params.put("VTscrollbar", "true");
199     applet.params.put("module#1", "ButtonBar");
200     applet.params.put("1#Button", "Exit|\\$exit()");
201     applet.params.put("2#Button", "Connect|\\$connect(\\@address@,\\@port@)");
202     applet.params.put("3#Input", "address#30|"
203           +(args.length > 0 ? args[0] : "localhost"));
204     applet.params.put("4#Input", "port#4|23");
205     applet.params.put("5#Button", "Disconnect|\\$disconnect()");
206
207     // we put the applet in its own frame
208     Frame frame = new Frame("The Java Telnet Application ["+args[0]+"]");
209     frame.setLayout(new BorderLayout());
210     frame.add("Center", applet);
211     frame.resize(380, 590);
212
213     applet.init();
214
215     frame.pack();
216     frame.show();
217
218     applet.start();
219   }
220   
221   /**
222    * Initialize applet. This method reads the PARAM tags "address",
223    * "port" and "emulation". The emulation class is loaded dynamically.
224    * It also loads modules given as parameter "module#<nr>".
225    */
226   public void init()
227   {
228     String tmp; 
229
230     // save the current parent for future use
231     parent = getParent();
232     
233     // get the address we want to connect to
234     address = getParameter("address");
235     
236     if((tmp = getParameter("port")) == null) 
237       port = 23;
238     else
239       port = Integer.parseInt(tmp);
240
241     if((proxy = getParameter("proxy")) != null)
242       if((tmp = getParameter("proxyport")) == null)
243         proxyport = 31415;
244       else
245         proxyport = Integer.parseInt(tmp);
246     
247     if((emulation = getParameter("emulation")) == null)
248       emulation = "vt320";
249
250     // load the terminal emulation
251     try {
252       term = (Terminal)Class.forName("display."+emulation).newInstance();
253       System.out.println("telnet: load terminal emulation: "+emulation);
254     } catch(Exception e) {
255       System.err.println("telnet: cannot load terminal emulation "+emulation);
256       e.printStackTrace();
257     }
258     setLayout(new BorderLayout());
259     
260     // load modules, position is determined by the @<position> modifier
261     modules = new Vector();
262     int nr = 1;
263     while((tmp = getParameter("module#"+nr++)) != null) try {
264       Panel north = null, south = null, west = null, east = null;
265       String position = "North", initFile = null;
266
267       // try to get the initialization file name
268       if(tmp.indexOf(',') != -1) {
269         initFile = tmp.substring(tmp.indexOf(','+1));
270         tmp = tmp.substring(0, tmp.indexOf(','));
271         initFile = tmp.substring(tmp.indexOf(','+1));
272       }
273            
274       // find the desired location
275       if(tmp.indexOf('@') != -1) {
276         position = tmp.substring(tmp.indexOf('@')+1);
277         tmp = tmp.substring(0, tmp.indexOf('@'));
278       } 
279       Object obj = (Object)Class.forName("modules."+tmp).newInstance();
280
281       // probe for module (implementing modules.Module)
282       try {
283         ((Module)obj).setLoader(this);
284         modules.addElement((Module)obj);
285         System.out.println("telnet: module "+tmp+" detected");
286       } catch(ClassCastException e) {
287         System.out.println("telnet: warning: "+tmp+" may not be a "+
288                            "valid module");
289       }
290
291       // probe for visible component (java.awt.Component and descendants)
292       try {
293         Component component = (Component)obj;
294         if(position.equals("North")) {
295           if(north == null) { north = new Panel(); add("North", north); }
296           north.add(component);
297         } else if(position.equals("South")) {
298           if(south == null) { south = new Panel(); add("South", south); }
299           south.add(component);
300         } else if(position.equals("East")) {
301           if(east == null) { east = new Panel(); add("East", east); }
302           east.add(component);
303         } else if(position.equals("West")) {
304           if(west == null) { west = new Panel(); add("West", west); } 
305           west.add(component);
306         }
307         System.err.println("telnet: module "+tmp+" is a visible component");
308       } catch(ClassCastException e) {}
309
310     } catch(Exception e) {
311       System.err.println("telnet: cannot load module "+tmp);
312       e.printStackTrace();
313     }
314     if(modules.isEmpty()) modules = null;
315     add("Center", term);
316   }
317
318   /**
319    * Upon start of the applet try to create a new connection.
320    */
321   public void start()
322   {
323     if(!connect(address, port) && params == null)
324       showStatus("telnet: connection to "+address+" "+port+" failed");
325   }
326
327   /**
328    * Disconnect when the applet is stopped.
329    */
330   public final void stop()
331   {
332     disconnect();
333   }
334
335   /**
336    * Try to read data from the sockets and put it on the terminal.
337    * This is done until the thread dies or an error occurs.
338    */
339   public void run()
340   {
341     while(t != null)
342       try {
343         String tmp = new String(tio.receive(), 0);
344
345         // cycle through the list of modules
346         if(modules != null) {
347           Enumeration modlist = modules.elements();
348           while(modlist.hasMoreElements()) {
349             Module m = (Module)modlist.nextElement();
350             String modified = m.receive(tmp);
351             // call the receive() method and if it returns null
352             // remove the module from the list
353             if(modified == null) modules.removeElement(m);
354             else tmp = modified;
355           }
356         }
357         // put the modified string to the terminal
358         term.putString(tmp);
359     } catch(IOException e) {
360       disconnect();
361     }
362   }
363
364   /**
365    * Connect to the specified host and port but don't break existing 
366    * connections. Connects to the host and port specified in the tags. 
367    * @return false if connection was unsuccessful
368    */
369   public boolean connect()
370   {
371     return connect(address, port);
372   }
373
374   /**
375    * Connect to the specified host and port but don't break existing 
376    * connections. Uses the port specified in the tags or 23.
377    * @param host destination host address
378    */
379   public boolean connect(String host)
380   {
381     return connect(host, port);
382   }
383   
384   /**
385    * Connect to the specified host and port but don't break existing 
386    * connections.
387    * @param host destination host address
388    * @param prt destination hosts port
389    */
390   public boolean connect(String host, int prt)
391   {
392     address = host; port = prt;
393
394     if(address == null || address.length() == 0) return false;
395     
396     // There should be no thread when we try to connect
397     if(t != null && connected) {
398       System.err.println("telnet: connect: existing connection preserved");
399       return false;
400     } else t = null;
401     
402     try {
403       // In any case try to disconnect if tio is still active
404       // if there was no tio create a new one.
405       if(tio != null) try { tio.disconnect(); } catch(IOException e) {}
406       else (tio = new TelnetIO()).setPeer(this);
407
408       term.putString("Trying "+address+(port==23?"":" "+port)+" ...\n\r");
409       try {
410         // connect to to our destination at the given port
411         if(proxy != null) {
412           tio.connect(proxy, proxyport);
413           String str = "relay "+address+" "+port+"\n";
414           byte[] bytes = new byte[str.length()];
415           str.getBytes(0, str.length(), bytes, 0);
416           tio.send(bytes);
417         } else 
418           tio.connect(address, port);
419         term.putString("Connected to "+address+".\n\r");
420         // initial conditions are connected and localecho
421         connected = true;
422         localecho = true;
423
424         // cycle through the list of modules and notify connection
425         if(modules != null) {
426           Enumeration modlist = modules.elements();
427           while(modlist.hasMoreElements())
428             // call the connect() method
429             ((Module)modlist.nextElement()).connect(address, port);
430         }
431       } catch(IOException e) {
432         term.putString("Failed to connect.\n\r");
433         // to be sure, we remove the TelnetIO instance
434         tio = null;
435         System.err.println("telnet: failed to connect to "+address+" "+port);
436         e.printStackTrace();
437         return false;
438       }
439       // if our connection was successful, create a new thread and start it
440       t = new Thread(this);
441       t.setPriority(Thread.MIN_PRIORITY);
442       t.start();
443     } catch(Exception e) {
444       // hmm, what happened?
445       System.err.println("telnet: an error occured:");
446       e.printStackTrace();
447       return false;
448     }
449     return true;
450   }
451
452   /**
453    * Disconnect from the remote host.
454    * @return false if there was a problem disconnecting.
455    */
456   public boolean disconnect()
457   {
458     if(tio == null) {
459       System.err.println("telnet: no connection");
460       return false;
461     }
462     try {
463       connected = false; t = null;
464       // cycle through the list of modules and notify connection
465       if(modules != null) {
466         Enumeration modlist = modules.elements();
467         while(modlist.hasMoreElements())
468           // call the disconnect() method
469           ((Module)modlist.nextElement()).disconnect();
470       }
471       term.putString("\n\rConnection closed.\n\r");
472       tio.disconnect();
473     } catch(Exception e) {
474       System.err.println("telnet: disconnection problem");
475       e.printStackTrace();
476       tio = null; t = null;
477       return false;
478     }
479     return true;
480   }
481   
482   /**
483    * Send a String to the remote host. Implements display.TerminalHost
484    * @param s String to be sent
485    * @return true if we are connected
486    * @see display.TerminalHost
487    */
488   public boolean send(String str)
489   {
490     if(connected) try {
491       byte[] bytes = new byte[str.length()];
492       str.getBytes(0, str.length(), bytes, 0);
493       tio.send(bytes);
494       if(localecho) {
495         if ((str.length()==2) && (str.charAt(0)=='\r') && (str.charAt(1)==0))
496           term.putString("\r\n");
497         else
498           term.putString(str);
499       }
500     } catch(Exception e) {
501       System.err.println("telnet.send(): disconnected");
502       disconnect();
503       return false;
504     }
505     else return false;
506     return true;
507   }
508
509   /**
510    * Send a String to the remote Host.
511    * @param str String to be sent
512    * @return true if we are connected
513    * @see modules.BSXModule
514    */
515   public boolean writeToSocket(String str)
516     {
517       if (connected) try {
518         byte[] bytes = new byte[str.length()];
519         str.getBytes(0, str.length(), bytes, 0);
520         tio.send(bytes);
521       } catch(Exception e) {
522         System.err.println("telnet.send(): disconnected");
523         disconnect();
524         return false;
525       }
526       else return false;
527       return true;
528     }
529   /**
530    * Send a String to the users terminal
531    * @param str String to be displayed
532    * @return void
533    * @see modules.BSXModule
534    */
535   public void writeToUser(String str)
536   {
537     if (term!=null)
538       term.putString(str);
539   }
540
541   /**
542    * This method is called when telnet needs to be notified of status changes.
543    * @param status Vector of status information.
544    * @return an object of the information requested.
545    * @see socket.StatusPeer
546    */
547   public Object notifyStatus(Vector status)
548   {
549     String what = (String)status.elementAt(0);
550     if(what.equals("NAWS"))
551       return term.getSize();
552     if(what.equals("TTYPE"))
553       if(term.getTerminalType() == null)
554         return emulation;
555       else return term.getTerminalType();
556     if(what.equals("LOCALECHO"))
557       localecho = true;
558     if(what.equals("NOLOCALECHO")) 
559       localecho = false;
560     return null;
561   }
562 }