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.commands;
019:
020: import net.sf.drftpd.FileExistsException;
021: import net.sf.drftpd.Nukee;
022: import net.sf.drftpd.ObjectNotFoundException;
023: import net.sf.drftpd.SlaveUnavailableException;
024: import net.sf.drftpd.event.NukeEvent;
025: import net.sf.drftpd.master.BaseFtpConnection;
026: import net.sf.drftpd.master.command.CommandManager;
027: import net.sf.drftpd.master.command.CommandManagerFactory;
028: import net.sf.drftpd.master.queues.NukeLog;
029:
030: import org.apache.log4j.Level;
031: import org.apache.log4j.Logger;
032:
033: import org.drftpd.Bytes;
034: import org.drftpd.dynamicdata.Key;
035:
036: import org.drftpd.master.RemoteSlave;
037: import org.drftpd.master.RemoteTransfer;
038: import org.drftpd.remotefile.LinkedRemoteFileInterface;
039: import org.drftpd.usermanager.AbstractUser;
040: import org.drftpd.usermanager.NoSuchUserException;
041: import org.drftpd.usermanager.User;
042: import org.drftpd.usermanager.UserFileException;
043:
044: import org.jdom.Document;
045: import org.jdom.Element;
046:
047: import org.jdom.input.SAXBuilder;
048:
049: import java.io.FileNotFoundException;
050: import java.io.FileReader;
051: import java.io.IOException;
052:
053: import java.util.HashMap;
054: import java.util.Hashtable;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.Map;
058: import java.util.StringTokenizer;
059:
060: /**
061: * nukedamount -> amount after multiplier
062: * amount -> amount before multiplier
063: *
064: * @author mog
065: * @version $Id: Nuke.java 1533 2006-11-17 20:23:09Z dark $
066: */
067: public class Nuke implements CommandHandler, CommandHandlerFactory {
068: public static final Key NUKED = new Key(Nuke.class, "nuked",
069: Integer.class);
070: public static final Key NUKEDBYTES = new Key(Nuke.class,
071: "nukedBytes", Long.class);
072: private static final Logger logger = Logger.getLogger(Nuke.class);
073: public static final Key LASTNUKED = new Key(Nuke.class,
074: "lastNuked", Long.class);
075: private static NukeLog _nukelog;
076:
077: public Nuke() {
078: }
079:
080: public static long calculateNukedAmount(long size, float ratio,
081: int multiplier) {
082: if (size != 0 && ratio != 0 && multiplier != 0) {
083: return (long) ((size * ratio) + (size * (multiplier - 1)));
084: } else {
085:
086: /* If size is 0 then there are no nuked bytes. If ratio is 0
087: * then this user should not lose credits because they do not earn
088: * credits by uploading files. If multiplier is 0 then this user
089: * is exempt from losing credits due to this nuke.
090: */
091:
092: return 0L;
093: }
094: }
095:
096: public static void nukeRemoveCredits(
097: LinkedRemoteFileInterface nukeDir,
098: Hashtable<String, Long> nukees) {
099: for (Iterator iter = nukeDir.getFiles().iterator(); iter
100: .hasNext();) {
101: LinkedRemoteFileInterface file = (LinkedRemoteFileInterface) iter
102: .next();
103:
104: if (file.isDirectory()) {
105: nukeRemoveCredits(file, nukees);
106: }
107:
108: if (file.isFile()) {
109: String owner = file.getUsername();
110: Long total = (Long) nukees.get(owner);
111:
112: if (total == null) {
113: total = new Long(0);
114: }
115:
116: total = new Long(total.longValue() + file.length());
117: nukees.put(owner, total);
118: }
119: }
120: }
121:
122: /**
123: * USAGE: site nuke <directory> <multiplier> <message>
124: * Nuke a directory
125: *
126: * ex. site nuke shit 2 CRAP
127: *
128: * This will nuke the directory 'shit' and remove x2 credits with the
129: * comment 'CRAP'.
130: *
131: * NOTE: You can enclose the directory in braces if you have spaces in the name
132: * ex. site NUKE {My directory name} 1 because_i_dont_like_it
133: *
134: * Q) What does the multiplier in 'site nuke' do?
135: * A) Multiplier is a penalty measure. If it is 0, the user doesn't lose any
136: * credits for the stuff being nuked. If it is 1, user only loses the
137: * amount of credits he gained by uploading the files (which is calculated
138: * by multiplying total size of file by his/her ratio). If multiplier is more
139: * than 1, the user loses the credits he/she gained by uploading, PLUS some
140: * extra credits. The formula is this: size * ratio + size * (multiplier - 1).
141: * This way, multiplier of 2 causes user to lose size * ratio + size * 1,
142: * so the additional penalty in this case is the size of nuked files. If the
143: * multiplier is 3, user loses size * ratio + size * 2, etc.
144: * @throws ImproperUsageException
145: */
146: private Reply doSITE_NUKE(BaseFtpConnection conn)
147: throws ImproperUsageException {
148: if (!conn.getRequest().hasArgument()) {
149: throw new ImproperUsageException();
150: }
151:
152: StringTokenizer st = new StringTokenizer(conn.getRequest()
153: .getArgument(), " ");
154:
155: if (!st.hasMoreTokens()) {
156: throw new ImproperUsageException();
157: }
158:
159: int multiplier;
160: LinkedRemoteFileInterface nukeDir;
161: String nukeDirName;
162:
163: try {
164: nukeDirName = st.nextToken();
165: nukeDir = conn.getCurrentDirectory().getFile(nukeDirName);
166: } catch (FileNotFoundException e) {
167: Reply response = new Reply(550, e.getMessage());
168:
169: return response;
170: }
171:
172: if (!nukeDir.isDirectory()) {
173: Reply response = new Reply(550, nukeDirName
174: + ": not a directory");
175:
176: return response;
177: }
178:
179: String nukeDirPath = nukeDir.getPath();
180:
181: if (!st.hasMoreTokens()) {
182: throw new ImproperUsageException();
183: }
184:
185: try {
186: multiplier = Integer.parseInt(st.nextToken());
187: } catch (NumberFormatException ex) {
188: logger.warn("", ex);
189:
190: return new Reply(501, "Invalid multiplier: "
191: + ex.getMessage());
192: }
193: conn.getGlobalContext().getSlaveManager()
194: .cancelTransfersInDirectory(nukeDir);
195:
196: String reason;
197:
198: if (st.hasMoreTokens()) {
199: reason = st.nextToken("").trim();
200: } else {
201: reason = "";
202: }
203:
204: //get nukees with string as key
205: Hashtable<String, Long> nukees = new Hashtable<String, Long>();
206: nukeRemoveCredits(nukeDir, nukees);
207:
208: Reply response = new Reply(200, "NUKE suceeded");
209:
210: //// convert key from String to User ////
211: HashMap<User, Long> nukees2 = new HashMap<User, Long>(nukees
212: .size());
213:
214: for (Iterator iter = nukees.keySet().iterator(); iter.hasNext();) {
215: String username = (String) iter.next();
216: User user;
217:
218: try {
219: user = conn.getGlobalContext().getUserManager()
220: .getUserByName(username);
221: } catch (NoSuchUserException e1) {
222: response.addComment("Cannot remove credits from "
223: + username + ": " + e1.getMessage());
224: logger.warn("", e1);
225: user = null;
226: } catch (UserFileException e1) {
227: response.addComment("Cannot read user data for "
228: + username + ": " + e1.getMessage());
229: logger.warn("", e1);
230: response.setMessage("NUKE failed");
231:
232: return response;
233: }
234:
235: // nukees contains credits as value
236: if (user == null) {
237: Long add = (Long) nukees2.get(null);
238:
239: if (add == null) {
240: add = new Long(0);
241: }
242:
243: nukees2.put(user, new Long(add.longValue()
244: + ((Long) nukees.get(username)).longValue()));
245: } else {
246: nukees2.put(user, nukees.get(username));
247: }
248: }
249:
250: //rename
251: String toDirPath;
252: String toName = "[NUKED]-" + nukeDir.getName();
253:
254: try {
255: toDirPath = nukeDir.getParentFile().getPath();
256: } catch (FileNotFoundException ex) {
257: logger.fatal("", ex);
258:
259: return Reply.RESPONSE_553_REQUESTED_ACTION_NOT_TAKEN;
260: }
261:
262: try {
263: nukeDir.renameTo(toDirPath, toName);
264: nukeDir.createDirectory(conn.getUserNull().getName(), conn
265: .getUserNull().getGroup(), "REASON-" + reason);
266: } catch (IOException ex) {
267: logger.warn("", ex);
268: response.addComment(" cannot rename to \"" + toDirPath
269: + "/" + toName + "\": " + ex.getMessage());
270: response.setCode(500);
271: response.setMessage("NUKE failed");
272:
273: return response;
274: }
275:
276: long nukeDirSize = 0;
277: long nukedAmount = 0;
278:
279: //update credits, nukedbytes, timesNuked, lastNuked
280: for (Iterator iter = nukees2.keySet().iterator(); iter
281: .hasNext();) {
282: AbstractUser nukee = (AbstractUser) iter.next();
283:
284: if (nukee == null) {
285: continue;
286: }
287:
288: long size = ((Long) nukees2.get(nukee)).longValue();
289:
290: long debt = calculateNukedAmount(size, conn
291: .getGlobalContext().getConfig()
292: .getCreditCheckRatio(nukeDir, nukee), multiplier);
293:
294: nukedAmount += debt;
295: nukeDirSize += size;
296: nukee.updateCredits(-debt);
297: if (!conn.getGlobalContext().getConfig()
298: .checkPathPermission("nostatsup", nukee, nukeDir)) {
299: nukee.updateUploadedBytes(-size);
300: nukee.getKeyedMap().incrementObjectLong(NUKEDBYTES,
301: debt);
302: }
303: nukee.getKeyedMap().incrementObjectLong(NUKED);
304: nukee.getKeyedMap().setObject(Nuke.LASTNUKED,
305: new Long(System.currentTimeMillis()));
306:
307: try {
308: nukee.commit();
309: } catch (UserFileException e1) {
310: response.addComment("Error writing userfile: "
311: + e1.getMessage());
312: logger.log(Level.WARN, "Error writing userfile", e1);
313: }
314:
315: response.addComment(nukee.getName() + " "
316: + Bytes.formatBytes(debt));
317: }
318:
319: NukeEvent nuke = new NukeEvent(conn.getUserNull(), "NUKE",
320: nukeDirPath, nukeDirSize, nukedAmount, multiplier,
321: reason, nukees);
322: getNukeLog().add(nuke);
323: conn.getGlobalContext().dispatchFtpEvent(nuke);
324:
325: return response;
326: }
327:
328: private Reply doSITE_NUKES(BaseFtpConnection conn) {
329: Reply response = (Reply) Reply.RESPONSE_200_COMMAND_OK.clone();
330:
331: for (Iterator iter = getNukeLog().getAll().iterator(); iter
332: .hasNext();) {
333: response.addComment(iter.next());
334: }
335:
336: return response;
337: }
338:
339: /**
340: * USAGE: site unnuke <directory> <message>
341: * Unnuke a directory.
342: *
343: * ex. site unnuke shit NOT CRAP
344: *
345: * This will unnuke the directory 'shit' with the comment 'NOT CRAP'.
346: *
347: * NOTE: You can enclose the directory in braces if you have spaces in the name
348: * ex. site unnuke {My directory name} justcause
349: *
350: * You need to configure glftpd to keep nuked files if you want to unnuke.
351: * See the section about glftpd.conf.
352: */
353: private Reply doSITE_UNNUKE(BaseFtpConnection conn) {
354: StringTokenizer st = new StringTokenizer(conn.getRequest()
355: .getArgument());
356:
357: if (!st.hasMoreTokens()) {
358: return Reply.RESPONSE_501_SYNTAX_ERROR;
359: }
360:
361: String toName = st.nextToken();
362: String toPath;
363:
364: {
365: StringBuffer toPath2 = new StringBuffer(conn
366: .getCurrentDirectory().getPath());
367:
368: if (toPath2.length() != 1) {
369: toPath2.append("/"); // isn't /
370: }
371:
372: toPath2.append(toName);
373: toPath = toPath2.toString();
374: }
375:
376: String toDir = conn.getCurrentDirectory().getPath();
377: String nukeName = "[NUKED]-" + toName;
378:
379: String reason;
380:
381: if (st.hasMoreTokens()) {
382: reason = st.nextToken("").trim();
383: } else {
384: reason = "";
385: }
386:
387: LinkedRemoteFileInterface nukeDir;
388:
389: try {
390: nukeDir = conn.getCurrentDirectory().getFile(nukeName);
391: } catch (FileNotFoundException e2) {
392: return new Reply(200, nukeName + " doesn't exist: "
393: + e2.getMessage());
394: }
395:
396: conn.getGlobalContext().getSlaveManager()
397: .cancelTransfersInDirectory(nukeDir);
398:
399: Reply response = (Reply) Reply.RESPONSE_200_COMMAND_OK.clone();
400: NukeEvent nuke;
401:
402: try {
403: nuke = getNukeLog().get(toPath);
404: } catch (ObjectNotFoundException ex) {
405: response.addComment(ex.getMessage());
406:
407: return response;
408: }
409:
410: //Map nukees2 = new Hashtable();
411: // for (Iterator iter = nuke.getNukees().entrySet().iterator();
412: // iter.hasNext();
413: // ) {
414: for (Iterator iter = nuke.getNukees2().iterator(); iter
415: .hasNext();) {
416: //Map.Entry entry = (Map.Entry) iter.next();
417: Nukee nukeeObj = (Nukee) iter.next();
418:
419: //String nukeeName = (String) entry.getKey();
420: String nukeeName = nukeeObj.getUsername();
421: User nukee;
422:
423: try {
424: nukee = conn.getGlobalContext().getUserManager()
425: .getUserByName(nukeeName);
426: } catch (NoSuchUserException e) {
427: response.addComment(nukeeName + ": no such user");
428:
429: continue;
430: } catch (UserFileException e) {
431: response.addComment(nukeeName
432: + ": error reading userfile");
433: logger.fatal("error reading userfile", e);
434:
435: continue;
436: }
437:
438: long nukedAmount = calculateNukedAmount(nukeeObj
439: .getAmount(), conn.getGlobalContext().getConfig()
440: .getCreditCheckRatio(nukeDir, nukee), nuke
441: .getMultiplier());
442:
443: nukee.updateCredits(nukedAmount);
444: if (!conn.getGlobalContext().getConfig()
445: .checkPathPermission("nostatsup", nukee, nukeDir)) {
446: nukee.updateUploadedBytes(nukeeObj.getAmount());
447: }
448: nukee.getKeyedMap().incrementObjectInt(NUKED, -1);
449:
450: try {
451: nukee.commit();
452: } catch (UserFileException e3) {
453: logger.log(Level.FATAL, "Eroror saveing userfile for "
454: + nukee.getName(), e3);
455: response.addComment("Error saving userfile for "
456: + nukee.getName());
457: }
458:
459: response.addComment(nukeeName + ": restored "
460: + Bytes.formatBytes(nukedAmount));
461: }
462:
463: try {
464: getNukeLog().remove(toPath);
465: } catch (ObjectNotFoundException e) {
466: response.addComment("Error removing nukelog entry");
467: }
468:
469: try {
470: nukeDir.renameTo(toDir, toName);
471: } catch (FileExistsException e1) {
472: response
473: .addComment("Error renaming nuke, target dir already exists");
474: } catch (IOException e1) {
475: response.addComment("Error: " + e1.getMessage());
476: logger
477: .log(
478: Level.FATAL,
479: "Illegaltargetexception: means parent doesn't exist",
480: e1);
481: }
482:
483: try {
484: LinkedRemoteFileInterface reasonDir = nukeDir
485: .getFile("REASON-" + nuke.getReason());
486:
487: if (reasonDir.isDirectory()) {
488: reasonDir.delete();
489: }
490: } catch (FileNotFoundException e3) {
491: logger.debug("Failed to delete 'REASON-" + nuke.getReason()
492: + "' dir in UNNUKE", e3);
493: }
494:
495: nuke.setCommand("UNNUKE");
496: nuke.setReason(reason);
497: nuke.setUser(conn.getUserNull());
498: conn.getGlobalContext().dispatchFtpEvent(nuke);
499:
500: return response;
501: }
502:
503: public Reply execute(BaseFtpConnection conn)
504: throws UnhandledCommandException, ImproperUsageException {
505: if (_nukelog == null) {
506: return new Reply(500, "You must reconnect to use NUKE");
507: }
508:
509: String cmd = conn.getRequest().getCommand();
510:
511: if ("SITE NUKE".equals(cmd)) {
512: return doSITE_NUKE(conn);
513: }
514:
515: if ("SITE NUKES".equals(cmd)) {
516: return doSITE_NUKES(conn);
517: }
518:
519: if ("SITE UNNUKE".equals(cmd)) {
520: return doSITE_UNNUKE(conn);
521: }
522:
523: throw UnhandledCommandException.create(Nuke.class, conn
524: .getRequest());
525: }
526:
527: public String[] getFeatReplies() {
528: return null;
529: }
530:
531: public static NukeLog getNukeLog() {
532: return _nukelog;
533: }
534:
535: public CommandHandler initialize(BaseFtpConnection conn,
536: CommandManager initializer) {
537: return this ;
538: }
539:
540: public void load(CommandManagerFactory initializer) {
541: _nukelog = new NukeLog();
542:
543: try {
544: Document doc = new SAXBuilder().build(new FileReader(
545: "nukelog.xml"));
546: List nukes = doc.getRootElement().getChildren();
547:
548: for (Iterator iter = nukes.iterator(); iter.hasNext();) {
549: Element nukeElement = (Element) iter.next();
550:
551: User user = initializer
552: .getConnectionManager()
553: .getGlobalContext()
554: .getUserManager()
555: .getUserByName(nukeElement.getChildText("user"));
556: String directory = nukeElement.getChildText("path");
557: long time = Long.parseLong(nukeElement
558: .getChildText("time"));
559: int multiplier = Integer.parseInt(nukeElement
560: .getChildText("multiplier"));
561: String reason = nukeElement.getChildText("reason");
562:
563: long size = Long.parseLong(nukeElement
564: .getChildText("size"));
565: long nukedAmount = Long.parseLong(nukeElement
566: .getChildText("nukedAmount"));
567:
568: Map<String, Long> nukees = new Hashtable<String, Long>();
569: List nukeesElement = nukeElement.getChild("nukees")
570: .getChildren("nukee");
571:
572: for (Iterator iterator = nukeesElement.iterator(); iterator
573: .hasNext();) {
574: Element nukeeElement = (Element) iterator.next();
575: String nukeeUsername = nukeeElement
576: .getChildText("username");
577: Long nukeeAmount = new Long(nukeeElement
578: .getChildText("amount"));
579:
580: nukees.put(nukeeUsername, nukeeAmount);
581: }
582:
583: _nukelog.add(new NukeEvent(user, "NUKE", directory,
584: time, size, nukedAmount, multiplier, reason,
585: nukees));
586: }
587: } catch (FileNotFoundException ex) {
588: logger
589: .log(Level.DEBUG,
590: "nukelog.xml not found, will create it after first nuke.");
591: } catch (Exception ex) {
592: logger.log(Level.INFO,
593: "Error loading nukelog from nukelog.xml", ex);
594: }
595: }
596:
597: public void unload() {
598: _nukelog = null;
599: }
600: }
|