1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package uk.ac.rdg.resc.jstyx.server;
30
31 import java.util.Hashtable;
32 import java.util.Vector;
33 import java.util.Enumeration;
34
35 import org.apache.mina.protocol.ProtocolSession;
36
37 import uk.ac.rdg.resc.jstyx.types.Qid;
38 import uk.ac.rdg.resc.jstyx.StyxUtils;
39 import uk.ac.rdg.resc.jstyx.StyxException;
40
41 /***
42 * Class containing information about the state of a particular Styx connection,
43 * from the point of view of the server. This is attached to the Session object.
44 *
45 * @author Jon Blower
46 * $Revision: 601 $
47 * $Date: 2006-03-20 17:51:50 +0000 (Mon, 20 Mar 2006) $
48 * $Log$
49 * Revision 1.4 2006/03/20 17:51:50 jonblower
50 * Adding authentication to base JStyx system
51 *
52 * Revision 1.3 2005/03/11 14:02:16 jonblower
53 * Merged MINA-Test_20059309 into main line of development
54 *
55 * Revision 1.2.2.1 2005/03/09 19:44:18 jonblower
56 * Changes concerned with migration to MINA
57 *
58 * Revision 1.2 2005/02/24 17:52:32 jonblower
59 * Constructor for StyxSessionState no longer throws StyxException
60 *
61 * Revision 1.1.1.1 2005/02/16 18:58:35 jonblower
62 * Initial import
63 *
64 */
65 class StyxSessionState
66 {
67 private ProtocolSession session;
68
69 private boolean versionNegotiated;
70
71
72 private long maxMessageSize;
73
74 private User user;
75 private boolean authenticated;
76
77
78 private Hashtable fidsInUse;
79 private Vector tagsInUse;
80
81
82 private static final int EXECUTE = 0;
83 private static final int WRITE = 1;
84 private static final int READ = 2;
85
86 /*** Creates a new instance of StyxSessionState */
87 public StyxSessionState(ProtocolSession session)
88 {
89 this.versionNegotiated = false;
90 this.maxMessageSize = 0;
91 this.user = null;
92 this.fidsInUse = new Hashtable();
93 this.tagsInUse = new Vector();
94 this.authenticated = false;
95 this.session = session;
96 }
97
98 /***
99 * @return true if the protocol version has been negotiated. If this returns
100 * false then the protocol version must be negotiated with a Tversion/Rversion
101 * exchange before any other messages can be processed.
102 */
103 public boolean isVersionNegotiated()
104 {
105 return this.versionNegotiated;
106 }
107
108 /***
109 * @return the maximum size of message that is permitted on this connection.
110 * Note that the max message size can only be set by resetting the session
111 * in response to a TversionMessage (see resetSession())
112 */
113 public long getMaxMessageSize()
114 {
115 return this.maxMessageSize;
116 }
117
118 /***
119 * Aborts all outstanding i/o on this connection (called after receiving
120 * a TversionMessage)
121 */
122 public synchronized void resetSession(long maxMessageSize)
123 {
124
125
126 this.versionNegotiated = true;
127 this.maxMessageSize = maxMessageSize;
128 }
129
130 /***
131 * @return the remote user
132 */
133 public User getUser()
134 {
135 return user;
136 }
137
138 /***
139 * Sets the remote user (in response to an RattachMessage)
140 */
141 public void setUser(User user)
142 {
143 this.user = user;
144 }
145
146 /***
147 * Associates a fid with a file. You must check that the fid is not in
148 * use before calling this or the old fid will be forgotten about.
149 */
150 public void associate(long fid, StyxFile file)
151 {
152 synchronized(this.fidsInUse)
153 {
154
155
156
157
158 this.fidsInUse.remove(new Long(fid));
159 this.fidsInUse.put(new Long(fid), file);
160 }
161 }
162
163 /***
164 * @return true if the given fid is already in use
165 */
166 public boolean fidInUse(long fid)
167 {
168 return this.fidsInUse.containsKey(new Long(fid));
169 }
170
171 /***
172 * @return true if the given tag is already in use
173 */
174 public boolean tagInUse(int tag)
175 {
176 return this.tagsInUse.contains(new Integer(tag));
177 }
178
179 /***
180 * Adds the given tag to the list of tags in use, first checking to see if
181 * it is already in use (i.e. no need to call tagInUse() before calling
182 * this)
183 * @throws TagInUseException if the given tag is already in use
184 */
185 public synchronized void addTag(int tag) throws TagInUseException
186 {
187 synchronized (this.tagsInUse)
188 {
189 if (this.tagInUse(tag))
190 {
191 throw new TagInUseException(tag);
192 }
193 this.tagsInUse.add(new Integer(tag));
194 }
195 }
196
197 /***
198 * Called when a message is replied to, releasing the tag. TODO: should we
199 * throw an exception if the tag does not exist?
200 */
201 public void releaseTag(int tag)
202 {
203 this.tagsInUse.remove(new Integer(tag));
204 }
205
206 /***
207 * Called in response to a Tflush message to abort a previous message
208 */
209 public void flushTag(int tag)
210 {
211
212
213 this.releaseTag(tag);
214 }
215
216 /***
217 * Flushes all tags open in this session (called when a client disconnects)
218 */
219 public void flushAll()
220 {
221 synchronized (this.tagsInUse)
222 {
223 Enumeration en = this.tagsInUse.elements();
224 while(en.hasMoreElements())
225 {
226 int tag = ((Integer)en.nextElement()).intValue();
227 this.flushTag(tag);
228 }
229 }
230 }
231
232 /***
233 * @return The StyxFile that's associated with the given fid
234 * @throws FidNotFoundException if there is no StyxFile associated with the
235 * given fid
236 */
237 public StyxFile getStyxFile(long fid) throws FidNotFoundException
238 {
239 StyxFile sf = (StyxFile)this.fidsInUse.get(new Long(fid));
240 if (sf == null)
241 {
242 throw new FidNotFoundException(fid);
243 }
244 return sf;
245 }
246
247 /***
248 * Forgets about the file represented by the fid (i.e. closes it)
249 * @throws FidNotFoundException if there is no StyxFile associated with
250 * this fid
251 */
252 public void clunk(long fid) throws FidNotFoundException
253 {
254 synchronized(this.fidsInUse)
255 {
256 if (!fidInUse(fid))
257 {
258 throw new FidNotFoundException(fid);
259 }
260 StyxFile sf = this.getStyxFile(fid);
261 synchronized(sf)
262 {
263
264
265 StyxFileClient sfc = sf.getClient(this.session, fid);
266
267 if (sfc != null && sfc.deleteOnClunk())
268 {
269 try
270 {
271 sf.remove();
272 }
273 catch(StyxException se)
274 {
275
276
277
278 }
279 }
280
281
282 sf.removeClient(sfc);
283 }
284 this.fidsInUse.remove(new Long(fid));
285 }
286 }
287
288 /***
289 * Clunks all fids open in this session (called when a client disconnects)
290 */
291 public void clunkAll()
292 {
293 synchronized(this.fidsInUse)
294 {
295 Enumeration en = this.fidsInUse.keys();
296 while (en.hasMoreElements())
297 {
298 try
299 {
300 long fid = ((Long)en.nextElement()).longValue();
301 this.clunk(fid);
302 }
303 catch (FidNotFoundException fnfe)
304 {
305
306 }
307 }
308 }
309 }
310
311 /***
312 * Checks that the given file can be opened with the given mode
313 * @throws StyxException if not successful
314 */
315 public void checkOpen(StyxFile sf, int mode) throws StyxException
316 {
317
318 if (sf.isExclusive())
319 {
320 if (sf.getNumClients() != 0)
321 {
322 throw new StyxException("can't open locked file");
323 }
324 }
325
326 int openMode = mode & 3;
327 switch (openMode)
328 {
329 case StyxUtils.OREAD:
330 if (!checkPermissions(sf, this.READ))
331 {
332 throw new StyxException("read permission denied");
333 }
334 break;
335 case StyxUtils.OWRITE:
336 if (!checkPermissions(sf, this.WRITE))
337 {
338 throw new StyxException("write permission denied");
339 }
340 break;
341 case StyxUtils.ORDWR:
342 if (!checkPermissions(sf, this.READ))
343 {
344 throw new StyxException("read permission denied");
345 }
346 if (!checkPermissions(sf, this.WRITE))
347 {
348 throw new StyxException("write permission denied");
349 }
350 break;
351 case StyxUtils.OEXEC:
352 if (!checkPermissions(sf, this.EXECUTE))
353 {
354 throw new StyxException("execute permission denied");
355 }
356 break;
357 default:
358
359 throw new IllegalStateException("openMode = " + openMode +
360 ": should be between 0 and 3");
361 }
362 if ((mode & StyxUtils.OTRUNC) == StyxUtils.OTRUNC)
363 {
364
365 if(sf.isDirectory())
366 {
367 throw new StyxException("Cannot truncate a directory");
368 }
369
370 if (!checkPermissions(sf, this.WRITE))
371 {
372 throw new StyxException("need write permissions to truncate a file");
373 }
374 }
375 if ((mode & StyxUtils.ORCLOSE) == StyxUtils.ORCLOSE)
376 {
377
378 if (sf.isDirectory())
379 {
380 throw new StyxException("Cannot automatically delete a directory when fid is clunked");
381 }
382
383
384 if (!checkPermissions(sf.getParent(), this.WRITE))
385 {
386 throw new StyxException("need write permissions on the parent "
387 + "directory to delete the file when fid is clunked");
388 }
389
390 }
391 return;
392 }
393
394 /***
395 * Checks to see if the current user has permission to write in the given
396 * file or directory
397 */
398 public boolean checkWrite(StyxFile sf)
399 {
400 return this.checkPermissions(sf, this.WRITE);
401 }
402
403 /***
404 * Checks to see if the current user has permissions to execute the given
405 * file (or, in the case of a directory, to open it)
406 * @todo Call this "CheckEnter" and make sure that sf is a directory?
407 */
408 public boolean checkExecute(StyxFile sf)
409 {
410 return this.checkPermissions(sf, this.EXECUTE);
411 }
412
413 /***
414 * Checks the permissions for a given mode
415 * @param perms the file permissions (e.g. 0755)
416 * @param mode the mode to check (EXECUTE, WRITE or READ)
417 */
418 private boolean checkPermissions(StyxFile sf, int mode)
419 {
420 if (mode < 0 || mode > 2)
421 {
422 throw new IllegalArgumentException("Internal error: mode should be 0, 1 or 2");
423 }
424
425
426
427
428
429
430 int perms = sf.getPermissions() >> mode;
431
432
433 if (perms % 2 == 1)
434 {
435 return true;
436 }
437
438 if ( (perms >> 3) % 2 == 1)
439 {
440
441
442 if (this.user.isMemberOf(sf.getGroup()))
443 {
444 return true;
445 }
446 }
447
448 if ( (perms >> 6) % 2 == 1)
449 {
450
451
452 if (this.user.getUsername().equals(sf.getOwner()))
453 {
454 return true;
455 }
456 }
457 return false;
458 }
459
460 /***
461 * Gets the maximum amount of data (not including the header) that can be
462 * sent in a Styx Message
463 */
464 public long getIOUnit()
465 {
466
467
468 return this.maxMessageSize - 24;
469 }
470
471 }