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 org.drftpd.plugins;
019:
020: import java.io.FileInputStream;
021: import java.io.FileNotFoundException;
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.Calendar;
025: import java.util.Date;
026: import java.util.Iterator;
027: import java.util.Properties;
028:
029: import net.sf.drftpd.event.Event;
030: import net.sf.drftpd.event.FtpListener;
031: import net.sf.drftpd.event.UserEvent;
032: import net.sf.drftpd.master.config.FtpConfig;
033: import net.sf.drftpd.util.CalendarUtils;
034:
035: import org.apache.log4j.Level;
036: import org.apache.log4j.Logger;
037: import org.drftpd.Bytes;
038: import org.drftpd.GlobalContext;
039: import org.drftpd.PropertyHelper;
040: import org.drftpd.commands.UserManagement;
041: import org.drftpd.dynamicdata.KeyNotFoundException;
042: import org.drftpd.permissions.Permission;
043: import org.drftpd.sitebot.Trials;
044: import org.drftpd.usermanager.User;
045:
046: import com.Ostermiller.util.StringTokenizer;
047:
048: /**
049: * @author mog
050: * @version $Id: Trial.java 1513 2006-10-13 22:41:08Z tdsoul $
051: */
052: public class Trial extends FtpListener {
053: private static final Logger logger = Logger.getLogger(Trial.class);
054: public static final int PERIOD_ALL = 0;
055: public static final int PERIOD_DAILY = Calendar.DAY_OF_MONTH; // = 5
056: public static final short PERIOD_MONTHLY = Calendar.MONTH; // = 2
057: public static final short PERIOD_WEEKLY = Calendar.WEEK_OF_YEAR; // = 3
058: private ArrayList _limits;
059: private Trials _siteBot;
060:
061: public Trial() throws FileNotFoundException, IOException {
062: super ();
063: }
064:
065: public static void doAction(String action, User user) {
066: try {
067: if (action == null) {
068: return;
069: }
070:
071: StringTokenizer st = new StringTokenizer(action);
072:
073: if (!st.hasMoreTokens()) {
074: logger.info(user.getName() + " no action specified");
075:
076: return;
077: }
078:
079: String cmd = st.nextToken().toLowerCase();
080:
081: if ("chgrp".equals(cmd)) {
082: while (st.hasMoreTokens()) {
083: user.toggleGroup(st.nextToken());
084: }
085: } else if ("setgrp".equals(cmd)) {
086: user.setGroup(st.nextToken());
087: logger.info(user.getName() + " primary group set to "
088: + user.getGroup());
089: } else if ("delete".equals(cmd)) {
090: user.setDeleted(true);
091: logger.info(user.getName() + " deleted");
092: } else if ("purge".equals(cmd)) {
093: user.setDeleted(true);
094: user.purge();
095: logger.info(user.getName() + " purged");
096: }
097: } catch (java.util.NoSuchElementException e) {
098: logger.info("Error parsing \"" + action + "\"", e);
099: }
100: }
101:
102: public static Calendar getCalendarForEndOfBonus(User user,
103: int period) {
104: Calendar cal = Calendar.getInstance();
105: try {
106: cal.setTime((Date) user.getKeyedMap().getObject(
107: UserManagement.CREATED));
108: } catch (KeyNotFoundException e) {
109: throw new IllegalArgumentException("User has no created");
110: }
111: moveCalendarToEndOfPeriod(cal, period);
112:
113: return cal;
114: }
115:
116: /**
117: * Returns last day of the first, unique, period.
118: */
119: public static Calendar getCalendarForEndOfFirstPeriod(User user,
120: int period) {
121: Calendar cal = Calendar.getInstance();
122: try {
123: cal.setTime((Date) user.getKeyedMap().getObject(
124: UserManagement.CREATED));
125: } catch (KeyNotFoundException e) {
126: throw new IllegalArgumentException(
127: "User has no created info");
128: }
129: CalendarUtils.ceilAllLessThanDay(cal);
130:
131: switch (period) {
132: case PERIOD_DAILY:
133:
134: //user added on monday @ 15:00, trial ends on tuesday with reset at 23:59
135: //bonus ends at tuesday 23:59
136: CalendarUtils.incrementDay(cal);
137:
138: return cal;
139:
140: case PERIOD_WEEKLY:
141:
142: //user added on week 1, wednsday @ 15:00, trial
143: //trial ends with a reset at week 2, wednsday 24:00
144: //bonus ends on week 3, monday 00:00
145: CalendarUtils.incrementWeek(cal);
146:
147: return cal;
148:
149: case PERIOD_MONTHLY:
150:
151: //user added on january 31 15:00
152: //trial ends on feb 28 24:00
153: //bonus ends on mar 1 00:00
154: CalendarUtils.incrementMonth(cal);
155:
156: return cal;
157:
158: default:
159: throw new IllegalArgumentException(
160: "Don't know how to handle " + period);
161: }
162: }
163:
164: public static Calendar getCalendarForEndOfPeriod(int period) {
165: Calendar cal = Calendar.getInstance();
166: CalendarUtils.ceilAllLessThanDay(cal);
167:
168: switch (period) {
169: case PERIOD_DAILY:
170: break;
171:
172: case PERIOD_WEEKLY:
173:
174: int dow = CalendarUtils.getLastDayOfWeek(cal);
175:
176: //dow is less than current day of week, increment week
177: if (dow < cal.get(Calendar.DAY_OF_WEEK)) {
178: cal.add(Calendar.WEEK_OF_YEAR, 1);
179: }
180:
181: cal.set(Calendar.DAY_OF_WEEK, dow);
182:
183: return cal;
184:
185: case PERIOD_MONTHLY:
186: cal.set(Calendar.DAY_OF_MONTH, cal
187: .getActualMaximum(Calendar.DAY_OF_MONTH));
188:
189: return cal;
190:
191: default:
192: throw new IllegalArgumentException("" + period);
193: }
194:
195: //moveCalendarToEndOfPeriod(cal, period);
196: return cal;
197: }
198:
199: public static String getPeriodName(int s) {
200: switch (s) {
201: case PERIOD_DAILY:
202: return "day";
203:
204: case PERIOD_MONTHLY:
205: return "month";
206:
207: case PERIOD_WEEKLY:
208: return "week";
209:
210: default:
211: throw new IllegalArgumentException("" + s);
212: }
213: }
214:
215: public static String getPeriodName2(int s) {
216: switch (s) {
217: case PERIOD_DAILY:
218: return "daily";
219:
220: case PERIOD_MONTHLY:
221: return "monthly";
222:
223: case PERIOD_WEEKLY:
224: return "weekly";
225:
226: default:
227: throw new IllegalArgumentException("" + s);
228: }
229: }
230:
231: public static long getUploadedBytesForPeriod(User user, int period) {
232: if (isInFirstPeriod(user, period)) {
233: return user.getDownloadedBytes();
234: }
235:
236: switch (period) {
237: case PERIOD_DAILY:
238: return user.getUploadedBytesDay();
239:
240: case PERIOD_WEEKLY:
241: return user.getUploadedBytesWeek();
242:
243: case PERIOD_MONTHLY:
244: return user.getUploadedBytesMonth();
245:
246: default:
247: throw new IllegalArgumentException();
248: }
249: }
250:
251: public static boolean isInFirstPeriod(User user, int period) {
252: return isInFirstPeriod(user, period, System.currentTimeMillis());
253: }
254:
255: public static boolean isInFirstPeriod(User user, int period,
256: long time) {
257: return time <= getCalendarForEndOfBonus(user, period)
258: .getTimeInMillis();
259: }
260:
261: public static Calendar moveCalendarToEndOfPeriod(Calendar cal,
262: int period) {
263: CalendarUtils.ceilAllLessThanDay(cal);
264:
265: switch (period) {
266: case PERIOD_DAILY:
267:
268: //CalendarUtils.incrementDay(cal);
269: return cal;
270:
271: case PERIOD_WEEKLY:
272: CalendarUtils.incrementWeek(cal);
273:
274: return cal;
275:
276: case PERIOD_MONTHLY:
277: CalendarUtils.incrementMonth(cal);
278:
279: return cal;
280:
281: default:
282: throw new IllegalArgumentException("" + period);
283: }
284: }
285:
286: public void actionPerformed(Event event) {
287: String cmd = event.getCommand();
288: if (cmd.equals("RELOAD")) {
289: try {
290: reload();
291: } catch (IOException e) {
292: logger.log(Level.WARN, "", e);
293: }
294: }
295: if (!(event instanceof UserEvent)) {
296: return;
297: }
298:
299: UserEvent uevent = (UserEvent) event;
300:
301: //logger.debug("event.getTime(): " + new Date(event.getTime()));
302: //logger.debug(
303: // "uevent.getUser().getLastReset(): "
304: // + new Date(uevent.getUser().getLastReset()));
305: if ("RESETDAY".equals(cmd)) {
306: Calendar cal;
307:
308: //MONTH UNIQUE //
309: cal = getCalendarForEndOfFirstPeriod(uevent.getUser(),
310: PERIOD_MONTHLY);
311:
312: //logger.debug("end of first, montly, period: " + cal.getTime());
313: //last reset before unique period and event time equals or bigger than unique period
314: if ((uevent.getUser().getKeyedMap().getObjectDate(
315: UserManagement.LASTSEEN).getTime() <= cal
316: .getTimeInMillis())
317: && (uevent.getTime() >= cal.getTimeInMillis())) {
318: checkPassed(uevent.getUser(), uevent.getUser()
319: .getUploadedBytes(), PERIOD_MONTHLY);
320: }
321:
322: // WEEK UNIQUE //
323: // if less than month unique period
324: if (uevent.getTime() < cal.getTimeInMillis()) {
325: cal = getCalendarForEndOfFirstPeriod(uevent.getUser(),
326: PERIOD_WEEKLY);
327:
328: //logger.debug("end of first, weekly, period: " + cal.getTime());
329: //last reset before unique period and event time equals or bigger than unique period
330: if ((uevent.getUser().getKeyedMap().getObjectDate(
331: UserManagement.LASTSEEN).getTime() <= cal
332: .getTimeInMillis())
333: && (uevent.getTime() >= cal.getTimeInMillis())) {
334: checkPassed(uevent.getUser(), uevent.getUser()
335: .getUploadedBytes(), PERIOD_WEEKLY);
336: }
337: }
338:
339: // DAY UNIQUE //
340: //if event lesss than week unique period (cal)
341: if (uevent.getTime() < cal.getTimeInMillis()) {
342: cal = getCalendarForEndOfFirstPeriod(uevent.getUser(),
343: PERIOD_DAILY);
344:
345: //logger.debug("end of first day period: " + cal.getTime());
346: //is day unique period
347: if ((uevent.getUser().getKeyedMap().getObjectDate(
348: UserManagement.LASTSEEN).getTime() <= cal
349: .getTimeInMillis())
350: && (uevent.getTime() >= cal.getTimeInMillis())) {
351: checkPassed(uevent.getUser(), uevent.getUser()
352: .getUploadedBytes(), PERIOD_DAILY);
353:
354: //after day unique period
355: } else if (uevent.getTime() > cal.getTimeInMillis()) {
356: checkPassed(uevent.getUser(), uevent.getUser()
357: .getUploadedBytesDay(), PERIOD_DAILY);
358: }
359: } else {
360: //always check if after
361: checkPassed(uevent.getUser(), uevent.getUser()
362: .getUploadedBytesDay(), PERIOD_DAILY);
363: }
364: }
365:
366: if ("RESETWEEK".equals(cmd)) {
367: if (!isInFirstPeriod(uevent.getUser(), PERIOD_WEEKLY,
368: uevent.getTime())) {
369: checkPassed(uevent.getUser(), uevent.getUser()
370: .getUploadedBytesWeek(), PERIOD_WEEKLY);
371: }
372: }
373:
374: if ("RESETMONTH".equals(cmd)) {
375: if (!isInFirstPeriod(uevent.getUser(), PERIOD_MONTHLY,
376: uevent.getTime())) {
377: checkPassed(uevent.getUser(), uevent.getUser()
378: .getUploadedBytesMonth(), PERIOD_MONTHLY);
379: }
380: }
381: }
382:
383: private void checkPassed(User user, long bytes, int period) {
384: for (Iterator iter = _limits.iterator(); iter.hasNext();) {
385: Limit limit = (Limit) iter.next();
386:
387: if ((limit.getPeriod() == period)
388: && limit.getPerm().check(user)) {
389: long bytesleft = limit.getBytes() - bytes;
390:
391: if (bytesleft > 0) {
392: logger.info(user.getName() + " failed "
393: + limit.getName() + " by "
394: + Bytes.formatBytes(bytesleft));
395: limit.doFailed(user);
396: } else {
397: logger.info(user.getName() + " passed "
398: + limit.getName() + " with "
399: + Bytes.formatBytes(-bytesleft) + " extra");
400: limit.doPassed(user);
401: }
402: }
403: }
404: }
405:
406: public ArrayList getLimits() {
407: return _limits;
408: }
409:
410: public void init(GlobalContext gctx) {
411: super .init(gctx);
412: try {
413: reload();
414: } catch (Exception e) {
415: throw new RuntimeException(e);
416: }
417: }
418:
419: private void reload() throws IOException {
420: Properties props = new Properties();
421: FileInputStream fis = null;
422: try {
423: fis = new FileInputStream("conf/trial.conf");
424: props.load(fis);
425: } finally {
426: if (fis != null) {
427: fis.close();
428: }
429: }
430: reload(props);
431: }
432:
433: public void reload(ArrayList limits) {
434: _limits = limits;
435: }
436:
437: protected void reload(Properties props) {
438: ArrayList<Limit> limits = new ArrayList<Limit>();
439:
440: for (int i = 1;; i++) {
441: if (props.getProperty(i + ".quota") == null) {
442: break;
443: }
444:
445: Limit limit = new Limit();
446: limit.setName(PropertyHelper
447: .getProperty(props, i + ".name"));
448: limit.setActionPassed(props.getProperty(i + ".pass", props
449: .getProperty(i + ".passed", "")));
450: limit.setActionFailed(props.getProperty(i + ".fail", ""));
451:
452: if (limit.getActionFailed().equals("")
453: && limit.getActionPassed().equals("")) {
454: throw new IllegalArgumentException(
455: "Both .pass and .fail cannot be empty for " + i
456: + " (" + limit.getName() + ")");
457: }
458:
459: String period = PropertyHelper.getProperty(props,
460: i + ".period").toLowerCase();
461:
462: if ("monthly".equals(period)) {
463: limit.setPeriod(PERIOD_MONTHLY);
464: } else if ("weekly".equals(period)) {
465: limit.setPeriod(PERIOD_WEEKLY);
466: } else if ("daily".equals(period)) {
467: limit.setPeriod(PERIOD_DAILY);
468: } else {
469: throw new RuntimeException(new IOException(period
470: + " is not a recognized period"));
471: }
472:
473: String perm = props.getProperty(i + ".perm");
474:
475: if (perm == null) {
476: perm = "*";
477: }
478:
479: limit.setPerm(new Permission(FtpConfig
480: .makeUsers(new StringTokenizer(perm))));
481: limit.setBytes(Bytes.parseBytes(PropertyHelper.getProperty(
482: props, i + ".quota")));
483: limits.add(limit);
484: logger.debug("Limit: " + limit);
485: }
486:
487: reload(limits);
488: }
489:
490: public void unload() {
491: }
492:
493: public static class Limit {
494: private String _actionFailed;
495: private String _actionPassed;
496: private long _bytes;
497: private String _name;
498: private int _period;
499: private Permission _perm;
500:
501: public Limit() {
502: }
503:
504: public void doFailed(User user) {
505: Trial.doAction(getActionFailed(), user);
506: }
507:
508: public void doPassed(User user) {
509: Trial.doAction(getActionPassed(), user);
510: }
511:
512: public String getActionFailed() {
513: return _actionFailed;
514: }
515:
516: public String getActionPassed() {
517: return _actionPassed;
518: }
519:
520: public long getBytes() {
521: return _bytes;
522: }
523:
524: public String getName() {
525: return _name;
526: }
527:
528: public int getPeriod() {
529: return _period;
530: }
531:
532: public Permission getPerm() {
533: return _perm;
534: }
535:
536: public void setActionFailed(String action) {
537: validateAction(action);
538: _actionFailed = action;
539: }
540:
541: public void setActionPassed(String action) {
542: validateAction(action);
543: _actionPassed = action;
544: }
545:
546: public void setBytes(long bytes) {
547: _bytes = bytes;
548: }
549:
550: public void setName(String name) {
551: _name = name;
552: }
553:
554: public void setPeriod(int period) {
555: _period = period;
556: }
557:
558: public void setPerm(Permission perm) {
559: _perm = perm;
560: }
561:
562: public String toString() {
563: return "Limit[name=" + _name + ",bytes="
564: + Bytes.formatBytes(_bytes) + ",period="
565: + Trial.getPeriodName(_period) + "]";
566: }
567:
568: private void validateAction(String action) {
569: if (action == null) {
570: return;
571: }
572:
573: StringTokenizer st = new StringTokenizer(action);
574:
575: if (!st.hasMoreTokens()) {
576: return;
577: }
578:
579: String cmd = st.nextToken();
580:
581: if (!("delete".equals(action) || "purge".equals(action)
582: || "chgrp".equals(cmd) || "setgrp".equals(cmd))) {
583: throw new IllegalArgumentException(cmd
584: + " is not a valid action");
585: }
586:
587: if ("setgrp".equals(cmd)) {
588: st.nextToken();
589:
590: if (st.hasMoreTokens()) {
591: throw new IllegalArgumentException(
592: "extra tokens in \"" + action + "\"");
593: }
594: }
595: }
596: }
597: }
|