Source code: com/neuron/jaffer/AFP_Server.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 com.strangeberry.rendezvous.*;
12
13 /* TODO: request/reply queues
14 * * command 0x7a - client going to sleep?
15 *
16 * AFP BUGS:
17 * * sending truncated list for GetVolParams bitmap results in kernel panic / crash
18 * * Login (0x12) is *not* sending pad byte after command)
19 * * failure to reply to DSI_TICKLE results in kernel panic
20 * * AFP31 p. 32 - offspring count is 2 bytes, NOT 4 bytes
21 * * sending TICKLE with 'reply' flag panics kernel
22 * * failing to complete an enumerate_ext2 call will panic/hang the system
23 * * responding to DSI_WRITE with mirror reply panics kernel
24 */
25 public abstract class AFP_Server implements AFP_Constants, Runnable
26 {
27 static boolean DEBUG_DEBUG = true;
28 static boolean DEBUG_DSI = true;
29 static boolean DEBUG_DSI_REQUEST = true && DEBUG_DSI;
30 static boolean DEBUG_DSI_REPLY = true && DEBUG_DSI;
31 static boolean DEBUG_DSI_LINE = true && DEBUG_DSI;
32 static boolean DEBUG_PRINT = DEBUG_DEBUG | DEBUG_DSI;
33
34 private final static String[] protoStrings = {
35 "AFP3.1" ,
36 "AFP2.3"
37 };
38
39 private int port;
40 private String bind;
41 private ServerSocket socket;
42 private String serverName;
43 private Thread thread;
44 private int nextVolID = 1;
45 private Hashtable volumesByID;
46 private Hashtable volumesByName;
47 private Rendezvous rendezvous;
48 private AFP_ServerInfo serverInfo;
49
50 public AFP_Server()
51 throws IOException
52 {
53 this(TCP_PORT);
54 }
55
56 public AFP_Server(int port)
57 throws IOException
58 {
59 this(null, port);
60 }
61
62 public AFP_Server(String rname, int port)
63 throws IOException
64 {
65 this(rname, null, port);
66 }
67
68 public AFP_Server(String rname, String bind, int port)
69 throws IOException
70 {
71 this.bind = bind;
72 this.port = port;
73 this.rendezvous = new Rendezvous();
74 this.volumesByID = new Hashtable();
75 this.volumesByName = new Hashtable();
76 // set debug level
77 String dl = System.getProperty("debug.afp");
78 if (dl != null && dl.length() > 0)
79 {
80 setDebugLevel(Integer.parseInt(dl));
81 }
82 // register server with Rendezvous
83 InetAddress addr = InetAddress.getLocalHost();
84 serverName = rname != null ? rname : addr.getHostName();
85 if (rname == null && serverName.indexOf('.') > 0)
86 {
87 serverName = serverName.substring(0, serverName.indexOf('.'));
88 }
89 rendezvous.registerService(new ServiceInfo(
90 "_afpovertcp._tcp.local.", serverName+"._afpovertcp._tcp.local.", addr, port, 1, 1, "Java AFP Server"
91 ));
92 }
93
94 public void setDebugLevel(int lvl)
95 {
96 DEBUG_DEBUG = true;
97 DEBUG_DSI = true;
98 DEBUG_DSI_REQUEST = DEBUG_DSI;
99 DEBUG_DSI_REPLY = DEBUG_DSI;
100 DEBUG_DSI_LINE = DEBUG_DSI;
101 DEBUG_PRINT = DEBUG_DEBUG | DEBUG_DSI;
102
103 switch (lvl)
104 {
105 case 0:
106 DEBUG_DEBUG=false;
107 DEBUG_PRINT=false;
108 DEBUG_DSI=false;
109 break;
110 case 1:
111 DEBUG_DSI_REQUEST=false;
112 DEBUG_DSI_REPLY=false;
113 break;
114 default:
115 break;
116 }
117
118 DEBUG_DSI_REQUEST &= DEBUG_DSI;
119 DEBUG_DSI_REPLY &= DEBUG_DSI;
120 DEBUG_DSI_LINE &= DEBUG_DSI;
121 }
122
123 public synchronized int addVolume(AFP_Volume vol)
124 {
125 int id = nextVolID++;
126 volumesByID.put(new Integer(id), vol);
127 volumesByName.put(vol.getName(), vol);
128 vol.setID(id);
129 return id;
130 }
131
132 public AFP_Volume getVolume(int vid)
133 {
134 return (AFP_Volume)volumesByID.get(new Integer(vid));
135 }
136
137 public AFP_Volume getVolume(String vname)
138 {
139 return (AFP_Volume)volumesByName.get(vname);
140 }
141
142 public synchronized AFP_Volume[] getVolumes()
143 {
144 Object k[] = volumesByName.keySet().toArray();
145 AFP_Volume v[] = new AFP_Volume[k.length];
146 for (int i=0; i<k.length; i++)
147 {
148 v[i] = (AFP_Volume)volumesByName.get(k[i]);
149 }
150 return v;
151 }
152
153 public synchronized void delVolume(AFP_Volume vol)
154 {
155 if (vol == null)
156 {
157 return;
158 }
159 volumesByName.remove(vol.getName());
160 volumesByID.remove(new Integer(vol.getID()));
161 }
162
163 public void delVolume(String vname)
164 {
165 delVolume((AFP_Volume)volumesByName.get(vname));
166 }
167
168 public void delVolume(int vid)
169 {
170 delVolume((AFP_Volume)volumesByID.get(new Integer(vid)));
171 }
172
173 public synchronized void start()
174 throws IOException
175 {
176 if (thread != null)
177 {
178 return;
179 }
180 socket = bind != null ?
181 new ServerSocket(port, 10, InetAddress.getByName(bind)) :
182 new ServerSocket(port);
183 thread = new Thread(this, "AFP Server");
184 thread.start();
185 }
186
187 public void run()
188 {
189 try
190 {
191 System.out.println(
192 "Jaffer AFP/TCP Server v"+Main.VERSION+
193 " ready on port "+socket.getLocalPort()+" as "+serverName);
194 while (true)
195 {
196 acceptConnection();
197 }
198 }
199 catch (Exception ex)
200 {
201 ex.printStackTrace();
202 }
203 }
204
205 public abstract boolean hasCleartextPasswords()
206 ;
207
208 public abstract boolean hasUser(String userName)
209 ;
210
211 public abstract boolean checkPassword(String userName, String password)
212 ;
213
214 public abstract boolean setThreadOwner(String userName)
215 ;
216
217 public abstract String getPassword(String userName)
218 ;
219
220 public abstract String getGuestUser()
221 ;
222
223 public AFP_ServerInfo getServerInfo()
224 {
225 if (serverInfo == null)
226 {
227 serverInfo = new AFP_ServerInfo(serverName, protoStrings,
228 hasCleartextPasswords() ?
229 new String[] { UAM_STR_GUEST, UAM_STR_CLEARTEXT, UAM_STR_RANDOM_NUM1, UAM_STR_DHX_128 } :
230 new String[] { UAM_STR_GUEST, UAM_STR_CLEARTEXT, UAM_STR_DHX_128 },
231 0
232 );
233 }
234 return serverInfo;
235 }
236
237 private void acceptConnection()
238 throws IOException
239 {
240 Socket s = socket.accept();
241 s.setTcpNoDelay(true);
242 System.out.println("AFP_Server: connect from "+s.getInetAddress());
243 AFP_Session session = new AFP_Session(this, s);
244 session.start();
245 }
246 }
247