View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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;   // The object representing the 
68                                         // connection to the client
69      private boolean versionNegotiated; // True when the version and message size
70                                         // has been negotiated (i.e. after exchange
71                                         // of Tversion/Rversion messages)
72      private long maxMessageSize;       // The maximum size of message that will
73                                         // be sent or received on this connection
74      private User user;               // The name of the remote user
75      private boolean authenticated;     // True if this is an authenticated connection
76      
77      // Need to keep track of all open fids and tags
78      private Hashtable fidsInUse;       // Maps fids to StyxFiles
79      private Vector tagsInUse;          // The tags that are currently awaiting
80                                         // reply on this connection
81      
82      private static final int EXECUTE = 0; // These constants are used by checkPermissions()
83      private static final int WRITE = 1;   // The values of these constants are meaningful
84      private static final int READ = 2;    // and should NOT be changed
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         // TODO: Clunk all outstanding fids
125         // TODO: Release all outstanding tags
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             // First remove any previous association with this fid
155             // (used legitimately when using a TwalkMessage where the newFid is
156             // the same as the old fid, or when a TcreateMessage arrives with
157             // a fid that is the same as an existing one)
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         // TODO: abort any outstanding i/o (reads or writes) associated with
212         // the previous message
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                 // Check to see if client has requested that this file is
264                 // deleted on clunk
265                 StyxFileClient sfc = sf.getClient(this.session, fid);
266                 // sfc could be null - the client might not have this file open
267                 if (sfc != null && sfc.deleteOnClunk())
268                 {
269                     try
270                     {
271                         sf.remove();
272                     }
273                     catch(StyxException se)
274                     {
275                         // if there was a problem removing the file, ignore it
276                         // (the particular StyxFile may not allow itself to be
277                         // removed)
278                     }
279                 }
280                 // Remove this client from the StyxFile. This fires the 
281                 // clientDisconnected event on the StyxFile.
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                     // ignore this, we are closing down anyway
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         // Check to see if the file is permanently an exclusive-use file
318         if (sf.isExclusive())
319         {
320             if (sf.getNumClients() != 0)
321             {
322                 throw new StyxException("can't open locked file");
323             }
324         }
325         // Mask off the last two bits; these contain the type of I/O (r, w, x)
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                 // Shouldn't happen
359                 throw new IllegalStateException("openMode = " + openMode + 
360                     ": should be between 0 and 3");
361         }
362         if ((mode & StyxUtils.OTRUNC) == StyxUtils.OTRUNC)
363         {
364             // Can't truncate a directory
365             if(sf.isDirectory())
366             {
367                 throw new StyxException("Cannot truncate a directory");
368             }
369             // Need to have write permission to truncate            
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             // Can't delete a directory on closing
378             if (sf.isDirectory())
379             {
380                 throw new StyxException("Cannot automatically delete a directory when fid is clunked");
381             }
382             // Need to have write permissions on the parent directory and the
383             // file itself to delete the file on clunking its fid
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             // TODO: do we need write permissions on the file itself?
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         // This is the cunning bit: we bit-shift the permissions value so that 
425         // the mode in question is represented by the last bit (all), the
426         // fourth-to-last bit (group) and the seventh-to-last bit (user).
427         // So if we started with a mode of 0755 (binary 111101101, rwxrwxrwx)
428         // and we want to check write permissions, we shift by one bit so that 
429         // the value of "perms" is 11110110, rwxrwxrw)
430         int perms = sf.getPermissions() >> mode;
431         // Check permissions for "all"; this is the last bit in the
432         // permissions number
433         if (perms % 2 == 1)
434         {
435             return true;
436         }
437         // Check group permissions
438         if ( (perms >> 3) % 2 == 1)
439         {
440             // The group has the right permissions; now we have to find if the user
441             // is a member of the group to which the file belongs
442             if (this.user.isMemberOf(sf.getGroup()))
443             {
444                 return true;
445             }
446         }
447         // Check owner permissions
448         if ( (perms >> 6) % 2 == 1)
449         {
450             // the owner has the right permissions; now we check that the user
451             // is the owner of the file
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         // A Twrite message has 23 bytes of header. We add a header byte "for luck",
467         // since this is what Inferno seems to do
468         return this.maxMessageSize - 24;
469     }
470     
471 }