Source code: com/neuron/jaffer/AFP_Session.java
1 /*
2 * Copyright (c) 2003 Stewart Allen <stewart@neuron.com>. All rights reserved.
3 * This program is free software. See the 'License' file for details.
4 */
5
6 package com.neuron.jaffer;
7
8 import java.io.*;
9 import java.net.*;
10 import java.util.*;
11 import java.math.*;
12
13 /*
14 * The session server is currently comprised of a listener thread,
15 * a command-handler thread and (soon) a sender thread. This should be
16 * re-written (or abstracted) at some point to take advantage of Java 1.4
17 * NIO facilities.
18 *
19 * TODO: session: nio, locking
20 * TODO: osvolume: eliminate most file caching. solve dir id's w/ id db.
21 * TODO: open/close dir deprecated. perhaps remove support.
22 */
23
24 final class AFP_Session extends Utility implements AFP_Constants
25 {
26 private final static boolean printOnlyUnknown = System.getProperty("debug.pou") != null;
27 private final static boolean nothreads = System.getProperty("nothreads") != null;
28 private final static Random random = new Random();;
29 private final static BigInteger bigOne = new BigInteger("1");
30 private final static BigInteger bigMask = new BigInteger("ffffffffffffffff", 16);
31 private final static BigInteger serverPrivate = new BigInteger(256, random);
32 private final static BigInteger serverPublic = DHX_G.modPow(serverPrivate, DHX_P);
33 private final static byte[] DHX_Encode = "CJalbert".getBytes();
34 private final static byte[] DHX_Decode = "LWallace".getBytes();
35
36 private final static int MODE_OLD = 0;
37 private final static int MODE_EXT = 1;
38 private final static int MODE_NONE = 2;
39
40 private AFP_Server server;
41 private Socket socket;
42 private InputStream input;
43 private OutputStream output;
44 private Thread recvThread;
45 private Thread cmmdThread;
46 private Thread sendThread;
47 private int maxAttnQuantum;
48 private int nextReqID = 0x1;
49 private int nextForkID = 0x1;
50 private int maxPacket = 0x8000;
51 private Hashtable openForks;
52 private CommandQueue cmmdQueue;
53 private CommandQueue sendQueue;
54 private boolean running;
55 private boolean validated;
56 private String userName;
57 private Stack packets;
58 // for UAMs
59 private long randNum;
60 private int loginType;
61 private BigInteger nonce;
62 private BigInteger sessionKey;
63 private BigInteger clientPublic;
64
65 // ----------------------------------------------------------------------------------------
66
67 AFP_Session(AFP_Server server, Socket socket)
68 {
69 this.server = server;
70 this.socket = socket;
71 this.openForks = new Hashtable();
72 this.cmmdQueue = new CommandLoop();
73 this.sendQueue = new SendLoop();
74 this.packets = new Stack();
75 }
76
77 public synchronized void start()
78 {
79 if (running)
80 {
81 return;
82 }
83 recvThread = new Thread(new ReceiveLoop(), "AFP Session ["+getSessionID()+"] Receiver");
84 cmmdThread = new Thread(cmmdQueue, "AFP Session ["+getSessionID()+"] Command Dispatch");
85 sendThread = new Thread(sendQueue, "AFP Session ["+getSessionID()+"] Sender");
86 recvThread.start();
87 cmmdThread.start();
88 sendThread.start();
89 running = true;
90 }
91
92 private void print(String msg)
93 {
94 if (server.DEBUG_PRINT)
95 {
96 System.out.println(msg);
97 }
98 }
99
100 private void debug(String msg)
101 {
102 if (server.DEBUG_DEBUG)
103 {
104 print("-- "+msg);
105 }
106 }
107
108 private void error(boolean value, String msg)
109 throws Exception
110 {
111 if (value)
112 {
113 throw new Exception(msg);
114 }
115 }
116
117 private void printPacket(DSI_Packet dp)
118 {
119 synchronized (socket)
120 {
121 if (dp.isRequest())
122 {
123 if (server.DEBUG_DSI_LINE)
124 {
125 print("---["+getSessionID()+"]-----------------------------------------------------------");
126 }
127 if (server.DEBUG_DSI)
128 {
129 print("<< client "+dp);
130 }
131 if (server.DEBUG_DSI_REQUEST)
132 {
133 dp.dumpRecvPayload(" < ");
134 }
135 }
136 else
137 {
138 if (server.DEBUG_DSI)
139 {
140 print(">> server "+dp);
141 }
142 if (server.DEBUG_DSI_REPLY)
143 {
144 dp.dumpSendPayload(" > ");
145 }
146 }
147 }
148 }
149
150 private DSI_Packet recvPacket()
151 throws IOException
152 {
153 DSI_Packet dp = null;
154 synchronized (packets)
155 {
156 if (packets.empty())
157 {
158 dp = new DSI_Packet(maxPacket);
159 }
160 else
161 {
162 dp = (DSI_Packet)packets.pop();
163 }
164 }
165 dp.reset();
166 synchronized (input)
167 {
168 dp.read(input);
169 }
170 return dp;
171 }
172
173 private void sendPacket(DSI_Packet dp)
174 throws IOException
175 {
176 synchronized (output)
177 {
178 dp.write(output);
179 }
180 // do not reuse packets that are too small
181 if (dp.getBufferSize() >= maxPacket)
182 {
183 synchronized (packets)
184 {
185 packets.push(dp);
186 }
187 }
188 if (!printOnlyUnknown)
189 {
190 printPacket(dp);
191 }
192 }
193
194 private int getSessionID()
195 {
196 return socket.getPort();
197 }
198
199 private int nextRequestID()
200 {
201 nextReqID++;
202 if (nextReqID > 0xffff)
203 {
204 nextReqID = 0;
205 }
206 return nextReqID;
207 }
208
209 private int nextForkID()
210 {
211 nextForkID++;
212 if (nextForkID > 0xffff)
213 {
214 nextForkID = 0;
215 }
216 return nextForkID;
217 }
218
219 private synchronized void terminateSession()
220 {
221 if (!running)
222 {
223 return;
224 }
225 //debug("["+getSessionID()+"] Session Terminating");
226 print("session ["+getSessionID()+"] terminating");
227 recvThread.interrupt();
228 cmmdThread.interrupt();
229 synchronized (cmmdThread)
230 {
231 cmmdThread.notify();
232 }
233 sendThread.interrupt();
234 synchronized (sendThread)
235 {
236 sendThread.notify();
237 }
238 try
239 {
240 socket.getInputStream().close();
241 socket.getOutputStream().close();
242 socket.close();
243 }
244 catch (Exception ex)
245 {
246 //ex.printStackTrace();
247 }
248 running = false;
249 }
250
251 // ----------------------------------------------------------------------------------------
252
253 private abstract class CommandQueue extends Queue implements Runnable
254 {
255 public void run()
256 {
257 while (true)
258 {
259 DSI_Packet dp = (DSI_Packet)dequeue();
260 if (dp == null)
261 {
262 break;
263 }
264 try
265 {
266 handleCommand(dp);
267 }
268 catch (IOException ex)
269 {
270 ex.printStackTrace();
271 break;
272 }
273 }
274 //debug("["+getSessionID()+"] Loop ("+getClass()+") Terminated");
275 terminateSession();
276 }
277
278 public abstract void handleCommand(DSI_Packet dp)
279 throws IOException
280 ;
281 }
282
283 // ----------------------------------------------------------------------------------------
284
285 private class ReceiveLoop implements Runnable
286 {
287 public void run()
288 {
289 try
290 {
291 input = socket.getInputStream();
292 output = socket.getOutputStream();
293 print("session ["+getSessionID()+"] started");
294 boolean valid = true;
295 while (valid)
296 {
297 DSI_Packet dp = recvPacket();
298 if (!printOnlyUnknown)
299 {
300 printPacket(dp);
301 }
302 if (dp.isReply())
303 {
304 continue;
305 }
306 switch (dp.getCommand())
307 {
308 case DSI_Constants.CMD_GET_STATUS:
309 dp.setReply();
310 server.getServerInfo().write(dp.getWriter());
311 //dp.getWriter().writeBytes(server.getServerInfo().encode());
312 sendPacket(dp);
313 break;
314 case DSI_Constants.CMD_OPEN_SESSION:
315 ByteReader rr = dp.getReader();
316 int cmd = rr.readUnsignedByte();
317 int opt = rr.readUnsignedByte();
318 int qnt = rr.readInt();
319 switch (cmd)
320 {
321 // sent by a client
322 case DSI_Constants.OPT_ATTN_QUANT:
323 error(opt != 4, "Option length != 4");
324 maxAttnQuantum = qnt;
325 dp.setReply();
326 ByteWriter ww = dp.getWriter();
327 ww.writeByte(DSI_Constants.OPT_SERV_QUANT);
328 ww.writeByte(4); // length
329 ww.writeInt(maxPacket); // server quantum (max client request)
330 sendPacket(dp);
331 break;
332 // sent by a server
333 case DSI_Constants.OPT_SERV_QUANT:
334 print("We are not a client");
335 terminateSession();
336 break;
337 default:
338 print("Unknown type: "+hex(cmd));
339 terminateSession();
340 break;
341 }
342 break;
343 case DSI_Constants.CMD_WRITE:
344 case DSI_Constants.CMD_COMMAND:
345 if (nothreads)
346 {
347 cmmdQueue.handleCommand(dp);
348 }
349 else
350 {
351 cmmdQueue.enqueue(dp);
352 }
353 break;
354 case DSI_Constants.CMD_CLOSE_SESSION:
355 valid = false;
356 break;
357 case DSI_Constants.CMD_ATTENTION:
358 // we are not a client
359 break;
360 case DSI_Constants.CMD_TICKLE:
361 dp.setRequestID(nextRequestID());
362 sendPacket(dp);
363 break;
364 default:
365 print("!!!!! Invalid DSI command: "+hex(dp.getCommand())+" !!!!!");
366 dp.setReply();
367 sendPacket(dp);
368 return;
369 }
370 }
371 }
372 catch (Exception ex)
373 {
374 print("session ["+getSessionID()+"] error '"+ex+"'");
375 //ex.printStackTrace();
376 }
377 //print("** ["+getSessionID()+"] Receive Loop Exited");
378 terminateSession();
379 }
380 }
381
382 // ----------------------------------------------------------------------------------------
383
384 private class CommandLoop extends CommandQueue
385 {
386 public void handleCommand(DSI_Packet dp)
387 throws IOException
388 {
389 ByteReader rr = dp.getReader();
390 ByteWriter ww = dp.getWriter();
391 int err = ERR_NO_ERR;
392 int cmd = rr.readUnsignedByte();
393 try
394 {
395 // auth pre-check check
396 switch (cmd)
397 {
398 case CMD_LOGIN:
399 case CMD_LOGIN_EXT:
400 case CMD_LOGIN_CONT:
401 break;
402 default:
403 if (!validated)
404 {
405 throw new AFP_Error(ERR_PARAM_ERR);
406 }
407 break;
408 }
409 // command dispatch
410 switch (cmd)
411 {
412 case CMD_LOGIN: err = cmdLogin(rr, ww); break;
413 case CMD_LOGIN_EXT: err = cmdLoginExt(rr, ww); break;
414 case CMD_LOGIN_CONT: err = cmdLoginCont(rr, ww); break;
415 case CMD_LOGOUT: err = cmdLogout(rr, ww); break;
416 case CMD_MAP_ID: err = cmdMapID(rr, ww); break;
417 case CMD_CREATE_DIR: err = cmdCreateDir(rr, ww); break;
418 case CMD_CREATE_FILE: err = cmdCreateFile(rr, ww); break;
419 case CMD_CREATE_ID: err = ERR_PARAM_ERR; break; // TODO
420 case CMD_GET_USER_INFO: err = cmdGetUserInfo(rr, ww); break;
421 case CMD_GET_SRVR_PARMS: err = cmdGetServerParams(rr, ww); break;
422 case CMD_GET_VOL_PARMS: err = cmdGetVolumeParams(rr, ww); break;
423 case CMD_GET_FILE_DIR_PARMS: err = cmdGetFileDirParams(rr, ww); break;
424 case CMD_GET_SESSION_TOKEN: err = cmdGetSessionToken(rr, ww); break;
425 case CMD_SET_VOL_PARMS: err = ERR_NO_ERR; break; // TODO
426 case CMD_SET_DIR_PARMS: err = cmdSetDirParams(rr, ww); break;
427 case CMD_SET_FORK_PARMS: err = cmdSetForkParams(rr, ww); break;
428 case CMD_SET_FILE_PARMS: err = cmdSetFileParams(rr, ww); break;
429 case CMD_SET_FILE_DIR_PARMS: err = cmdSetFileDirParams(rr, ww); break;
430 case CMD_OPEN_DT: err = ERR_MISC_ERR; break; // TODO
431 case CMD_OPEN_VOL: err = cmdOpenVolume(rr, ww); break;
432 case CMD_OPEN_DIR: err = cmdOpenDir(rr, ww); break;
433 case CMD_OPEN_FORK: err = cmdOpenFork(rr, ww); break;
434 case CMD_ENUMERATE: err = cmdEnumerate(rr, ww); break;
435 case CMD_ENUMERATE_EXT2: err = cmdEnumerateExt2(rr, ww); break;
436 case CMD_READ: err = cmdRead(rr, ww); break;
437 case CMD_READ_EXT: err = cmdReadExt(rr, ww); break;
438 case CMD_WRITE: err = cmdWrite(rr, ww); break;
439 case CMD_WRITE_EXT: err = cmdWriteExt(rr, ww); break;
440 case CMD_FLUSH_FORK: err = cmdFlushFork(rr, ww); break;
441 case CMD_CLOSE_VOL: err = cmdCloseVolume(rr, ww); break;
442 case CMD_CLOSE_DIR: err = cmdCloseDir(rr, ww); break;
443 case CMD_CLOSE_FORK: err = cmdCloseFork(rr, ww); break;
444 case CMD_MOVE_AND_RENAME: err = cmdMoveAndRename(rr, ww); break;
445 case CMD_RENAME: err = cmdRename(rr, ww); break;
446 case CMD_DELETE: err = cmdDelete(rr, ww); break;
447 default:
448 print("!!!!! Unhandled AFP command: "+hex(cmd)+" !!!!!");
449 if (printOnlyUnknown)
450 {
451 printPacket(dp);
452 }
453 err = ERR_CALL_NOT_SUPPORTED;
454 break;
455 }
456 }
457 catch (AFP_Error ae)
458 {
459 err = ae.getError();
460 }
461 catch (IOException ex)
462 {
463 err = ERR_MISC_ERR;
464 ex.printStackTrace();
465 }
466 catch (Exception ex)
467 {
468 // TODO: we should perhaps shut down the session here
469 err = ERR_MISC_ERR;
470 ex.printStackTrace();
471 }
472 dp.setErrorCode(err);
473 dp.setReply();
474 if (nothreads)
475 {
476 sendPacket(dp);
477 }
478 else
479 {
480 sendQueue.enqueue(dp);
481 }
482 }
483 }
484
485 // ----------------------------------------------------------------------------------------
486
487 private class SendLoop extends CommandQueue
488 {
489 public void handleCommand(DSI_Packet dp)
490 throws IOException
491 {
492 sendPacket(dp);
493 }
494 }
495
496 // ----------------------------------------------------------------------------------------
497
498 // CMD_LOGIN
499 private int cmdLogin(ByteReader rr, ByteWriter ww)
500 throws IOException
501 {
502 //note: pad byte not being sent from OSX per spec
503 String afp = rr.readPString();
504 String uam = rr.readPString();
505 debug("login afp="+afp+",uam="+uam+",ssn="+getSessionID());
506 return loginCommon(uam, null, rr, ww);
507 }
508
509 // CMD_LOGIN_EXT
510 private int cmdLoginExt(ByteReader rr, ByteWriter ww)
511 throws IOException
512 {
513 rr.skip(1);
514 int flags = rr.readUnsignedShort();
515 String afp = rr.readPString();
516 String uam = rr.readPString();
517 String usName = rr.readTypedString();
518 String paName = rr.readTypedString();
519 rr.skipBytes(rr.getPosition()%2);
520 debug("loginext afp="+afp+",uam="+uam+",user="+usName+",path="+paName+",ssn="+getSessionID());
521 return loginCommon(uam, usName, rr, ww);
522 }
523
524 // login common helper method
525 private int loginCommon(String uam, String user, ByteReader rr, ByteWriter ww)
526 throws IOException
527 {
528 ww.writeShort(getSessionID());
529 loginType = getUAM(uam);
530 switch (loginType)
531 {
532 case UAM_GUEST:
533 user = server.getGuestUser();
534 if (user != null && server.hasUser(user))
535 {
536 userName = user;
537 validated = true;
538 }
539 else
540 {
541 return ERR_USER_NOT_AUTH;
542 }
543 break;
544 case UAM_CLEARTEXT:
545 if (user == null)
546 {
547 user = rr.readPString();
548 }
549 rr.skip(rr.getPosition() % 2);
550 String pass = rr.readCString(8);
551 if (!server.checkPassword(user, pass))
552 {
553 return ERR_USER_NOT_AUTH;
554 }
555 userName = user;
556 server.setThreadOwner(userName);
557 validated = true;
558 break;
559 case UAM_RANDOM_NUM1:
560 if (!server.hasCleartextPasswords())
561 {
562 return ERR_BAD_UAM;
563 }
564 if (user == null)
565 {
566 user = rr.readPString();
567 }
568 if (!server.hasUser(user))
569 {
570 return ERR_USER_NOT_AUTH;
571 }
572 userName = user;
573 randNum = random.nextLong();
574 ww.writeLong(randNum);
575 return ERR_AUTH_CONTINUE;
576 case UAM_DHX_128:
577 if (user == null)
578 {
579 user = rr.readPString();
580 }
581 if (!server.hasUser(user))
582 {
583 return ERR_USER_NOT_AUTH;
584 }
585 rr.skip(rr.getPosition() % 2);
586 clientPublic = new BigInteger(1, rr.readBytes(16));
587 sessionKey = clientPublic.modPow(serverPrivate, DHX_P);
588 nonce = new BigInteger(128, random).abs();
589 byte inbuf[] = new byte[32];
590 byte outbuf[] = new byte[32];
591 System.arraycopy(keyBytes(nonce),0,inbuf,0,16);
592 new CAST128(keyBytes(sessionKey)).encrypt(inbuf, 0, outbuf, 0, 32, DHX_Encode);
593 ww.writeBytes(keyBytes(serverPublic));
594 ww.writeBytes(outbuf);
595 userName = user;
596 return ERR_AUTH_CONTINUE;
597 case UAM_UNKNOWN:
598 default:
599 return ERR_BAD_UAM;
600 }
601 return ERR_NO_ERR;
602 }
603
604 // CMD_LOGIN_CONT
605 private int cmdLoginCont(ByteReader rr, ByteWriter ww)
606 throws IOException
607 {
608 rr.skip(1);
609 int context = rr.readUnsignedShort();
610 debug("logincont context="+hex(context));
611 switch (loginType)
612 {
613 case UAM_RANDOM_NUM1:
614 if (!server.hasCleartextPasswords())
615 {
616 return ERR_BAD_UAM;
617 }
618 byte[] cryptRand = rr.readBytes(8);
619 debug("crypted random = "+hex(cryptRand));
620 String pass = server.getPassword(userName);
621 if (pass != null)
622 {
623 byte pb[] = new byte[8];
624 System.arraycopy(pass.getBytes(), 0, pb, 0, pass.length());
625 new DES(pb).decrypt(cryptRand);
626 if (readInt8(cryptRand, 0) != randNum)
627 {
628 return ERR_USER_NOT_AUTH;
629 }
630 }
631 server.setThreadOwner(userName);
632 validated = true;
633 break;
634 case UAM_DHX_128:
635 byte inbuf[] = new byte[16+64];
636 byte outbuf[] = new byte[16+64];
637 rr.readBytes(inbuf);
638 CAST128 c = new CAST128(keyBytes(sessionKey));
639 c.decrypt(inbuf, 0, outbuf, 0, 32, DHX_Decode);
640 byte nonceb[] = new byte[16];
641 System.arraycopy(outbuf, 0, nonceb, 0, 16);
642 BigInteger noncep1 = new BigInteger(1, nonceb);
643 //TODO: it appears that after the first auth, only the
644 // last 8 bytes are incremented by the client. thus the 'and'
645 if (!noncep1.subtract(nonce).and(bigMask).equals(bigOne))
646 {
647 return ERR_PARAM_ERR;
648 }
649 pass = readCString(outbuf, 16);
650 if (!server.checkPassword(userName, pass))
651 {
652 return ERR_USER_NOT_AUTH;
653 }
654 server.setThreadOwner(userName);
655 validated = true;
656 break;
657 case UAM_UNKNOWN:
658 case UAM_GUEST:
659 case UAM_CLEARTEXT:
660 default:
661 return ERR_BAD_UAM;
662 }
663 return ERR_NO_ERR;
664 }
665
666 // CMD_LOGOUT
667 private int cmdLogout(ByteReader rr, ByteWriter ww)
668 throws IOException
669 {
670 return ERR_NO_ERR;
671 }
672
673 // CMD_GET_USER_INFO
674 private int cmdGetUserInfo(ByteReader rr, ByteWriter ww)
675 throws IOException
676 {
677 int thisUser = rr.readUnsignedByte();
678 int uid = rr.readInt();
679 int flags = rr.readUnsignedShort(); // bits: 0=user, 1=group
680 debug("getuserinfo this="+thisUser+",uid="+uid+",flags="+hex(flags));
681 ww.writeShort(flags);
682 if (hasBits(flags, 0x1)) { ww.writeInt(0x00000000); } // user = guest
683 if (hasBits(flags, 0x2)) { ww.writeInt(0x00000000); } // group = guest
684 return ERR_NO_ERR;
685 }
686
687 // CMD_GET_SRVR_PARMS
688 private int cmdGetServerParams(ByteReader rr, ByteWriter ww)
689 throws IOException
690 {
691 AFP_Volume v[] = server.getVolumes();
692 ww.writeInt(unix2afpTime(System.currentTimeMillis()));
693 ww.writeByte(v.length);
694 for (int i=0; i<v.length; i++)
695 {
696 ww.writeByte( ((v[i].hasUnixPrivs() ? 0x1 : 0) | (v[i].getPassword() != null ? 0x80 : 0)) );
697 ww.writePString(v[i].getName());
698 }
699 return ERR_NO_ERR;
700 }
701
702 // CMD_OPEN_VOL
703 private int cmdOpenVolume(ByteReader rr, ByteWriter ww)
704 throws IOException
705 {
706 rr.skip(1);
707 int flags = rr.readUnsignedShort();
708 String volName = rr.readPString();
709 String volPass = rr.readCString(8);
710 debug("openvol name=("+volName+") pass=("+volPass+")");
711 AFP_Volume vol = server.getVolume(volName);
712 if (vol == null)
713 {
714 return ERR_OBJECT_NOT_FOUND;
715 }
716 String passCheck = vol.getPassword();
717 if (passCheck != null && (volPass == null || !passCheck.equals(volPass)))
718 {
719 return ERR_ACCESS_DENIED;
720 }
721 sendVolumeInfo(ww, vol, flags);
722 return ERR_NO_ERR;
723 }
724
725 // CMD_DELETE
726 private int cmdDelete(ByteReader rr, ByteWriter ww)
727 throws IOException
728 {
729 rr.skip(1);
730 int volID = rr.readUnsignedShort();
731 int dirID = rr.readInt();
732 String pathName = rr.readTypedString();
733 debug("delete vol="+volID+",dir="+dirID+",path="+pathName);
734 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
735 if (node.isDirectory() && node.getOffspringCount() > 0)
736 {
737 return ERR_DIR_NOT_EMPTY;
738 }
739 if (node.delete())
740 {
741 return ERR_NO_ERR;
742 }
743 else
744 {
745 return ERR_ACCESS_DENIED;
746 }
747 }
748
749 // CMD_GET_FILE_DIR_PARMS
750 private int cmdGetFileDirParams(ByteReader rr, ByteWriter ww)
751 throws IOException
752 {
753 rr.skip(1);
754 int volID = rr.readUnsignedShort();
755 int dirID = rr.readInt();
756 int fileFlags = rr.readUnsignedShort();
757 int dirFlags = rr.readUnsignedShort();
758 String pathName = rr.readTypedString();
759
760 debug("getparm vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",path="+pathName);
761
762 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
763 ww.writeShort(fileFlags);
764 ww.writeShort(dirFlags);
765 if (node.isDirectory())
766 {
767 sendDirectoryInfo(ww, node, dirFlags, MODE_EXT);
768 }
769 else
770 if (node.isFile())
771 {
772 sendFileInfo(ww, node, fileFlags, MODE_EXT);
773 }
774 return ERR_NO_ERR;
775 }
776
777 // CMD_SET_FORK_PARMS
778 private int cmdSetForkParams(ByteReader rr, ByteWriter ww)
779 throws IOException
780 {
781 rr.skip(1);
782 int forkRef = rr.readUnsignedShort();
783 int flags = rr.readUnsignedShort();
784 int length = rr.readInt();
785 debug("setforkparms fork="+forkRef+",flags="+hex(flags)+",len="+hex(length));
786 // TODO -- validate fork type and make sure other flags aren't set.
787 getFork(forkRef).setLength(length);
788 return ERR_NO_ERR;
789 }
790
791 // CMD_SET_FILE_PARMS
792 private int cmdSetFileParams(ByteReader rr, ByteWriter ww)
793 throws IOException
794 {
795 rr.skip(1);
796 int volID = rr.readUnsignedShort();
797 int dirID = rr.readInt();
798 int fileFlags = rr.readUnsignedShort();
799 String pathName = rr.readTypedString();
800 debug("setfileparm vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",path="+pathName);
801 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
802 recvFileInfo(rr, node, fileFlags);
803 return ERR_NO_ERR;
804 }
805
806 // CMD_SET_DIR_PARMS
807 private int cmdSetDirParams(ByteReader rr, ByteWriter ww)
808 throws IOException
809 {
810 rr.skip(1);
811 int volID = rr.readUnsignedShort();
812 int dirID = rr.readInt();
813 int dirFlags = rr.readUnsignedShort();
814 String pathName = rr.readTypedString();
815 debug("setdirparm vol="+volID+",dir="+dirID+",df="+hex(dirFlags)+",path="+pathName);
816 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
817 recvDirectoryInfo(rr, node, dirFlags);
818 return ERR_NO_ERR;
819 }
820
821 // CMD_SET_FILE_DIR_PARMS
822 private int cmdSetFileDirParams(ByteReader rr, ByteWriter ww)
823 throws IOException
824 {
825 rr.skip(1);
826 int volID = rr.readUnsignedShort();
827 int dirID = rr.readInt();
828 int flags = rr.readUnsignedShort();
829 String pathName = rr.readTypedString();
830 debug("setfiledirparm vol="+volID+",dir="+dirID+",df="+hex(flags)+",path="+pathName);
831 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
832 if (node.isDirectory())
833 {
834 recvDirectoryInfo(rr, node, flags);
835 }
836 else
837 {
838 recvFileInfo(rr, node, flags);
839 }
840 return ERR_NO_ERR;
841 }
842
843 // CMD_GET_VOL_PARMS
844 private int cmdGetVolumeParams(ByteReader rr, ByteWriter ww)
845 throws IOException
846 {
847 rr.skip(1);
848 int volID = rr.readUnsignedShort();
849 int flags = rr.readUnsignedShort();
850 AFP_Volume vol = server.getVolume(volID);
851 if (vol == null)
852 {
853 return ERR_PARAM_ERR;
854 }
855 sendVolumeInfo(ww, vol, flags);
856 return ERR_NO_ERR;
857 }
858
859 // CMD_GET_SESSION_TOKEN
860 private int cmdGetSessionToken(ByteReader rr, ByteWriter ww)
861 throws IOException
862 {
863 rr.skip(1);
864 int type = rr.readUnsignedShort();
865 int dlen = rr.readInt();
866 ww.writeShort(type);
867 ww.writeInt(4);
868 ww.writeInt(0x77665544);
869 return ERR_NO_ERR;
870 }
871
872 // CMD_CLOSE_VOL
873 private int cmdCloseVolume(ByteReader rr, ByteWriter ww)
874 throws IOException
875 {
876 rr.skip(1);
877 int volID = rr.readUnsignedShort();
878 return ERR_NO_ERR;
879 }
880
881 // CMD_MAP_ID
882 private int cmdMapID(ByteReader rr, ByteWriter ww)
883 throws IOException
884 {
885 rr.skip(1);
886 int userID = rr.readInt();
887 ww.writePString("doodleman");
888 return ERR_NO_ERR;
889 }
890
891 // CMD_CREATE_DIR
892 private int cmdCreateDir(ByteReader rr, ByteWriter ww)
893 throws IOException
894 {
895 rr.skip(1);
896 int volID = rr.readUnsignedShort();
897 int dirID = rr.readInt();
898 String pathName = rr.readTypedString();
899 debug("createdir vol="+volID+",dir="+dirID+",path="+pathName);
900 ww.writeInt(createDirPath(volID, dirID, pathName).getNode().getNodeID());
901 return ERR_NO_ERR;
902 }
903
904 // CMD_CREATE_FILE
905 private int cmdCreateFile(ByteReader rr, ByteWriter ww)
906 throws IOException
907 {
908 rr.skip(1);
909 int volID = rr.readUnsignedShort();
910 int dirID = rr.readInt();
911 String pathName = rr.readTypedString();
912 debug("createfile vol="+volID+",dir="+dirID+",path="+pathName);
913 createFilePath(volID, dirID, pathName);
914 return ERR_NO_ERR;
915 }
916
917 // CMD_OPEN_DIR
918 private int cmdOpenDir(ByteReader rr, ByteWriter ww)
919 throws IOException
920 {
921 rr.skip(1);
922 int volID = rr.readUnsignedShort();
923 int dirID = rr.readInt();
924 String pathName = rr.readTypedString();
925 debug("opendir vol="+hex(volID)+",dir="+hex(dirID)+",path="+pathName);
926 AFP_CNode node = openPath(volID, dirID, pathName).open().getNode();
927 if (!node.isDirectory())
928 {
929 return ERR_OBJECT_TYPE_ERR;
930 }
931 ww.writeInt(node.getNodeID());
932 return ERR_NO_ERR;
933 }
934
935 // CMD_OPEN_FORK
936 private int cmdOpenFork(ByteReader rr, ByteWriter ww)
937 throws IOException
938 {
939 int which = rr.readUnsignedByte(); // bits: 7=fork (0=file, 1=resource)
940 int volID = rr.readUnsignedShort();
941 int dirID = rr.readInt();
942 int flags = rr.readUnsignedShort(); // bits: getFileParams bits
943 int mode = rr.readUnsignedShort(); // bits: 0=read, 1=write, 4=readlock, 5=writelock
944 String pathName = rr.readTypedString();
945 debug("openfork flags="+hex(flags)+",which="+hex(which)+",mode="+hex(mode)+",path="+pathName);
946 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
947 if (node.isDirectory())
948 {
949 return ERR_OBJECT_TYPE_ERR;
950 }
951 int fid = nextForkID();
952 AFP_Fork fork = hasBits(which,0x80) ? node.openResourceFork(mode) : node.openFileFork(mode);
953 if (fork == null)
954 {
955 return ERR_ACCESS_DENIED;
956 }
957 debug("openfork ref="+hex(fid));
958 openForks.put(new Integer(fid), fork);
959 ww.writeShort(flags);
960 ww.writeShort(fid);
961 sendFileInfo(ww, node, flags, MODE_NONE);
962 return ERR_NO_ERR;
963 }
964
965 // CMD_RENAME
966 private int cmdRename(ByteReader rr, ByteWriter ww)
967 throws IOException
968 {
969 rr.skip(1);
970 int volID = rr.readUnsignedShort();
971 int dirID = rr.readInt();
972 String path = rr.readTypedString();
973 String newName = rr.readTypedString();
974 debug("rename vol="+volID+",dir="+dirID+",path="+path+",newName="+newName);
975 Path dir = openPath(volID, dirID, path);
976 if (!dir.getNode().moveTo(dir.getNode(), newName))
977 {
978 return ERR_ACCESS_DENIED;
979 }
980 return ERR_NO_ERR;
981 }
982
983 // CMD_MOVE_AND_RENAME
984 private int cmdMoveAndRename(ByteReader rr, ByteWriter ww)
985 throws IOException
986 {
987 rr.skip(1);
988 int volID = rr.readUnsignedShort();
989 int srcDirID = rr.readInt();
990 int dstDirID = rr.readInt();
991 String srcPath = rr.readTypedString();
992 String dstPath = rr.readTypedString();
993 String newName = rr.readTypedString();
994 debug("move/rename vol="+volID+",srcDir="+srcDirID+",dstDir="+dstDirID+",srcPath="+srcPath+",dstPath="+dstPath+",name="+newName);
995 Path src = openPath(volID, srcDirID, srcPath);
996 Path dst = openPath(volID, dstDirID, dstPath);
997 if (!src.getNode().moveTo(dst.getNode(), newName))
998 {
999 return ERR_ACCESS_DENIED;
1000 }
1001 return ERR_NO_ERR;
1002 }
1003
1004 // CMD_READ
1005 private int cmdRead(ByteReader rr, ByteWriter ww)
1006 throws IOException
1007 {
1008 rr.skip(1);
1009 int forkRef = rr.readUnsignedShort();
1010 long offset = rr.readInt();
1011 long length = rr.readInt();
1012 int nlMask = rr.readUnsignedByte();
1013 int nlChar = rr.readUnsignedByte();
1014 debug("read fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length)+",nlm="+hex(nlMask)+",nlc="+hex(nlChar));
1015 getFork(forkRef).readRange(offset, length, ww);
1016 return ERR_NO_ERR;
1017 }
1018
1019 // CMD_READ_EXT
1020 private int cmdReadExt(ByteReader rr, ByteWriter ww)
1021 throws IOException
1022 {
1023 rr.skip(1);
1024 int forkRef = rr.readUnsignedShort();
1025 long offset = rr.readLong();
1026 long length = rr.readLong();
1027 debug("readx fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1028 getFork(forkRef).readRange(offset, length, ww);
1029 return ERR_NO_ERR;
1030 }
1031
1032 // CMD_WRITE
1033 private int cmdWrite(ByteReader rr, ByteWriter ww)
1034 throws IOException
1035 {
1036 int flag = rr.readUnsignedByte(); // bits: 7=relative to (0=from start, 1=from end)
1037 int forkRef = rr.readUnsignedShort();
1038 long offset = rr.readInt();
1039 long length = rr.readInt();
1040 debug("write fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1041 AFP_Fork fork = getFork(forkRef);
1042 if (offset < 0)
1043 {
1044 offset = fork.getLength() - offset;
1045 }
1046 if (flag == 0x80)
1047 {
1048 offset += fork.getLength();
1049 }
1050 long wrote = fork.writeRange(offset, length, rr);
1051 ww.writeInt((int)(offset + wrote));
1052 return ERR_NO_ERR;
1053 }
1054
1055 // CMD_WRITE_EXT
1056 private int cmdWriteExt(ByteReader rr, ByteWriter ww)
1057 throws IOException
1058 {
1059 int flag = rr.readUnsignedByte(); // bits: 7=relative to (0=from start, 1=from end)
1060 int forkRef = rr.readUnsignedShort();
1061 long offset = rr.readLong();
1062 long length = rr.readLong();
1063 debug("writex fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1064 AFP_Fork fork = getFork(forkRef);
1065 if (offset < 0)
1066 {
1067 offset = fork.getLength() - offset;
1068 }
1069 if (flag == 0x80)
1070 {
1071 offset += fork.getLength();
1072 }
1073 long wrote = fork.writeRange(offset, length, rr);
1074 ww.writeLong(offset + wrote);
1075 return ERR_NO_ERR;
1076 }
1077
1078 // CMD_FLUSH_FORK
1079 private int cmdFlushFork(ByteReader rr, ByteWriter ww)
1080 throws IOException
1081 {
1082 rr.skip(1);
1083 int forkRef = rr.readUnsignedShort();
1084 debug("flush fork="+hex(forkRef));
1085 getFork(forkRef).flush();
1086 return ERR_NO_ERR;
1087 }
1088
1089 // CMD_CLOSE_DIR
1090 private int cmdCloseDir(ByteReader rr, ByteWriter ww)
1091 throws IOException
1092 {
1093 rr.skip(1);
1094 int volID = rr.readUnsignedShort();
1095 int dirID = rr.readInt();
1096 debug("closedir vol="+hex(volID)+",dir="+hex(dirID));
1097 return ERR_NO_ERR;
1098 }
1099
1100 // CMD_CLOSE_FORK
1101 private int cmdCloseFork(ByteReader rr, ByteWriter ww)
1102 throws IOException
1103 {
1104 rr.skip(1);
1105 int forkRef = rr.readUnsignedShort();
1106 AFP_Fork fork = getFork(forkRef);
1107 openForks.remove(new Integer(forkRef));
1108 debug("closefork ref="+hex(forkRef)+" fork="+fork);
1109 fork.close();
1110 return ERR_NO_ERR;
1111 }
1112
1113 // TODO: uses too many byte writers. optimize byte writers and re-use.
1114 // CMD_ENUMERATE
1115 private int cmdEnumerate(ByteReader rr, ByteWriter ww)
1116 throws IOException
1117 {
1118 rr.skip(1);
1119 int volID = rr.readUnsignedShort();
1120 int dirID = rr.readInt();
1121 int fileFlags = rr.readUnsignedShort();
1122 int dirFlags = rr.readUnsignedShort();
1123 int maxRecords = rr.readUnsignedShort();
1124 int startIndex = (rr.readUnsignedShort() - 1);
1125 int maxReply = rr.readUnsignedShort();
1126 String pathName = rr.readTypedString();
1127
1128 debug("enum vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",xrec="+hex(maxRecords)+",idx="+hex(startIndex)+",xrep="+hex(maxReply)+",path="+pathName);
1129
1130 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
1131 int numOffspring = node.getOffspringCount();
1132 int returnRecords = Math.min(maxRecords, numOffspring - startIndex);
1133 if (returnRecords <= 0)
1134 {
1135 return ERR_OBJECT_NOT_FOUND;
1136 }
1137 ww.writeShort(fileFlags);
1138 ww.writeShort(dirFlags);
1139 Enumeration en = node.getOffspringEnumeration();
1140 if (en == null)
1141 {
1142 return ERR_MISC_ERR;
1143 }
1144 int sent = 0;
1145 ByteWriter w3 = new ByteWriter(maxReply);
1146 for (int i=0; i<numOffspring; i++)
1147 {
1148 if (!en.hasMoreElements())
1149 {
1150 return ERR_OBJECT_NOT_FOUND;
1151 }
1152 AFP_CNode next = (AFP_CNode)en.nextElement();
1153 if (i < startIndex)
1154 {
1155 continue;
1156 }
1157 ByteWriter w2 = new ByteWriter(128);
1158 if (next.isDirectory())
1159 {
1160 sendDirectoryInfo(w2, next, dirFlags, MODE_OLD);
1161 }
1162 else
1163 if (next.isFile())
1164 {
1165 sendFileInfo(w2, next, fileFlags, MODE_OLD);
1166 }
1167 else
1168 {
1169 // faulty AFP_CNode implementation
1170 debug("!! node is neither file nor directory !! "+next);
1171 continue;
1172 }
1173 byte data[] = w2.toByteArray();
1174 if (data.length % 2 == 0)
1175 {
1176 w3.writeByte(data.length+2);
1177 w3.writeBytes(data);
1178 w3.writeByte(0);
1179 }
1180 else
1181 {
1182 w3.writeByte(data.length+1);
1183 w3.writeBytes(data);
1184 }
1185 sent++;
1186 if (w3.getSize() > maxReply - 128)
1187 {
1188 break;
1189 }
1190 }
1191
1192 ww.writeShort(sent);
1193 ww.writeBytes(w3.toByteArray());
1194 return ERR_NO_ERR;
1195 }
1196
1197 // TODO: uses too many byte writers. optimize byte writers and re-use.
1198 // CMD_ENUMERATE_EXT2
1199 private int cmdEnumerateExt2(ByteReader rr, ByteWriter ww)
1200 throws IOException
1201 {
1202 rr.skip(1);
1203 int volID = rr.readUnsignedShort();
1204 int dirID = rr.readInt();
1205 int fileFlags = rr.readUnsignedShort();
1206 int dirFlags = rr.readUnsignedShort();
1207 int maxRecords = rr.readUnsignedShort();
1208 int startIndex = (rr.readInt() - 1);
1209 int maxReply = rr.readInt();
1210 String pathName = rr.readTypedString();
1211
1212 debug("enum vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",xrec="+hex(maxRecords)+",idx="+hex(startIndex)+",xrep="+hex(maxReply)+",path="+pathName);
1213
1214 AFP_CNode node = openPath(volID, dirID, pathName).getNode();
1215 int numOffspring = node.getOffspringCount();
1216 int returnRecords = Math.min(maxRecords, numOffspring - startIndex);
1217 if (returnRecords <= 0)
1218 {
1219 return ERR_OBJECT_NOT_FOUND;
1220 }
1221 ww.writeShort(fileFlags);
1222 ww.writeShort(dirFlags);
1223 Enumeration en = node.getOffspringEnumeration();
1224 if (en == null)
1225 {
1226 return ERR_MISC_ERR;
1227 }
1228 int sent = 0;
1229 ByteWriter w3 = new ByteWriter(maxReply);
1230 for (int i=0; i<numOffspring; i++)
1231 {
1232 if (!en.hasMoreElements())
1233 {
1234 return ERR_OBJECT_NOT_FOUND;
1235 }
1236 AFP_CNode next = (AFP_CNode)en.nextElement();
1237 if (i < startIndex)
1238 {
1239 continue;
1240 }
1241 ByteWriter w2 = new ByteWriter(128);
1242 if (next.isDirectory())
1243 {
1244 sendDirectoryInfo(w2, next, dirFlags, MODE_EXT);
1245 }
1246 else
1247 if (next.isFile())
1248 {
1249 sendFileInfo(w2, next, fileFlags, MODE_EXT);
1250 }
1251 else
1252 {
1253 // faulty AFP_CNode implementation
1254 debug("!! node is neither file nor directory !! "+next);
1255 continue;
1256 }
1257 byte data[] = w2.toByteArray();
1258 if (data.length % 2 == 0)
1259 {
1260 w3.writeShort(data.length+2);
1261 w3.writeBytes(data);
1262 }
1263 else
1264 {
1265 w3.writeShort(data.length+3);
1266 w3.writeBytes(data);
1267 w3.writeByte(0);
1268 }
1269 sent++;
1270 if (w3.getSize() > maxReply - 128)
1271 {
1272 break;
1273 }
1274 }
1275
1276 ww.writeShort(sent);
1277 ww.writeBytes(w3.toByteArray());
1278 return ERR_NO_ERR;
1279 }
1280
1281 // ----------------------------------------------------------------------------------------
1282
1283 // USED BY: OpenVolume and GetVolumeParams
1284 private void sendVolumeInfo(ByteWriter ww, AFP_Volume vol, int flags)
1285 throws IOException
1286 {
1287 ww.writeShort(flags);
1288 ww.markDeferredOffset();
1289 if (hasBits(flags, VOL_BIT_ATTRIBUTE)) { ww.writeShort(vol.getAttributes()); }
1290 if (hasBits(flags, VOL_BIT_SIGNATURE)) { ww.writeShort(vol.getSignature()); }
1291 if (hasBits(flags, VOL_BIT_CREATE_DATE)) { ww.writeInt(vol.getCreateDate()); }
1292 if (hasBits(flags, VOL_BIT_MOD_DATE)) { ww.writeInt(vol.getModifiedDate()); }
1293 if (hasBits(flags, VOL_BIT_BACKUP_DATE)) { ww.writeInt(vol.getBackupDate()); }
1294 if (hasBits(flags, VOL_BIT_ID)) { ww.writeShort(vol.getID()); }
1295 if (hasBits(flags, VOL_BIT_BYTES_FREE)) { ww.writeInt(vol.getBytesFree()); }
1296 if (hasBits(flags, VOL_BIT_BYTES_TOTAL)) { ww.writeInt(vol.getBytesTotal()); }
1297 if (hasBits(flags, VOL_BIT_NAME)) { ww.writePStringDeferred(vol.getName()); }
1298 if (hasBits(flags, VOL_BIT_XBYTES_FREE)) { ww.writeLong(vol.getExtBytesFree()); }
1299 if (hasBits(flags, VOL_BIT_XBYTES_TOTAL)) { ww.writeLong(vol.getExtBytesTotal()); }
1300 if (hasBits(flags, VOL_BIT_BLOCK_SIZE)) { ww.writeInt(vol.getBlockSize()); }
1301 }
1302
1303 // USED BY: GetFileDirParams
1304 private void sendDirectoryInfo(ByteWriter ww, AFP_CNode node, int flags, int mode)
1305 throws IOException
1306 {
1307 // 0x80 = directory + 0x00 = pad
1308 switch (mode)
1309 {
1310 case MODE_OLD: ww.writeByte(0x80); break;
1311 case MODE_EXT: ww.writeShort(0x8000); break;
1312 }
1313 ww.markDeferredOffset();
1314 if (hasBits(flags, DIR_BIT_ATTRIBUTE)) { ww.writeShort(node.getAttributes()); }
1315 if (hasBits(flags, DIR_BIT_PARENT_DIR_ID)) { ww.writeInt(node.getParentNodeID()); }
1316 if (hasBits(flags, DIR_BIT_CREATE_DATE)) { ww.writeInt(node.getCreateDate()); }
1317 if (hasBits(flags, DIR_BIT_MOD_DATE)) { ww.writeInt(node.getModifiedDate()); }
1318 if (hasBits(flags, DIR_BIT_BACKUP_DATE)) { ww.writeInt(node.getBackupDate()); }
1319 if (hasBits(flags, DIR_BIT_FINDER_INFO)) { ww.writeBytes(node.finderInfo()); }
1320 if (hasBits(flags, DIR_BIT_LONG_NAME)) { ww.writePStringDeferred(node.longName()); }
1321 if (hasBits(flags, DIR_BIT_SHORT_NAME)) { ww.writePStringDeferred(node.shortName()); }
1322 if (hasBits(flags, DIR_BIT_NODE_ID)) { ww.writeInt(node.getNodeID()); }
1323 if (hasBits(flags, DIR_BIT_OFFSPRING_COUNT)) { ww.writeShort(node.getOffspringCount()); }
1324 if (hasBits(flags, DIR_BIT_OWNER_ID)) { ww.writeInt(node.getOwnerID()); }
1325 if (hasBits(flags, DIR_BIT_GROUP_ID)) { ww.writeInt(node.getGroupID()); }
1326 if (hasBits(flags, DIR_BIT_ACCESS_RIGHTS)) { ww.writeInt(node.getAccessRights()); }
1327 if (hasBits(flags, DIR_BIT_UTF8_NAME)) { ww.writeAFPStringDeferred(node.getUTF8Name()); }
1328 if (hasBits(flags, DIR_BIT_UNIX_PRIVS)) { ww.writeBytes(node.unixPrivs()); }
1329 }
1330
1331 // USED BY: SetDirParams
1332 private void recvDirectoryInfo(ByteReader rr, AFP_CNode node, int flags)
1333 throws IOException
1334 {
1335 rr.skipBytes(rr.getPosition() % 2);
1336 if (hasBits(flags, DIR_BIT_ATTRIBUTE)) { node.setAttributes(rr.readShort()); }
1337 if (hasBits(flags, DIR_BIT_CREATE_DATE)) { node.setCreateDate(rr.readInt()); }
1338 if (hasBits(flags, DIR_BIT_MOD_DATE)) { node.setModifiedDate(rr.readInt()); }
1339 if (hasBits(flags, DIR_BIT_BACKUP_DATE)) { node.setBackupDate(rr.readInt()); }
1340 if (hasBits(flags, DIR_BIT_FINDER_INFO)) { node.setFinderInfo(rr.readBytes(32)); }
1341 if (hasBits(flags, DIR_BIT_UNIX_PRIVS)) { node.setUnixPrivs(rr.readBytes(16)); }
1342 if (hasBits(flags, DIR_BIT_PARENT_DIR_ID |
1343 DIR_BIT_LONG_NAME |
1344 DIR_BIT_SHORT_NAME |
1345 DIR_BIT_NODE_ID |
1346 DIR_BIT_OFFSPRING_COUNT |
1347 DIR_BIT_OWNER_ID |
1348 DIR_BIT_GROUP_ID |
1349 DIR_BIT_ACCESS_RIGHTS |
1350 DIR_BIT_UTF8_NAME)) { throw new AFP_Error(ERR_BITMAP_ERR); }
1351 }
1352
1353 // USED BY: GetFileDirParams, EnumerateExt2
1354 private void sendFileInfo(ByteWriter ww, AFP_CNode node, int flags, int mode)
1355 throws IOException
1356 {
1357 // 0x00 = file + 0x00 = pad
1358 switch (mode)
1359 {
1360 case MODE_OLD: ww.writeByte(0x00); break;
1361 case MODE_EXT: ww.writeShort(0x0000); break;
1362 }
1363 ww.markDef