001: /*
002: * This file is part of DrFTPD, Distributed FTP Daemon.
003: *
004: * DrFTPD is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * DrFTPD is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with DrFTPD; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018: package net.sf.drftpd.master.config;
019:
020: import net.sf.drftpd.util.PortRange;
021:
022: import org.apache.log4j.Logger;
023:
024: import org.apache.oro.text.GlobCompiler;
025: import org.apache.oro.text.regex.MalformedPatternException;
026:
027: import org.drftpd.GlobalContext;
028: import org.drftpd.commands.Reply;
029: import org.drftpd.commands.UserManagement;
030:
031: import org.drftpd.permissions.GlobPathPermission;
032: import org.drftpd.permissions.MessagePathPermission;
033: import org.drftpd.permissions.PathPermission;
034: import org.drftpd.permissions.PatternPathPermission;
035: import org.drftpd.permissions.Permission;
036: import org.drftpd.permissions.RatioPathPermission;
037: import org.drftpd.remotefile.LinkedRemoteFileInterface;
038: import org.drftpd.slave.Slave;
039:
040: import org.drftpd.usermanager.User;
041:
042: import com.Ostermiller.util.StringTokenizer;
043:
044: import java.io.FileInputStream;
045: import java.io.FileReader;
046: import java.io.IOException;
047: import java.io.LineNumberReader;
048: import java.io.Reader;
049:
050: import java.net.InetAddress;
051: import java.net.UnknownHostException;
052:
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.Enumeration;
056: import java.util.Hashtable;
057: import java.util.Iterator;
058: import java.util.List;
059: import java.util.Observable;
060: import java.util.Properties;
061:
062: /**
063: * @author mog
064: * @version $Id: FtpConfig.java 1593 2007-01-31 22:56:52Z zubov $
065: */
066: public class FtpConfig extends Observable implements ConfigInterface {
067: private static final Logger logger = Logger
068: .getLogger(FtpConfig.class);
069: private ArrayList<InetAddress> _bouncerIps;
070: private boolean _capFirstDir;
071: private boolean _capFirstFile;
072: private ArrayList<RatioPathPermission> _creditcheck = new ArrayList<RatioPathPermission>();
073: private ArrayList<RatioPathPermission> _creditloss = new ArrayList<RatioPathPermission>();
074: private boolean _hideIps;
075: private boolean _isLowerDir;
076: private boolean _isLowerFile;
077: private String _loginPrompt = Slave.VERSION + " http://drftpd.org";
078: private int _maxUsersExempt;
079: private int _maxUsersTotal = Integer.MAX_VALUE;
080: private ArrayList<MessagePathPermission> _msgpath = new ArrayList<MessagePathPermission>();
081: private String _pasv_addr;
082: private Hashtable<String, ArrayList<PathPermission>> _pathsPerms = new Hashtable<String, ArrayList<PathPermission>>();
083: private Hashtable<String, Permission> _permissions = new Hashtable<String, Permission>();
084: private StringTokenizer _replaceDir = null;
085: private StringTokenizer _replaceFile = null;
086: private boolean _useDirNames = false;
087: private boolean _useFileNames = false;
088: private String newConf = "conf/perms.conf";
089: protected PortRange _portRange;
090: private Permission _shutdown;
091: protected GlobalContext _gctx;
092:
093: protected FtpConfig() {
094: }
095:
096: /**
097: * Constructor that allows reusing of cfg object
098: */
099: public FtpConfig(Properties cfg, String cfgFileName,
100: GlobalContext gctx) throws IOException {
101: loadConfig(cfg, gctx);
102: }
103:
104: public FtpConfig(String cfgFileName, GlobalContext gctx)
105: throws IOException {
106: Properties cfg = new Properties();
107: FileInputStream stream = null;
108: try {
109: stream = new FileInputStream(cfgFileName);
110: cfg.load(stream);
111: loadConfig(cfg, gctx);
112: } finally {
113: if (stream != null) {
114: stream.close();
115: }
116: }
117: }
118:
119: private static ArrayList makeRatioPermission(
120: ArrayList<RatioPathPermission> arr, StringTokenizer st)
121: throws MalformedPatternException {
122: arr.add(new RatioPathPermission(new GlobCompiler().compile(st
123: .nextToken()), Float.parseFloat(st.nextToken()),
124: makeUsers(st)));
125:
126: return arr;
127: }
128:
129: public static ArrayList<String> makeUsers(Enumeration st) {
130: ArrayList<String> users = new ArrayList<String>();
131:
132: while (st.hasMoreElements()) {
133: users.add((String) st.nextElement());
134: }
135:
136: return users;
137: }
138:
139: public boolean checkPathPermission(String key, User fromUser,
140: LinkedRemoteFileInterface path) {
141: return checkPathPermission(key, fromUser, path, false);
142: }
143:
144: public boolean checkPathPermission(String key, User fromUser,
145: LinkedRemoteFileInterface path, boolean defaults) {
146: Collection coll = ((Collection) _pathsPerms.get(key));
147:
148: if (coll == null) {
149: return defaults;
150: }
151:
152: Iterator iter = coll.iterator();
153:
154: while (iter.hasNext()) {
155: PathPermission perm = (PathPermission) iter.next();
156:
157: if (perm.checkPath(path)) {
158: return perm.check(fromUser);
159: }
160: }
161:
162: return defaults;
163: }
164:
165: public boolean checkPermission(String key, User user) {
166: Permission perm = _permissions.get(key);
167:
168: return (perm == null) ? false : perm.check(user);
169: }
170:
171: public void directoryMessage(Reply response, User user,
172: LinkedRemoteFileInterface dir) {
173: for (Iterator iter = _msgpath.iterator(); iter.hasNext();) {
174: MessagePathPermission perm = (MessagePathPermission) iter
175: .next();
176:
177: if (perm.checkPath(dir)) {
178: if (perm.check(user)) {
179: perm.printMessage(response);
180: }
181: }
182: }
183: }
184:
185: /**
186: * @return Returns the bouncerIp.
187: */
188: public List getBouncerIps() {
189: return _bouncerIps;
190: }
191:
192: public float getCreditCheckRatio(LinkedRemoteFileInterface path,
193: User fromUser) {
194: return this .getCreditCheckRatio(path.getPath(), fromUser);
195: }
196:
197: public float getCreditCheckRatio(String path, User fromUser) {
198: for (Iterator iter = _creditcheck.iterator(); iter.hasNext();) {
199: RatioPathPermission perm = (RatioPathPermission) iter
200: .next();
201:
202: if (perm.checkPath(path)) {
203: if (perm.check(fromUser)) {
204: return perm.getRatio();
205: }
206:
207: return fromUser.getKeyedMap().getObjectFloat(
208: UserManagement.RATIO);
209: }
210: }
211:
212: return fromUser.getKeyedMap().getObjectFloat(
213: UserManagement.RATIO);
214: }
215:
216: public float getCreditLossRatio(LinkedRemoteFileInterface path,
217: User fromUser) {
218: for (Iterator iter = _creditloss.iterator(); iter.hasNext();) {
219: RatioPathPermission perm = (RatioPathPermission) iter
220: .next();
221:
222: if (perm.checkPath(path)) {
223: if (perm.check(fromUser)) {
224: return perm.getRatio();
225: }
226: }
227: }
228:
229: //default credit loss ratio is 1
230: return (fromUser.getKeyedMap().getObjectFloat(
231: UserManagement.RATIO) == 0) ? 0 : 1;
232: }
233:
234: public String getDirName(String name) {
235: if (!_useDirNames) {
236: return name;
237: }
238:
239: String temp = null;
240:
241: if (_isLowerDir) {
242: temp = name.toLowerCase();
243: } else {
244: temp = name.toUpperCase();
245: }
246:
247: if (_capFirstDir) {
248: temp = temp.substring(0, 1).toUpperCase()
249: + temp.substring(1, temp.length());
250: }
251:
252: return replaceName(temp, _replaceDir);
253: }
254:
255: public String getFileName(String name) {
256: if (!_useFileNames) {
257: return name;
258: }
259:
260: String temp = name;
261:
262: if (_isLowerFile) {
263: temp = temp.toLowerCase();
264: } else {
265: temp = temp.toUpperCase();
266: }
267:
268: if (_capFirstFile) {
269: temp = temp.substring(0, 1).toUpperCase()
270: + temp.substring(1, temp.length());
271: }
272:
273: return replaceName(temp, _replaceFile);
274: }
275:
276: public GlobalContext getGlobalContext() {
277: assert _gctx != null;
278: return _gctx;
279: }
280:
281: public boolean getHideIps() {
282: return _hideIps;
283: }
284:
285: public String getLoginPrompt() {
286: return _loginPrompt;
287: }
288:
289: public int getMaxUsersExempt() {
290: return _maxUsersExempt;
291: }
292:
293: public int getMaxUsersTotal() {
294: return _maxUsersTotal;
295: }
296:
297: public String getPasvAddress() throws NullPointerException {
298: if (_pasv_addr == null)
299: throw new NullPointerException();
300: return _pasv_addr;
301: }
302:
303: public void loadConfig(Properties cfg, GlobalContext gctx)
304: throws IOException {
305: loadConfig2(new FileReader(newConf));
306: if (_portRange == null) {
307: //default portrange if none specified
308: _portRange = new PortRange(0);
309: }
310: _gctx = gctx;
311: loadConfig1(cfg);
312: }
313:
314: protected void loadConfig1(Properties cfg)
315: throws UnknownHostException {
316:
317: _hideIps = cfg.getProperty("hideips", "").equalsIgnoreCase(
318: "true");
319:
320: StringTokenizer st = new StringTokenizer(cfg.getProperty(
321: "bouncer_ip", ""), " ");
322:
323: ArrayList<InetAddress> bouncerIps = new ArrayList<InetAddress>();
324:
325: while (st.hasMoreTokens()) {
326: bouncerIps.add(InetAddress.getByName(st.nextToken()));
327: }
328:
329: _bouncerIps = bouncerIps;
330: }
331:
332: protected void loadConfig2(Reader in2) throws IOException {
333: LineNumberReader in = new LineNumberReader(in2);
334:
335: try {
336: String line;
337:
338: while ((line = in.readLine()) != null) {
339: StringTokenizer st = new StringTokenizer(line);
340:
341: if (!st.hasMoreTokens()) {
342: continue;
343: }
344:
345: String cmd = st.nextToken();
346:
347: try {
348: // login_prompt <string>
349: if (cmd.equals("login_prompt")) {
350: _loginPrompt = line.substring(13);
351: }
352: //max_users <maxUsersTotal> <maxUsersExempt>
353: else if (cmd.equals("max_users")) {
354: _maxUsersTotal = Integer.parseInt(st
355: .nextToken());
356: _maxUsersExempt = Integer.parseInt(st
357: .nextToken());
358: } else if (cmd.equals("pasv_addr")) {
359: _pasv_addr = st.nextToken();
360: } else if (cmd.equals("pasv_ports")) {
361: String[] temp = st.nextToken().split("-");
362: _portRange = new PortRange(Integer
363: .parseInt(temp[0]), Integer
364: .parseInt(temp[1]), 0);
365: } else if (cmd.equals("dir_names")) {
366: _useDirNames = true;
367: _capFirstDir = st.nextToken().equals("true");
368: _isLowerDir = st.nextToken().equals("lower");
369: _replaceDir = st;
370: } else if (cmd.equals("file_names")) {
371: _useFileNames = true;
372: _capFirstFile = st.nextToken().equals("true");
373: _isLowerFile = st.nextToken().equals("lower");
374: _replaceFile = st;
375: }
376: //msgpath <path> <filename> <flag/=group/-user>
377: else if (cmd.equals("msgpath")) {
378: String path = st.nextToken();
379: String messageFile = st.nextToken();
380: _msgpath.add(new MessagePathPermission(path,
381: messageFile, makeUsers(st)));
382: }
383: //creditloss <path> <multiplier> [<-user|=group|flag> ...]
384: else if (cmd.equals("creditloss")) {
385: makeRatioPermission(_creditloss, st);
386: }
387: //creditcheck <path> <ratio> [<-user|=group|flag> ...]
388: else if (cmd.equals("creditcheck")) {
389: makeRatioPermission(_creditcheck, st);
390: } else if (cmd.equals("pathperm")) {
391: addGlobPathPermission(st.nextToken(), st);
392:
393: } else if (cmd.equals("privpath")
394: || cmd.equals("dirlog")
395: || cmd.equals("hideinwho")
396: || cmd.equals("makedir")
397: || cmd.equals("pre")
398: || cmd.equals("upload")
399: || cmd.equals("download")
400: || cmd.equals("delete")
401: || cmd.equals("deleteown")
402: || cmd.equals("rename")
403: || cmd.equals("nostatsup")
404: || cmd.equals("nostatsdn")
405: || cmd.equals("renameown")
406: || cmd.equals("request")) {
407: addGlobPathPermission(cmd, st);
408: } else if (cmd.equals("makedir2")) {
409: addPatternPathPermission(cmd.substring(0, cmd
410: .length() - 1), st);
411: } else if ("userrejectsecure".equals(cmd)
412: || "userrejectinsecure".equals(cmd)
413: || "denydiruncrypted".equals(cmd)
414: || "denydatauncrypted".equals(cmd)
415: || "give".equals(cmd) || "take".equals(cmd)) {
416: if (_permissions.containsKey(cmd)) {
417: throw new RuntimeException(
418: "Duplicate key in perms.conf: "
419: + cmd + " line: "
420: + in.getLineNumber());
421: }
422:
423: _permissions.put(cmd, new Permission(
424: makeUsers(st)));
425: } else if ("shutdown".equals(cmd)) {
426: _shutdown = new Permission(makeUsers(st));
427: } else {
428: if (!cmd.startsWith("#")) {
429: addGlobPathPermission(cmd, st);
430: }
431: }
432: } catch (Exception e) {
433: logger.warn("Exception when reading " + newConf
434: + " line " + in.getLineNumber(), e);
435: }
436: }
437:
438: //notify any observers so they can add their own permissions
439: notifyObservers();
440: } finally {
441: in.close();
442: }
443: }
444:
445: private void addGlobPathPermission(String key, StringTokenizer st)
446: throws MalformedPatternException {
447: addPathPermission(key, new GlobPathPermission(
448: new GlobCompiler().compile(st.nextToken()),
449: makeUsers(st)));
450: }
451:
452: private void addPatternPathPermission(String key, StringTokenizer st) {
453: addPathPermission(key, new PatternPathPermission(
454: st.nextToken(), makeUsers(st)));
455: }
456:
457: public void addPathPermission(String key, PathPermission permission) {
458: ArrayList<PathPermission> perms = _pathsPerms.get(key);
459:
460: if (perms == null) {
461: perms = new ArrayList<PathPermission>();
462: _pathsPerms.put(key, perms);
463: }
464: perms.add(permission);
465: }
466:
467: private static void replaceChars(StringBuffer source,
468: Character oldChar, Character newChar) {
469: if (newChar == null) {
470: int x = 0;
471:
472: while (x < source.length()) {
473: if (source.charAt(x) == oldChar.charValue()) {
474: source.deleteCharAt(x);
475: } else {
476: x++;
477: }
478: }
479: } else {
480: int x = 0;
481:
482: while (x < source.length()) {
483: if (source.charAt(x) == oldChar.charValue()) {
484: source.setCharAt(x, newChar.charValue());
485: }
486:
487: x++;
488: }
489: }
490: }
491:
492: private static String replaceName(String source, StringTokenizer st) {
493: StringBuffer sb = new StringBuffer(source);
494: Character oldChar = null;
495: Character newChar = null;
496:
497: while (true) {
498: if (!st.hasMoreTokens()) {
499: return sb.toString();
500: }
501:
502: String nextToken = st.nextToken();
503:
504: if (nextToken.length() == 1) {
505: oldChar = new Character(nextToken.charAt(0));
506: newChar = null;
507: } else {
508: oldChar = new Character(nextToken.charAt(0));
509: newChar = new Character(nextToken.charAt(1));
510: }
511:
512: replaceChars(sb, oldChar, newChar);
513: }
514: }
515:
516: /**
517: * Returns true if user is allowed into a shutdown server.
518: */
519: public boolean isLoginAllowed(User user) {
520: return (_shutdown == null) ? true : _shutdown.check(user);
521: }
522:
523: public PortRange getPortRange() {
524: return _portRange;
525: }
526: }
|