/**
* Copyright (c) 2007, Markus Jevring <markus@jevring.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the contributors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
package cu.ftpd.modules.nukehandler;
import cu.ftpd.filesystem.filters.ForbiddenFilesFilter;
import cu.ftpd.filesystem.metadata.Directory;
import cu.ftpd.filesystem.metadata.Metadata;
import cu.ftpd.logging.Logging;
import cu.ftpd.user.User;
import cu.ftpd.user.userbases.NoSuchUserException;
import cu.ftpd.modules.nukehandler.NukeException;
import cu.ftpd.modules.nukehandler.Nuke;
import cu.ftpd.Server;
import cu.ftpd.ServiceManager;
import java.io.*;
import java.security.AccessControlException;
import java.util.HashMap;
import java.util.Map;
import java.text.MessageFormat;
/**
* @author Markus Jevring <markus@jevring.net>
* @since 2007-okt-08 : 23:36:30
* @version $Id: NukeHandler.java 292 2009-03-04 19:44:36Z jevring $
*/
public class NukeHandler {
private File logfile;
protected PrintWriter nukelog;
protected final Object logLock = new Object();
// releasename, section, nuker, size, multiplier, reason, victims(formatted string, user1:group/bytesMB user1:group/bytesMB)
protected static final MessageFormat nuke = new MessageFormat("NUKE: \"{0}\" \"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\"");
// releasename, section, unnuker, reason
protected static final MessageFormat unnuke = new MessageFormat("UNNUKE: \"{0}\" \"{1}\" \"{2}\" \"{3}\"");
public NukeHandler(String logfilePath) throws IOException {
// maybe 'site nukes [username=user] [section=section] [dir=dir] [number=10{default=10}}'
logfile = new File(logfilePath);
try {
nukelog = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logfile, true))));
} catch (IOException e) {
throw new IOException("Could not open nuke log for writing: " + e.getMessage(), e);
}
}
protected NukeHandler() {
}
public void shutdown() {
synchronized(logLock) {
nukelog.close();
}
}
protected void log(String message) {
synchronized(logLock) {
nukelog.println(message);
nukelog.flush();
}
}
public void logNukeToEventLog(Nuke n) {
Logging.getEventLog().log(nuke.format(new Object[]{n.getReleaseName(), n.getSection(), n.getNuker(), n.getSize(), n.getMultiplier(), n.getReason(), n.getVictims()}));
}
public void logUnnukeToEventLog(String releaseName, String section, String unnuker, String reason) {
Logging.getEventLog().log(unnuke.format(new String[]{releaseName, section, unnuker, reason}));
}
/*
It would be AWESOME if we ran the statistics and the logging in a database. then other scripts could query it as well.
*/
private int calculateNukeSize(File nukedDir, HashMap<String, Long> victims) {
int totalBytesNuked = 0;
Directory dir = ServiceManager.getServices().getMetadataHandler().getDirectory(nukedDir);
Metadata metadata;
Long bytesForUser;
for (File file : nukedDir.listFiles(new ForbiddenFilesFilter())) {
if (file.isDirectory()) {
totalBytesNuked += calculateNukeSize(file, victims);
} else {
totalBytesNuked += file.length();
metadata = dir.getMetadata(file.getName());
if (metadata != null) { // if there's no user information for this file, then we don't do anything about it
bytesForUser = victims.get(metadata.getUsername());
if (bytesForUser == null) {
bytesForUser = 0L;
}
victims.put(metadata.getUsername(), bytesForUser + file.length()); // update the value for the user
}
}
}
return totalBytesNuked;
}
public boolean nuke(String realPwd, String section, String releaseName, int multiplier, String nuker, String reason) throws FileNotFoundException, AccessControlException, NukeException {
// if you think it isn't removing credits, make sure the person doesn't have leech
// this will fail if someone has an open file handle in this dir, so make sure to kill every access to this dir when doing a nuke (and a nrfr+rnto, for that matter)
File originalDir = new File(realPwd, releaseName);
if (originalDir.exists() && originalDir.isDirectory()) {
File nukedDir = new File(realPwd, "[NUKED]-" + releaseName);
boolean ok = originalDir.renameTo(nukedDir);
if (ok) {
// create reason dir:
File reasonDir = new File(nukedDir, "NUKED " + multiplier + "x by " + nuker + " - " + reason);
reasonDir.mkdir();
// only calculate credit loss etc. if we actually suceeded in nuking the dir
nukedDir.setWritable(false); // this will prevent new uploads in the directory
HashMap<String, Long> victims = new HashMap<String, Long>();
ServiceManager.getServices().getMetadataHandler().move(originalDir, nukedDir, null, null);
long totalBytesNuked = calculateNukeSize(nukedDir, victims);
String nukees = "{";
for (Map.Entry<String, Long> victim : victims.entrySet()) {
try {
User user = ServiceManager.getServices().getUserbase().getUser(victim.getKey());
long credits = 0;
if (!user.hasLeech()) {
credits = victim.getValue() * multiplier;
user.takeCredits(credits);
}
nukees += user.getUsername() + '/' + user.getPrimaryGroup() + '/' + credits + '|';
} catch (NoSuchUserException e) {
// just skip this user if it doesn't exist anymore
}
}
nukees += "}";
logNukeToEventLog(new Nuke(releaseName, section, nuker, totalBytesNuked, multiplier, reason, nukees));
log("NUKE;" + releaseName + ';' + section + ';' + nuker + ';' + totalBytesNuked + ';' + multiplier + ';' + reason + ';' + nukees + ';' + realPwd + ';' + System.currentTimeMillis());
return true;
} else {
Logging.getErrorLog().reportError("Failed to nuke directory: directory could not be renamed: " + originalDir.getAbsolutePath());
throw new NukeException("Failed to nuke directory: directory could not be renamed: " + originalDir.getName()); // only disclose the name here, lest we reveal any information about the underlying system
}
} else {
throw new FileNotFoundException("File not found: " + releaseName);
}
}
public boolean unnuke(String realPwd, String section, String releaseName, String unnuker, String reason) throws IOException, AccessControlException, NukeException {
releaseName = releaseName.replaceFirst("^\\[NUKED\\]-(.*)","$1");
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(logfile)));
String lastNuke = null;
String line;
while ((line = in.readLine()) != null) {
if (line.contains("NUKE") && line.contains(releaseName) && line.contains(realPwd)) {
// check release name first, which will mach MUCH less often than "NUKE"
lastNuke = line;
}
// keep on going to the end, so we find the last one.
}
if (lastNuke != null) {
String[] nuke = lastNuke.split(";");
if (nuke.length == 10) {
// rename the dir back (the only way to get this path is if we have the full path in the log, so this needs to be added)
File originalDir = new File(realPwd, releaseName);
File nukedDir = new File(realPwd, "[NUKED]-" + releaseName);
boolean ok = nukedDir.renameTo(originalDir);
if (ok) {
originalDir.setWritable(true); // make the directory writable again
// remove reason dir:
File reasonDir = new File(originalDir, "NUKED " + nuke[5] + "x by " + nuke[3] + " - " + nuke[6]); // this is a bit ugly and unstable, make this nicer.
//System.out.println("trying to delete " + reasonDir.getAbsolutePath());
reasonDir.delete();
// restore the credits of the users
nuke[7] = nuke[7].substring(1, nuke[7].length() - 1); // remove the leading and trailing '{' '}'
String[] nukees = nuke[7].split("\\|");
for (int i = 0; i < nukees.length; i++) {
String[] nukee = nukees[i].split("/");
try {
User user = ServiceManager.getServices().getUserbase().getUser(nukee[0]);
long credits = Long.parseLong(nukee[2]);
//System.out.println("giving back " + credits +" to user " + user.getUsername());
user.giveCredits(credits);
} catch (NoSuchUserException e) {
// this user doesn't exist anymore, skip it
}
}
logUnnukeToEventLog(releaseName, section, unnuker, reason);
log("UNNUKE;" + releaseName + ';' + section + ';' + unnuker + ';' + reason + ';' + System.currentTimeMillis());
return true;
} else {
Logging.getErrorLog().reportError("Failed to unnuke directory: directory could not be renamed: " + nukedDir.getAbsolutePath());
throw new NukeException("Failed to unnuke directory: directory could not be renamed: " + nukedDir.getName());
}
} else {
System.err.println("Malformed nuke line: " + lastNuke);
throw new NukeException("Malformed nuke line");
}
} else {
throw new NukeException("Release not found in nukelog.");
}
} catch (IOException e) {
Logging.getErrorLog().reportCritical("Could not read nukelog: " + e.getMessage());
throw e;
} finally {
if (in != null) {
in.close();
}
}
}
}