View Javadoc

1   /*
2    * ------------------------------------------------------------------------------
3    * Hermes FTP Server
4    * Copyright (c) 2005-2007 Lars Behnke
5    * ------------------------------------------------------------------------------
6    * 
7    * This file is part of Hermes FTP Server.
8    * 
9    * Hermes FTP Server is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   * 
14   * Hermes FTP Server is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public License
20   * along with Hermes FTP Server; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
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             /* Let the system decide which port to use. */
453             port = new Integer(0);
454         } else {
455 
456             /* Get the port from the user defined list. */
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         /* All sessions of user */
529         getUserManager().updateIncrementalStatistics(getUser(), countKey, value);
530 
531         /* Current session */
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         /* All sessions of user */
547         getUserManager().updateAverageStatistics(getUser(), avgKey, value);
548 
549         /* Current session */
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 }