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 org.apache.mina.common.IdleStatus;
32 import org.apache.mina.protocol.ProtocolHandler;
33 import org.apache.mina.protocol.ProtocolSession;
34
35 import org.apache.log4j.Logger;
36
37 import uk.ac.rdg.resc.jstyx.StyxUtils;
38 import uk.ac.rdg.resc.jstyx.StyxException;
39
40 import uk.ac.rdg.resc.jstyx.types.DirEntry;
41 import uk.ac.rdg.resc.jstyx.types.Qid;
42
43 import uk.ac.rdg.resc.jstyx.messages.*;
44
45 /***
46 * ProtocolHandler listener for a StyxServer (replaces StyxServerSessionListener
47 * from Netty).
48 * @todo Should these methods be more thread-safe, i.e. should we make sure that
49 * each method that affects a StyxFile is synchronized on that StyxFile?
50 *
51 * @author Jon Blower
52 * $Revision: 604 $
53 * $Date: 2006-03-21 14:58:42 +0000 (Tue, 21 Mar 2006) $
54 * $Log$
55 * Revision 1.16 2006/03/21 14:58:42 jonblower
56 * Implemented clear-text password-based authentication and did some simple tests
57 *
58 * Revision 1.15 2006/03/21 09:06:15 jonblower
59 * Still implementing authentication
60 *
61 * Revision 1.14 2006/03/20 17:51:50 jonblower
62 * Adding authentication to base JStyx system
63 *
64 * Revision 1.13 2005/12/01 08:21:56 jonblower
65 * Fixed javadoc comments
66 *
67 * Revision 1.12 2005/11/03 17:09:27 jonblower
68 * Created more efficient RreadMessage that involves less copying of buffers (still reliable)
69 *
70 * Revision 1.11 2005/09/08 07:08:59 jonblower
71 * Removed "String user" from list of parameters to StyxFile.write()
72 *
73 * Revision 1.10 2005/05/19 14:46:51 jonblower
74 * Changed behaviour of StyxDirectory.createChild(): no longer adds file to namespace in this method
75 *
76 * Revision 1.9 2005/05/10 19:19:44 jonblower
77 * Added call to StyxMessage.dispose() in messageSent() callback
78 *
79 * Revision 1.8 2005/05/05 16:57:38 jonblower
80 * Updated MINA library to revision 168337 and changed code accordingly
81 *
82 * Revision 1.7 2005/04/28 08:11:15 jonblower
83 * Modified permissions handling in documentation directory of SGS
84 *
85 * Revision 1.6 2005/03/19 21:47:02 jonblower
86 * Further fixes relating to releasing ByteBuffers
87 *
88 * Revision 1.5 2005/03/16 22:16:43 jonblower
89 * Added Styx Grid Service classes to core module
90 *
91 * Revision 1.4 2005/03/16 17:56:24 jonblower
92 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
93 *
94 * Revision 1.3 2005/03/15 15:51:41 jonblower
95 * Removed hard limit on maximum message size
96 *
97 * Revision 1.2 2005/03/11 14:02:16 jonblower
98 * Merged MINA-Test_20059309 into main line of development
99 *
100 * Revision 1.1.2.4 2005/03/11 08:30:30 jonblower
101 * Moved to log4j logging system (from apache commons logging)
102 *
103 * Revision 1.1.2.3 2005/03/10 20:55:40 jonblower
104 * Removed references to Netty
105 *
106 * Revision 1.1.2.2 2005/03/10 11:55:00 jonblower
107 * Replaced StyxFile with StyxDirectory for root of server
108 *
109 * Revision 1.1.2.1 2005/03/10 08:10:41 jonblower
110 * Initial import
111 *
112 */
113 public class StyxServerProtocolHandler implements ProtocolHandler
114 {
115 private static final Logger log = Logger.getLogger(StyxServerProtocolHandler.class);
116
117 private StyxDirectory root;
118 private StyxSecurityContext securityContext;
119
120 public StyxServerProtocolHandler(StyxDirectory fileTreeRoot,
121 StyxSecurityContext securityContext)
122 {
123 this.root = fileTreeRoot;
124 this.securityContext = securityContext;
125 }
126
127 /***
128 * Invoked when the session is created. Initialize default socket
129 * parameters and user-defined attributes here.
130 */
131 public void sessionCreated( ProtocolSession session ) throws Exception
132 {
133 log.info( session.getRemoteAddress() + " CREATED" );
134 }
135
136 public void sessionOpened(ProtocolSession session )
137 {
138 log.info( session.getRemoteAddress() + " OPENED" );
139 session.setAttachment(new StyxSessionState(session));
140 }
141
142 public void sessionClosed(ProtocolSession session )
143 {
144 StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
145 sessionState.clunkAll();
146 sessionState.flushAll();
147 log.info( session.getRemoteAddress() + " CLOSED" );
148 }
149
150 public void messageReceived(ProtocolSession session, Object message )
151 {
152 if (log.isDebugEnabled())
153 {
154 log.debug( session.getRemoteAddress() + " RCVD: " + message );
155 }
156
157
158 StyxMessage styxMessage = (StyxMessage)message;
159
160
161 int tag = styxMessage.getTag();
162
163
164 StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
165
166 try
167 {
168
169 sessionState.addTag(tag);
170
171 if (message instanceof TversionMessage)
172 {
173 replyVersion(session, sessionState, (TversionMessage)message, tag);
174 }
175 else if (message instanceof TauthMessage)
176 {
177 replyAuth(session, sessionState, (TauthMessage)message, tag);
178 }
179 else if (message instanceof TattachMessage)
180 {
181 replyAttach(session, sessionState, (TattachMessage)message, tag);
182 }
183 else if (message instanceof TflushMessage)
184 {
185 replyFlush(session, sessionState, (TflushMessage)message, tag);
186 }
187 else if (message instanceof TwalkMessage)
188 {
189 replyWalk(session, sessionState, (TwalkMessage)message, tag);
190 }
191 else if (message instanceof TopenMessage)
192 {
193 replyOpen(session, sessionState, (TopenMessage)message, tag);
194 }
195 else if (message instanceof TcreateMessage)
196 {
197 replyCreate(session, sessionState, (TcreateMessage)message, tag);
198 }
199 else if (message instanceof TreadMessage)
200 {
201 replyRead(session, sessionState, (TreadMessage)message, tag);
202 }
203 else if (message instanceof TwriteMessage)
204 {
205 replyWrite(session, sessionState, (TwriteMessage)message, tag);
206 }
207 else if (message instanceof TclunkMessage)
208 {
209 replyClunk(session, sessionState, (TclunkMessage)message, tag);
210 }
211 else if (message instanceof TremoveMessage)
212 {
213 replyRemove(session, sessionState, (TremoveMessage)message, tag);
214 }
215 else if (message instanceof TstatMessage)
216 {
217 replyStat(session, sessionState, (TstatMessage)message, tag);
218 }
219 else if (message instanceof TwstatMessage)
220 {
221 replyWstat(session, sessionState, (TwstatMessage)message, tag);
222 }
223 else
224 {
225 throw new StyxException("Not a valid Styx Tmessage");
226 }
227 }
228 catch(TagInUseException tiue)
229 {
230
231
232 log.error("tag " + tiue.getTag() + " already in use");
233 }
234 catch(FidNotFoundException fnfe)
235 {
236 reply(session, new RerrorMessage("fid " + fnfe.getFid() +
237 " not recognised"), tag);
238 }
239 catch(StyxException se)
240 {
241 reply(session, new RerrorMessage(se.getMessage()), tag);
242 }
243 }
244
245 private void replyVersion(ProtocolSession session, StyxSessionState sessionState,
246 TversionMessage tVerMsg, int tag) throws StyxException
247 {
248 long proposedMessageSize = tVerMsg.getMaxMessageSize();
249 long finalMessageSize = Math.min(proposedMessageSize, StyxUtils.MAX_MESSAGE_SIZE);
250 if (!tVerMsg.getVersion().equals("9P2000"))
251 {
252 throw new StyxException("Error negotiating protocol version (must be 9P2000)");
253 }
254
255
256 sessionState.resetSession(finalMessageSize);
257 reply(session, new RversionMessage(finalMessageSize, "9P2000"), tag);
258 }
259
260 private void replyAuth(ProtocolSession session, StyxSessionState sessionState,
261 TauthMessage tAuthMsg, int tag) throws StyxException
262 {
263
264
265
266 if (this.securityContext.supportsAuthentication())
267 {
268
269 if (sessionState.fidInUse(tAuthMsg.getAfid()))
270 {
271 throw new StyxException("Fid already in use");
272 }
273
274
275
276 AuthFile authFile = new AuthFile(this.securityContext, tAuthMsg.getUName());
277 sessionState.setUser(authFile.getUser());
278
279 sessionState.associate(tAuthMsg.getAfid(), authFile);
280
281 reply(session, new RauthMessage(authFile.getQid()), tag);
282 }
283 else
284 {
285
286 throw new StyxException("Authentication not supported");
287 }
288 }
289
290 private void replyAttach(ProtocolSession session, StyxSessionState sessionState,
291 TattachMessage tAttMsg, int tag) throws StyxException
292 {
293 if (!sessionState.isVersionNegotiated())
294 {
295 throw new StyxException("Tversion not seen");
296 }
297 if (tAttMsg.getAfid() == StyxUtils.NOFID)
298 {
299
300 if (this.securityContext.allowsAnonymousLogin())
301 {
302 sessionState.setUser(this.securityContext.getAnonymousUser());
303 }
304 else
305 {
306 throw new StyxException("Server does not allow anonymous logins");
307 }
308 }
309 else
310 {
311
312 if (this.securityContext.supportsAuthentication())
313 {
314
315
316 AuthFile authFile = (AuthFile)sessionState.getStyxFile(tAttMsg.getAfid());
317
318 if (!authFile.isAuthenticated(tAttMsg.getUname()))
319 {
320 throw new StyxException("User has not authenticated");
321 }
322 }
323 else
324 {
325 throw new StyxException("Authentication not supported");
326 }
327 }
328
329 if (sessionState.fidInUse(tAttMsg.getFid()))
330 {
331 throw new StyxException("Fid already in use");
332 }
333
334 sessionState.associate(tAttMsg.getFid(), this.root);
335
336 this.root.setLastAccessTime(StyxUtils.now());
337 reply(session, new RattachMessage(this.root.getQid()), tag);
338 }
339
340 private void replyFlush(ProtocolSession session, StyxSessionState sessionState,
341 TflushMessage tFlushMsg, int tag) throws StyxException
342 {
343 sessionState.flushTag(tFlushMsg.getOldTag());
344 reply(session, new RflushMessage(), tag);
345 }
346
347 private void replyWalk(ProtocolSession session, StyxSessionState sessionState,
348 TwalkMessage tWalkMsg, int tag) throws StyxException
349 {
350 if (tWalkMsg.getNumPathElements() > StyxUtils.MAXPATHELEMENTS)
351 {
352 throw new StyxException("Too many path elements in Twalk message");
353 }
354 long fid = tWalkMsg.getFid();
355
356
357 StyxFile sf = sessionState.getStyxFile(fid);
358 StyxFileClient client = (StyxFileClient)sf.getClient(session, fid);
359 if (client != null)
360 {
361 throw new StyxException("cannot walk an open fid");
362 }
363
364 long newFid = tWalkMsg.getNewFid();
365 String[] pathEls = tWalkMsg.getPathElements();
366
367 if (newFid != fid)
368 {
369
370
371 if (sessionState.fidInUse(newFid))
372 {
373 throw new StyxException("Fid already in use");
374 }
375 }
376
377
378 RwalkMessage rWalkMsg = new RwalkMessage(new Qid[0]);
379
380 for (int i = 0; i < pathEls.length; i++)
381 {
382 if (!(sf instanceof StyxDirectory))
383 {
384 throw new StyxException(sf.getName() + " is not a directory");
385 }
386 if (!pathEls[i].equals(".."))
387 {
388 if (!sessionState.checkExecute(sf))
389 {
390
391
392 throw new StyxException(sf.getName() + ": permission denied");
393 }
394 }
395 sf.setLastAccessTime(StyxUtils.now());
396 sf = ((StyxDirectory)sf).getChild(pathEls[i]);
397 if (sf == null)
398 {
399 if (i == 0)
400 {
401 throw new StyxException("file does not exist");
402 }
403 break;
404 }
405
406
407
408
409
410 rWalkMsg.putQid(sf.getQid());
411
412
413
414 sf.refresh();
415 }
416
417 if (rWalkMsg.getNumSuccessfulWalks() == pathEls.length)
418 {
419
420
421 sessionState.associate(newFid, sf);
422 }
423 reply(session, rWalkMsg, tag);
424 }
425
426 private void replyOpen(ProtocolSession session, StyxSessionState sessionState,
427 TopenMessage tOpenMsg, int tag) throws StyxException
428 {
429 StyxFile sf = sessionState.getStyxFile(tOpenMsg.getFid());
430 int mode = tOpenMsg.getMode();
431 sessionState.checkOpen(sf, mode);
432
433 sf.addClient(new StyxFileClient(session, tOpenMsg.getFid(), mode));
434 if ((mode & StyxUtils.OTRUNC) == StyxUtils.OTRUNC)
435 {
436
437
438 sf.setLastModified(StyxUtils.now(), sessionState.getUser());
439 }
440 reply(session, new RopenMessage(sf.getQid(), sessionState.getIOUnit()), tag);
441 }
442
443 private void replyCreate(ProtocolSession session, StyxSessionState sessionState,
444 TcreateMessage tCrtMsg, int tag) throws StyxException
445 {
446 StyxFile sf = sessionState.getStyxFile(tCrtMsg.getFid());
447 if (!(sf instanceof StyxDirectory))
448 {
449 throw new StyxException("can't create a file inside another file");
450 }
451 StyxDirectory dir = (StyxDirectory)sf;
452
453 if (!sessionState.checkWrite(dir))
454 {
455 throw new StyxException("permission denied: need write permissions in parent directory");
456 }
457
458 long perm = tCrtMsg.getPerm();
459 boolean isDir = ((perm & StyxUtils.DMDIR) == StyxUtils.DMDIR);
460 boolean isAppOnly = ((perm & StyxUtils.DMAPPEND) == StyxUtils.DMAPPEND);
461 boolean isExclusive = ((perm & StyxUtils.DMEXCL) == StyxUtils.DMEXCL);
462 boolean isAuth = ((perm & StyxUtils.DMAUTH) == StyxUtils.DMAUTH);
463 if (isAuth)
464 {
465
466 throw new StyxException("can't create a file of type DMAUTH");
467 }
468
469
470 int operm = (int)(tCrtMsg.getPerm() & 1023L);
471
472
473 int realPerm;
474 if (isDir)
475 {
476 realPerm = operm & (~0777 | (dir.getPermissions() & 0777));
477
478 if (tCrtMsg.getMode() != StyxUtils.OREAD)
479 {
480 throw new StyxException("when creating a directory, " +
481 "must open with read permissions");
482 }
483 }
484 else
485 {
486 realPerm = operm & (~0666 | (dir.getPermissions() & 0666));
487 }
488
489 StyxFile newFile = dir.createChild(tCrtMsg.getFileName(), realPerm,
490 isDir, isAppOnly, isExclusive);
491
492 dir.addChild(newFile);
493
494 sessionState.associate(tCrtMsg.getFid(), newFile);
495
496
497
498
499 newFile.addClient(new StyxFileClient(session, tCrtMsg.getFid(), tCrtMsg.getMode()));
500 reply(session, new RcreateMessage(newFile.getQid(), sessionState.getIOUnit()), tag);
501 }
502
503 private void replyRead(ProtocolSession session, StyxSessionState sessionState,
504 TreadMessage tReadMsg, int tag) throws StyxException
505 {
506 StyxFile sf = sessionState.getStyxFile(tReadMsg.getFid());
507
508 StyxFileClient client = sf.getClient(session, tReadMsg.getFid());
509 if (client == null || !client.canRead())
510 {
511 throw new StyxException("File is not open for reading");
512 }
513 if (tReadMsg.getCount() > sessionState.getIOUnit())
514 {
515 throw new StyxException("can't request more than " +
516 sessionState.getIOUnit() + " bytes in a single read");
517 }
518
519 sf.read(client, tReadMsg.getOffset().asLong(), tReadMsg.getCount(), tag);
520 }
521
522 private void replyWrite(ProtocolSession session, StyxSessionState sessionState,
523 TwriteMessage tWriteMsg, int tag) throws StyxException
524 {
525 StyxFile sf = sessionState.getStyxFile(tWriteMsg.getFid());
526
527 StyxFileClient client = sf.getClient(session, tWriteMsg.getFid());
528 if (client == null || !client.canWrite())
529 {
530 throw new StyxException("File is not open for writing");
531 }
532 if (tWriteMsg.getCount() > sessionState.getIOUnit())
533 {
534 throw new StyxException("can't write more than " +
535 sessionState.getIOUnit() + " bytes in a single operation");
536 }
537 boolean truncate = client.truncate();
538 long offset = tWriteMsg.getOffset().asLong();
539
540
541
542 if (sf.isAppendOnly())
543 {
544 offset = sf.getLength().asLong();
545 truncate = false;
546 }
547
548 sf.write(client, offset, tWriteMsg.getCount(), tWriteMsg.getData(),
549 truncate, tag);
550 }
551
552 private void replyClunk(ProtocolSession session, StyxSessionState sessionState,
553 TclunkMessage tClkMsg, int tag) throws StyxException
554 {
555 sessionState.clunk(tClkMsg.getFid());
556 reply(session, new RclunkMessage(), tag);
557 }
558
559 private void replyRemove(ProtocolSession session, StyxSessionState sessionState,
560 TremoveMessage tRmMsg, int tag) throws StyxException
561 {
562 StyxFile sf = sessionState.getStyxFile(tRmMsg.getFid());
563 synchronized (sf)
564 {
565
566
567 sessionState.clunk(tRmMsg.getFid());
568
569
570
571 StyxFile parent = sf.getParent();
572 if (!sessionState.checkWrite(parent))
573 {
574
575 throw new StyxException("permission denied");
576 }
577
578 if (sf instanceof StyxDirectory)
579 {
580 StyxDirectory sd = (StyxDirectory)sf;
581 if (sd.getNumChildren() != 0)
582 {
583 throw new StyxException("directory not empty");
584 }
585 }
586 sf.remove();
587 }
588
589 sf.getParent().setLastModified(StyxUtils.now(), sessionState.getUser());
590 reply(session, new RremoveMessage(), tag);
591 }
592
593 private void replyStat(ProtocolSession session, StyxSessionState sessionState,
594 TstatMessage tStatMsg, int tag) throws StyxException
595 {
596
597 StyxFile sf = sessionState.getStyxFile(tStatMsg.getFid());
598
599 reply(session, new RstatMessage(sf.getDirEntry()), tag);
600 }
601
602 private void replyWstat(ProtocolSession session, StyxSessionState sessionState,
603 TwstatMessage tWstatMsg, int tag) throws StyxException
604 {
605 DirEntry stat = tWstatMsg.getDirEntry();
606 StyxFile sf = sessionState.getStyxFile(tWstatMsg.getFid());
607 synchronized (sf)
608 {
609
610
611
612
613
614 if (!stat.getFileName().equals(""))
615 {
616
617 StyxDirectory dir = sf.getParent();
618 if (!sessionState.checkWrite(dir))
619 {
620 throw new StyxException("write permissions required on parent directory to change file name");
621 }
622
623 if (dir.childExists(stat.getFileName()))
624 {
625 throw new StyxException("cannot rename file to the name of an existing file");
626 }
627 sf.checkSetName(stat.getFileName());
628 }
629
630
631 if (stat.getFileLength().asLong() != -1)
632 {
633
634 if (!sessionState.checkWrite(sf))
635 {
636 throw new StyxException("write permissions required to change file length");
637 }
638 sf.checkSetLength(stat.getFileLength());
639 }
640
641
642 if (stat.getMode() != StyxUtils.MAXUINT)
643 {
644
645
646 if (!sf.getOwner().equals(sessionState.getUser()))
647 {
648 throw new StyxException("must be owner to change file mode");
649 }
650 boolean setDir = ((stat.getMode() & StyxUtils.DMDIR) == StyxUtils.DMDIR);
651
652 if (setDir != sf.isDirectory())
653 {
654 throw new StyxException("can't change a file to a directory or vice-versa");
655 }
656 sf.checkSetMode(stat.getMode());
657 }
658
659
660 if (stat.getLastModifiedTime() != StyxUtils.MAXUINT)
661 {
662
663
664 if (!sf.getOwner().equals(sessionState.getUser()))
665 {
666 throw new StyxException("must be owner to change last modified time");
667 }
668 sf.checkSetLastModifiedTime(stat.getLastModifiedTime());
669 }
670
671
672 if (!stat.getGroup().equals(""))
673 {
674
675 throw new StyxException("can't change group ID on this server");
676 }
677
678
679 if (stat.getType() != StyxUtils.MAXUSHORT)
680 {
681 throw new StyxException("can't change type");
682 }
683 if (stat.getDev() != StyxUtils.MAXUINT)
684 {
685 throw new StyxException("can't change dev");
686 }
687 Qid qid = stat.getQid();
688 if (qid.getType() != StyxUtils.MAXUBYTE ||
689 qid.getVersion() != StyxUtils.MAXUINT ||
690 qid.getPath().asLong() != StyxUtils.MAXULONG)
691 {
692 throw new StyxException("can't change qid");
693 }
694 if (stat.getLastAccessTime() != StyxUtils.MAXUINT)
695 {
696 throw new StyxException("can't change last access time directly");
697 }
698 if (!stat.getOwner().equals(""))
699 {
700 throw new StyxException("can't change file owner");
701 }
702 if (!stat.getLastModifiedBy().equals(""))
703 {
704 throw new StyxException("can't directly change user who last modified the file");
705 }
706
707
708
709 if (!stat.getFileName().equals(""))
710 {
711 sf.setName(stat.getFileName());
712 }
713 if (stat.getFileLength().asLong() != -1)
714 {
715
716 sf.setLength(stat.getFileLength());
717 }
718 if (stat.getMode() != StyxUtils.MAXUINT)
719 {
720
721
722 sf.setMode(stat.getMode());
723 }
724
725
726 if (sf.isDirectory() || stat.getLastModifiedTime() != StyxUtils.MAXUINT)
727 {
728 sf.setLastModified(stat.getLastModifiedTime(), sessionState.getUser());
729 }
730 }
731
732 reply(session, new RwstatMessage(), tag);
733 }
734
735 public void messageSent( ProtocolSession session, Object message )
736 {
737 if (log.isDebugEnabled())
738 {
739 log.debug( session.getRemoteAddress() + " SENT: " + message );
740 }
741
742 if (message instanceof StyxMessage)
743 {
744 ((StyxMessage)message).dispose();
745 }
746 }
747
748 public void sessionIdle( ProtocolSession session, IdleStatus status )
749 {
750 log.debug( session.getRemoteAddress() + " IDLE(" + status + ")" );
751
752 }
753
754 public void exceptionCaught( ProtocolSession session, Throwable cause )
755 {
756 if (log.isDebugEnabled())
757 {
758 cause.printStackTrace();
759 }
760 log.error( session.getRemoteAddress() + " EXCEPTION: " + cause.getMessage());
761
762 session.close();
763 }
764
765 /***
766 * Convenience method for sending a message back to the client.
767 */
768 public static void reply(ProtocolSession session, StyxMessage message, int tag)
769 {
770 StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
771 synchronized(sessionState)
772 {
773
774 if (sessionState.tagInUse(tag))
775 {
776 message.setTag(tag);
777 session.write(message);
778 sessionState.releaseTag(tag);
779 }
780 }
781 }
782
783 }