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 package net.sf.hermesftp.session.impl;
26
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.OutputStreamWriter;
32 import java.io.PrintWriter;
33 import java.net.Socket;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.Properties;
39 import java.util.ResourceBundle;
40
41 import net.sf.hermesftp.cmd.SocketProvider;
42 import net.sf.hermesftp.common.FtpConstants;
43 import net.sf.hermesftp.common.FtpEventListener;
44 import net.sf.hermesftp.common.FtpServerOptions;
45 import net.sf.hermesftp.common.FtpSessionContext;
46 import net.sf.hermesftp.exception.FtpConfigException;
47 import net.sf.hermesftp.exception.FtpQuotaException;
48 import net.sf.hermesftp.usermanager.UserManager;
49 import net.sf.hermesftp.usermanager.model.GroupDataList;
50 import net.sf.hermesftp.usermanager.model.UserData;
51 import net.sf.hermesftp.utils.LoggingReader;
52 import net.sf.hermesftp.utils.LoggingWriter;
53 import net.sf.hermesftp.utils.VarMerger;
54
55 import org.apache.commons.io.FileUtils;
56 import org.apache.commons.io.FilenameUtils;
57 import org.apache.commons.logging.Log;
58 import org.apache.commons.logging.LogFactory;
59
60 /***
61 * This class servers as a means of transportation for data shared by a single FTP session.
62 * Instances of the <code>FtpSessionContextImpl</code> class are passed to each of the commands
63 * while executing a FTP command sequence. The command objects read connection settings and other
64 * options from the context. In turn data that may concern the general state of the FTP session can
65 * be stored in the context.
66 *
67 * @author Lars Behnke
68 */
69 public class FtpSessionContextImpl implements FtpConstants, FtpSessionContext {
70
71 private static Log log = LogFactory.getLog(FtpSessionContextImpl.class);
72
73 private static int portIdx;
74
75 private String user;
76
77 private String password;
78
79 private boolean authenticated;
80
81 private int dataType = DT_BINARY;
82
83 private int transmissionMode = MODE_STREAM;
84
85 private int storageStructure = STRUCT_FILE;
86
87 private String remoteDir;
88
89 private Socket clientSocket;
90
91 private BufferedReader clientCmdReader;
92
93 private PrintWriter clientResponseWriter;
94
95 private FtpServerOptions options;
96
97 private FtpEventListener eventListener;
98
99 private ResourceBundle resourceBundle;
100
101 private SocketProvider dataSocketProvider;
102
103 private UserManager userManager;
104
105 private Date creationTime;
106
107 private Map<String, Object> attributes;
108
109 private Map<String, Long> sessionStatistics = Collections.synchronizedMap(new HashMap<String, Long>());
110
111 /***
112 * Constructor.
113 *
114 * @param options The server options.
115 * @param userManager The user manager.
116 * @param resourceBundle The resource bundle that containts messages and texts.
117 * @param listener The listener that is informed on session events.
118 */
119 public FtpSessionContextImpl(FtpServerOptions options, UserManager userManager,
120 ResourceBundle resourceBundle, FtpEventListener listener) {
121 super();
122 this.userManager = userManager;
123 this.resourceBundle = resourceBundle;
124 this.options = options;
125 this.attributes = Collections.synchronizedMap(new HashMap<String, Object>());
126 this.eventListener = listener;
127 }
128
129 /***
130 * {@inheritDoc}
131 */
132 public Object getAttribute(String name) {
133 return attributes.get(name);
134 }
135
136 /***
137 * {@inheritDoc}
138 */
139 public void setAttribute(String name, Object value) {
140 if (value == null) {
141 attributes.remove(name);
142 } else {
143 attributes.put(name, value);
144 }
145 }
146
147 /***
148 * {@inheritDoc}
149 */
150 public FtpServerOptions getOptions() {
151 return options;
152 }
153
154 /***
155 * {@inheritDoc}
156 */
157 public String getOption(String key) {
158 return getOptions().getProperty(key);
159 }
160
161 /***
162 * {@inheritDoc}
163 */
164 public String getPassword() {
165 return password;
166 }
167
168 /***
169 * {@inheritDoc}
170 */
171 public void setPassword(String password) {
172 this.password = password;
173 }
174
175 /***
176 * {@inheritDoc}
177 */
178 public String getRemoteDir() {
179 if (remoteDir == null) {
180 remoteDir = getOptions().getRootDir();
181 }
182 return remoteDir;
183 }
184
185 /***
186 * {@inheritDoc}
187 */
188 public String getRemoteRelDir() {
189 String relDir = null;
190 try {
191 String canDir = new File(getRemoteDir()).getCanonicalPath();
192 canDir = FilenameUtils.normalizeNoEndSeparator(canDir);
193
194 String canRoot = new File(getOptions().getRootDir()).getCanonicalPath();
195 canRoot = FilenameUtils.normalizeNoEndSeparator(canRoot);
196
197 if (canDir.toUpperCase().startsWith(canRoot.toUpperCase())) {
198 relDir = canDir.substring(canRoot.length());
199 }
200 if (!relDir.startsWith(File.separator)) {
201 relDir = File.separator + relDir;
202 }
203 } catch (IOException e) {
204 log.error(e);
205 }
206 return relDir;
207 }
208
209 /***
210 * {@inheritDoc}
211 */
212 public void setRemoteDir(String remoteDir) {
213 this.remoteDir = FilenameUtils.normalizeNoEndSeparator(remoteDir);
214 }
215
216 /***
217 * {@inheritDoc}
218 */
219 public String getUser() {
220 return user;
221 }
222
223 /***
224 * {@inheritDoc}
225 */
226 public void setUser(String user) {
227 this.user = user;
228 }
229
230 /***
231 * {@inheritDoc}
232 */
233 public FtpEventListener getEventListener() {
234 return eventListener;
235 }
236
237 /***
238 * {@inheritDoc}
239 */
240 public String getRes(String id) {
241 return resourceBundle.getString(id);
242 }
243
244 /***
245 * {@inheritDoc}
246 */
247 public boolean isAuthenticated() {
248 return authenticated;
249 }
250
251 /***
252 * {@inheritDoc}
253 */
254 public int getDataType() {
255 return dataType;
256 }
257
258 /***
259 * {@inheritDoc}
260 */
261 public void setDataType(int dataType) {
262 this.dataType = dataType;
263 }
264
265 /***
266 * {@inheritDoc}
267 */
268 public int getStorageStructure() {
269 return storageStructure;
270 }
271
272 /***
273 * {@inheritDoc}
274 */
275 public void setStorageStructure(int storageStructure) {
276 this.storageStructure = storageStructure;
277 }
278
279 /***
280 * {@inheritDoc}
281 */
282 public int getTransmissionMode() {
283 return transmissionMode;
284 }
285
286 /***
287 * {@inheritDoc}
288 */
289 public void setTransmissionMode(int transmissionMode) {
290 this.transmissionMode = transmissionMode;
291 }
292
293 /***
294 * {@inheritDoc}
295 */
296 public SocketProvider getDataSocketProvider() {
297 return dataSocketProvider;
298 }
299
300 /***
301 * {@inheritDoc}
302 */
303 public void setDataSocketProvider(SocketProvider provider) {
304 this.dataSocketProvider = provider;
305 }
306
307 /***
308 * {@inheritDoc}
309 */
310 public Socket getClientSocket() {
311 return clientSocket;
312 }
313
314 /***
315 * {@inheritDoc}
316 */
317 public void setClientSocket(Socket clientSocket) throws IOException {
318 this.clientSocket = clientSocket;
319 this.clientResponseWriter = new LoggingWriter(new OutputStreamWriter(clientSocket.getOutputStream()),
320 true);
321 this.clientCmdReader = new LoggingReader(new InputStreamReader(clientSocket.getInputStream()));
322 }
323
324 /***
325 * {@inheritDoc}
326 */
327 public PrintWriter getClientResponseWriter() {
328 return clientResponseWriter;
329 }
330
331 /***
332 * {@inheritDoc}
333 */
334 public BufferedReader getClientCmdReader() {
335 return clientCmdReader;
336 }
337
338 /***
339 * {@inheritDoc}
340 */
341 public int getPermission(String path) {
342 int result = PRIV_NONE;
343 try {
344 GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA);
345 result = list.getPermission(path, getUser(), options.getRootDir());
346 } catch (FtpConfigException e) {
347 log.error(e);
348 }
349 return result;
350 }
351
352 /***
353 * {@inheritDoc}
354 */
355 public UserManager getUserManager() {
356 return userManager;
357 }
358
359 /***
360 * {@inheritDoc}
361 */
362 public boolean authenticate() {
363 authenticated = false;
364 String dirName = null;
365 try {
366 authenticated = userManager.authenticate(getUser(), getPassword(), this);
367 if (authenticated) {
368 setAttribute(ATTR_LOGIN_TIME, new Date());
369 UserData userData = userManager.getUserData(getUser());
370 setAttribute(ATTR_USER_DATA, userData);
371 GroupDataList groupList = userManager.getGroupDataList(getUser());
372 setAttribute(ATTR_GROUP_DATA, groupList);
373 dirName = getStartDir();
374 File dir = new File(dirName);
375 if (!dir.exists()) {
376 FileUtils.forceMkdir(dir);
377 }
378 setRemoteDir(dirName);
379 }
380 } catch (FtpConfigException e) {
381 log.error(e);
382 } catch (IOException e) {
383 log.error("Could not create directory: " + dirName);
384 }
385 return authenticated;
386 }
387
388 private synchronized String getStartDir() throws FtpConfigException {
389 UserData userData = (UserData) getAttribute(ATTR_USER_DATA);
390 if (userData == null) {
391 throw new FtpConfigException("User data not available");
392 }
393 VarMerger varMerger = new VarMerger(userData.getDir());
394 Properties props = new Properties();
395 props.setProperty("ftproot", FilenameUtils.separatorsToUnix(options.getRootDir()));
396 props.setProperty("user", user);
397 varMerger.merge(props);
398 if (!varMerger.isReplacementComplete()) {
399 throw new FtpConfigException("Unresolved placeholders in user configuration file found.");
400 }
401 return varMerger.getText();
402 }
403
404 /***
405 * {@inheritDoc}
406 */
407 public UserData getUserData() {
408 return (UserData) getAttribute(ATTR_USER_DATA);
409 }
410
411 /***
412 * {@inheritDoc}
413 */
414 public void resetCredentials() {
415 authenticated = false;
416 setUser(null);
417 setPassword(null);
418 }
419
420 /***
421 * {@inheritDoc}
422 */
423 public void closeSockets() {
424 if (getDataSocketProvider() != null) {
425 getDataSocketProvider().closeSocket();
426 }
427 }
428
429 /***
430 * {@inheritDoc}
431 */
432 public String getCharset() {
433 String charset;
434 Boolean forceUtf8 = (Boolean) getAttribute(ATTR_FORCE_UTF8);
435 if (forceUtf8 != null && forceUtf8.booleanValue()) {
436 charset = "UTF-8";
437 } else {
438 String key = getDataType() == DT_EBCDIC ? OPT_CHARSET_EBCDIC : OPT_CHARSET_ASCII;
439 charset = getOptions().getProperty(key);
440 }
441 return charset;
442 }
443
444 /***
445 * {@inheritDoc}
446 */
447 public Integer getNextPassivePort() {
448 Integer port;
449 Integer[] allowedPorts = getOptions().getAllowedPorts();
450 if (allowedPorts == null || allowedPorts.length == 0) {
451
452
453 port = new Integer(0);
454 } else {
455
456
457 port = allowedPorts[portIdx++];
458 if (portIdx >= allowedPorts.length) {
459 portIdx = 0;
460 }
461 }
462 return port;
463
464 }
465
466 /***
467 * {@inheritDoc}
468 */
469 public Date getCreationTime() {
470 return creationTime;
471 }
472
473 /***
474 * {@inheritDoc}
475 */
476 public void setCreationTime(Date creationTime) {
477 this.creationTime = creationTime;
478 }
479
480 /***
481 * {@inheritDoc}
482 */
483 public Map<String, Long> getSessionStatistics() {
484 return sessionStatistics;
485 }
486
487 private int getUpperLimit(String globalOptionKey, String groupLimitKey) {
488 long result = -1;
489 long globalLimit = getOptions().getInt(globalOptionKey, -1);
490
491 GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA);
492 long groupLimit = list.getUpperLimit(groupLimitKey);
493
494 if (globalLimit < 0) {
495 result = groupLimit;
496 } else if (groupLimit < 0) {
497 result = globalLimit;
498 } else {
499 result = Math.max(groupLimit, globalLimit);
500 }
501
502 return (int) result;
503 }
504
505 /***
506 * {@inheritDoc}
507 */
508 public int getMaxDownloadRate() {
509 return getUpperLimit(OPT_MAX_DOWNLOAD_RATE, STAT_DOWNLOAD_RATE);
510 }
511
512 /***
513 * {@inheritDoc}
514 */
515 public int getMaxUploadRate() {
516 return getUpperLimit(OPT_MAX_UPLOAD_RATE, STAT_UPLOAD_RATE);
517 }
518
519 /***
520 * Increases a particular resource consumption by the passed value.
521 *
522 * @param countKey The name of the statistic.
523 * @param value The value
524 * @throws FtpQuotaException Thrown if a resource limit has been reached.
525 */
526 public void updateIncrementalStat(String countKey, long value) throws FtpQuotaException {
527
528
529 getUserManager().updateIncrementalStatistics(getUser(), countKey, value);
530
531
532 Map<String, Long> sessionStats = getSessionStatistics();
533 Long consumptionObj = (Long) sessionStats.get(countKey);
534 long consumption = consumptionObj == null ? 0 : consumptionObj.longValue();
535 sessionStats.put(countKey, new Long(consumption + value));
536 }
537
538 /***
539 * Updates the upload or download transfer rate taking the passed value into account.
540 *
541 * @param avgKey The name of the statistic.
542 * @param value The value
543 */
544 public void updateAverageStat(String avgKey, int value) {
545
546
547 getUserManager().updateAverageStatistics(getUser(), avgKey, value);
548
549
550 String countKey = "Sample count (" + avgKey + ")";
551 Map<String, Long> sessionStats = getSessionStatistics();
552 Long prevAvgObj = (Long) sessionStats.get(avgKey);
553 long prevAvg = prevAvgObj == null ? 0 : prevAvgObj.longValue();
554 Long prevCountObj = (Long) sessionStats.get(countKey);
555 long prevCount = prevCountObj == null ? 0 : prevCountObj.longValue();
556 long currentAvg = (prevAvg * prevCount + value) / (prevCount + 1);
557 sessionStats.put(avgKey, new Long(currentAvg));
558 sessionStats.put(countKey, new Long(prevCount + 1));
559 }
560
561 }