Source code: com/synaptics/elvis/Elvis.java
1 /*
2 * The contents of this file are subject to the Mozilla Public License Version
3 * 1.1 (the "License"); you may not use this file except in compliance with the
4 * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
5 *
6 * Software distributed under the License is distributed on an "AS IS" basis,
7 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
8 * the specific language governing rights and limitations under the License.
9 *
10 * The Original Code is com.synaptics.elvis code.
11 *
12 * The Initial Developers of the Original Code are Synaptics, Inc. and Christopher Heiny.
13 * Portions created by Synaptics, Inc. and Christopher Heiny are
14 * Copyright (C) 2002 Synaptics, Inc. and Christopher Heiny. All Rights Reserved.
15 *
16 * Contributor(s):
17 * Christopher Heiny <cheiny@synaptics.com>
18 */
19
20 package com.synaptics.elvis;
21
22 import java.beans.*;
23
24 /** Elvis is a package for implementing a singleton, not only within a JVM, but for a given
25 * user on a particular machine.
26 * <P><B>Principles of operation.</b> The model of use for Elvis is simple.
27 * An Elvis-enabled program calls <CODE>Elvis.get()</code>
28 * with a program name and a command handler. This name is used to (hopefully) uniquely
29 * identify the Elvis-enabled program and its functions. If Elvis can establish that he
30 * is indeed the only running instance of that particular program (for that user, on that machine),
31 * he will become the <B>One True Elvis</b>, and establish a thread that will listen for requests
32 * from other Elvis's (also known as Elvis impersonators or Elvis clients).
33 * <P>Once the call to <CODE>Elvis.get()</code> returns, the program can call <CODE>isTheOneTrueElvis()</code>
34 * to determine if their
35 * Elvis is the One True Elvis. If it isn't they probably just want to send the other Elvis
36 * some messages indicating what it wants to do, and then leave the building (exit gracefully).
37 *<P><B><FONT COLOR="red">KNOWN SECURITY HOLES</font></b> The current version of Elvis is
38 * most emphatically <B>not</b> secure. The most prominent holes are:
39 * <UL>
40 * <LI>On multi-user systems (such as Linux/Unix), the Elvis information file
41 * may be created readable by users other than the creator. An attacker to could read the Elvis
42 * information file to determine the port, key, and user. This information could
43 * be used to:
44 * <UL>
45 * <LI>Submit bogus requests to the One True Elvis;
46 * <LI>Spoof responses from the One True Elvis.
47 * <LI>If the attacker could determine that the One True Elvis is not
48 * running, he could use this information to set up his own fraudlent Elvis.
49 * </ul>
50 * <LI>On multi-user systems (such as Linux/Unix), the Elvis information file
51 * may be created writable by users other than the creator. An attacker to could
52 * substitute his own information in the file, and redirect requests to his own
53 * fraudulent Elvis.
54 * <LI>In almost all cases, things that are symptomatic of an attack or probe
55 * of Elvis are simply logged, and no further actions are taken.
56 * </ul>
57 * <P>
58 * Right now, my use of Elvis is in a trusted environment, where these situations are
59 * considered very unlikely to occur. Although updates are planned for upcoming
60 * releases of Elvis, if you are planning to use this release of Elvis in a less secure
61 * environment, you should seriously consider beefing up Elvis's security.
62 *
63 * @author cheiny
64 * @version $Id: Elvis.java,v 1.1 2002/05/09 07:17:17 clheiny Exp $
65 */
66 public class Elvis extends Object implements java.io.Serializable {
67
68 private PropertyChangeSupport propertySupport;
69
70 /** A flag indicating whether we really are the King or not. */
71 private boolean theOneTrueElvis;
72
73 /** The port associated with this instance of Elvis */
74 private int port;
75
76 /** This is the name of the Elvis program associated with this instance of Elvis */
77 private String program;
78
79 /** Indicates whether this Elvis is active or not. If Elvis has left the building,
80 * the Elvis listen thread has been cancelled, and most method invocations will throw
81 * an ElvisException.
82 */
83 private boolean inTheBuilding = true;
84
85 /** The request handler, provided by the Elvis application */
86 private ElvisHandler handler;
87
88 /** The listener handles most of Elvis's communication. */
89 private ElvisComm listener = null;
90
91 /** A handy dandy feedback string that the Elvis application can consult. */
92 private String feedback;
93
94 /** Utility field used by bound properties. */
95 private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this);
96
97 /** Creates a new Elvis. This is private to force folks to use the factory method. We
98 * want them to use the factory, so we can catch attempts to create multiple Elvises on
99 * the same port.
100 * <P>We presume the parameters have already been vetted by the factory, and don't check them here. */
101 private Elvis ( String program, int port, ElvisHandler handler, PropertyChangeListener propListener ) {
102 setProgram ( program );
103 setPort ( port );
104 setHandlerInternal ( handler );
105 propertySupport = new PropertyChangeSupport ( this );
106 if ( propListener != null ) this.addPropertyChangeListener ( propListener );
107
108 try {
109 listener = new ElvisComm ( program, port, handler );
110 listener.addPropertyChangeListener ( new StatusListener (listener) );
111 listener.start();
112 } catch ( Exception e ) {
113 System.err.println ( "Failed to acquire an ElvisComm - " + e.getMessage() );
114 e.printStackTrace();
115 }
116
117 if ( listener == null ) {
118 theOneTrueElvis = false;
119 }
120 else if ( listener.getStatus() == ElvisCommStatus.NOT_THE_KING ) {
121 theOneTrueElvis = false;
122 }
123 else {
124 theOneTrueElvis = true;
125 }
126 }
127
128
129 /** This is used to monitor the status of the ElvisComm, and adjust the feedback string
130 * as necessary (among other possible, not yet implemented, actions.
131 */
132 private class StatusListener implements PropertyChangeListener {
133 private ElvisComm listener = null;
134 public StatusListener ( ElvisComm listener ) {
135 this.listener = listener;
136 }
137
138 public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) {
139 System.out.println ( "Elvis listener reports: " + listener.getStatus() );
140 setFeedback ( listener.getStatus().toString() );
141 }
142
143 }
144
145 /** Get an instance of the specified Elvis program on the specified port. The
146 * program name may not contain the <CODE>':'</code> or end of line characters.
147 * <P>If we are indeed the one true Elvis, the specified ElvisHandler will
148 * be used to handle requests.
149 * @param program The program name string.
150 * @param handler The handler.
151 * @return Elvis, or someone who looks just like him.
152 * @throws IllegalArgumentException if you submit an invalid argument (duh).
153 */
154 public static final Elvis get ( String program, int port, ElvisHandler handler ) {
155 if ( (program == null) || ( program.length() == 0) ) throw new IllegalArgumentException ( "invalid program passed to Elvis.get" );
156 if ( handler == null ) throw new IllegalArgumentException ( "null handler passed to Elvis.get" );
157 return new Elvis( program, port, handler, null );
158 }
159
160 /** Get an instance of the specified Elvis program on the specified port. The
161 * program name may not contain the <CODE>':'</code> or end of line characters.
162 * <P>If we are indeed the one true Elvis, the specified ElvisHandler will
163 * be used to handle requests.
164 * @param program The program name string.
165 * @param handler The handler.
166 * @return Elvis, or someone who looks just like him.
167 * @throws IllegalArgumentException if you submit an invalid argument (duh).
168 */
169 public static final Elvis get ( String program, int port, ElvisHandler handler, PropertyChangeListener propListener ) {
170 if ( (program == null) || ( program.length() == 0) ) throw new IllegalArgumentException ( "invalid program passed to Elvis.get" );
171 if ( program.indexOf(ElvisMessage.SEPARATOR) != -1 ) throw new IllegalArgumentException ( "invalid program name (contains separator character "
172 + ElvisMessage.SEPARATOR + ")" );
173 if ( handler == null ) throw new IllegalArgumentException ( "null handler passed to Elvis.get" );
174 return new Elvis( program, port, handler, propListener );
175 }
176
177 /** <CODE>theOneTrueElvis</CODE> indicates whether this Elvis is The King (<CODE>true</code>)
178 * or just an impersonator (<CODE>false</code>).
179 * <P>If we are indeed the One True Elvis,
180 * then we will be handling all Elvis based interactions for all JVMs. The client must
181 * be ready to deal with this.
182 * @return Value of property theOneTrueElvis.
183 * @throws ElvisException if Elvis has left the building.
184 */
185 public boolean isTheOneTrueElvis() {
186 if ( this.hasLeftTheBuilding() ) {
187 throw new ElvisException ( "Elvis has left the building" );
188 }
189 return theOneTrueElvis;
190 }
191
192 /** We can only set this ourselves. This will prevent someone from deciding they want to
193 * impersonate Elvis.
194 * @param theOneTrueElvis Boolean indicating that we are the king. Or not.
195 */
196 private void setTheOneTrueElvis(boolean theOneTrueElvis) {
197 this.theOneTrueElvis = theOneTrueElvis;
198 }
199
200 /** Get the port number that this Elvis is (or should be) listening on.
201 * @return The port we're listening to.
202 */
203 public int getPort() {
204 return listener.getPort();
205 }
206
207 /** The program name is the unique identifier for this Elvis's functionality.
208 * It is unique (hopefully) across all Elvis-capable programs in all JVMs.
209 * <P>This
210 * uniqueness is actually subject to the discipline exercised by the various
211 * Elvis client programmers. There is nothing in the Elvis implementation that
212 * prevents two different programmers from attempting to implement two very
213 * different Elvis programs using the name <CODE>Splat</code>.
214 * The best advice I can offer is <I>Don't
215 * do this!</i> Instead, try using the fully qualified (package name and class
216 * name) for the class implementing the Elvis-based functionality.
217 * @return A String - the program name.
218 */
219 public String getProgram() {
220 return program;
221 }
222
223 /** setPort should only be called at class instantiation time.
224 * @param port The port to listen on.
225 */
226 private void setPort(int port) {
227 this.port = port;
228 }
229
230 /** The program name can only be set internally, preferably at creation time.
231 * @param program The Elvis program name.
232 * @see #getProgram() getProgram()
233 */
234 private void setProgram(String program) {
235 this.program = program;
236 }
237
238 /** Calling <CODE>leaveTheBuilding()</code> will result in the Elvis listener thread
239 * being cancelled (if this Elvis is the one true Elvis) and most Elvis related infrastructure
240 * being dismantled. Once Elvis has left the building, most method calls will through
241 * an ElvisException.
242 */
243 public void leaveTheBuilding() {
244 inTheBuilding = false;
245 }
246
247 /** Returns <CODE>true</code> if Elvis has left the building; <CODE>false</code> if he
248 * is still around and ready to perform.
249 */
250 public boolean hasLeftTheBuilding() {
251 return !inTheBuilding;
252 }
253
254 /** Returns the handler associated with this Elvis. This will be <CODE>null</code> if
255 * we are not the One True Elvis.
256 * @return The ElvisHandler being used by this Elvis, or <CODE>null</code>.
257 */
258 public ElvisHandler getHandler() {
259 return handler;
260 }
261
262 /** Set the Handler for this Elvis.
263 * @param handler New value of property handler.
264 * @throws ElvisException if this is not the One True Elvis, or if Elvis has left the building.
265 */
266 public void setHandler(ElvisHandler handler) {
267 if ( !this.isTheOneTrueElvis() ) {
268 throw new ElvisException ( "We are not the One True Elvis" );
269 }
270 if ( this.hasLeftTheBuilding() ) {
271 throw new ElvisException ( "Elvis has left the building" );
272 }
273 setHandlerInternal ( handler );
274 }
275
276 /** Internal worker that actually does the handler setting.
277 */
278 private void setHandlerInternal ( ElvisHandler handler ) {
279 this.handler = handler;
280 }
281
282 /** Add a PropertyChangeListener to the listener list.
283 * @param l The listener to add.
284 */
285 public void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
286 propertyChangeSupport.addPropertyChangeListener(l);
287 }
288
289 /** Removes a PropertyChangeListener from the listener list.
290 * @param l The listener to remove.
291 */
292 public void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
293 propertyChangeSupport.removePropertyChangeListener(l);
294 }
295
296 /** The feedback property provides information about the state and activities of Elvis. For
297 * example, various stages of establishing the ports we listen to are reported.
298 *<P>
299 * This is <B>not</b> intended to be a definitive means of determining the
300 * status of Elvis. It's more intended to give useful text information that can be
301 * presented in status bars and stuff like that.
302 * @return A string with information of varying interest.
303 */
304 public synchronized String getFeedback() {
305 return feedback;
306 }
307
308 /** Update the feedback string and notify any listeners.
309 * @param feedback New info of interest.
310 */
311 private synchronized void setFeedback(String feedback) {
312 String oldFeedback = this.feedback;
313 this.feedback = feedback;
314 propertyChangeSupport.firePropertyChange("feedback", oldFeedback, feedback);
315 }
316
317 /** Calling this will cause Elvis to do something. If we are the one true Elvis, the local
318 * ElvisHandler will be called with the specified data. If we are not the one true Elvis, the
319 * remote Elvis will be requested to perform and passed the specified data.
320 * @param data A String for Elvis to operate on (if needed, may be null or empty).
321 * @returns An ElvisResult indicating the success or failure of the performance, and including
322 * any relevant data.
323 */
324 public ElvisResult perform ( java.lang.String data ) {
325 if ( isTheOneTrueElvis() ) {
326 return handler.perform ( data );
327 }
328 else {
329 return listener.perform ( data );
330 }
331 }
332
333 }