package freenet.clients.http.wizardsteps;
import java.io.IOException;
import freenet.clients.http.ExternalLinkToadlet;
import freenet.clients.http.FirstTimeWizardToadlet;
import freenet.clients.http.SecurityLevelsToadlet;
import freenet.l10n.NodeL10n;
import freenet.node.MasterKeysFileSizeException;
import freenet.node.MasterKeysWrongPasswordException;
import freenet.node.Node;
import freenet.node.NodeClientCore;
import freenet.node.SecurityLevels;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileUtil;
import freenet.support.io.FileUtil.OperatingSystem;
/**
* Allows the user to set the physical security level.
*/
public class SECURITY_PHYSICAL implements Step {
private final NodeClientCore core;
private enum PASSWORD_PROMPT {
SET_BLANK, //Requested new password was blank
DECRYPT_WRONG, //Decryption password was wrong
DECRYPT_BLANK //Decryption password was blank
}
/**
* Constructs a new SECURITY_PHYSICAL GET handler.
* @param core used to check or set the current security level and password.
*/
public SECURITY_PHYSICAL(NodeClientCore core) {
this.core = core;
}
@Override
public void getStep(HTTPRequest request, PageHelper helper) {
if (request.isParameterSet("error")) {
if (errorHandler(request, helper)) {
//Error page generated successfully.
return;
}
//Problem generating error page; generate default.
}
HTMLNode contentNode = helper.getPageContent(WizardL10n.l10n("physicalSecurityPageTitle"));
HTMLNode infoboxContent = helper.getInfobox("infobox-normal",
WizardL10n.l10nSec("physicalThreatLevelShort"), contentNode, null, false);
infoboxContent.addChild("p", WizardL10n.l10nSec("physicalThreatLevel"));
HTMLNode form = helper.addFormChild(infoboxContent, ".", "physicalSecurityForm");
HTMLNode div = form.addChild("div", "class", "opennetDiv");
String controlName = "security-levels.physicalThreatLevel";
HTMLNode swapWarning = div.addChild("p").addChild("i");
NodeL10n.getBase().addL10nSubstitution(swapWarning, "SecurityLevels.physicalThreatLevelTruecrypt",
new String[]{"bold", "truecrypt"},
new HTMLNode[]{HTMLNode.STRONG,
HTMLNode.linkInNewWindow(ExternalLinkToadlet.escape("http://www.truecrypt.org/"))});
OperatingSystem os = FileUtil.detectedOS;
div.addChild("p", NodeL10n.getBase().getString("SecurityLevels.physicalThreatLevelSwapfile",
"operatingSystem",
NodeL10n.getBase().getString("OperatingSystemName."+os.name())));
if(os == FileUtil.OperatingSystem.Windows) {
swapWarning.addChild("#", " " + WizardL10n.l10nSec("physicalThreatLevelSwapfileWindows"));
}
for(SecurityLevels.PHYSICAL_THREAT_LEVEL level : SecurityLevels.PHYSICAL_THREAT_LEVEL.values()) {
HTMLNode input;
input = div.addChild("p").addChild("input",
new String[] { "type", "name", "value" },
new String[] { "radio", controlName, level.name() });
input.addChild("b", WizardL10n.l10nSec("physicalThreatLevel.name." + level));
input.addChild("#", ": ");
NodeL10n.getBase().addL10nSubstitution(input, "SecurityLevels.physicalThreatLevel.choice."+level, new String[] { "bold" }, new HTMLNode[] { HTMLNode.STRONG });
if(level == SecurityLevels.PHYSICAL_THREAT_LEVEL.HIGH &&
core.node.securityLevels.getPhysicalThreatLevel() != level) {
// Add password form on high security if not already at high security.
HTMLNode p = div.addChild("p");
p.addChild("label", "for", "passwordBox", WizardL10n.l10nSec("setPasswordLabel")+":");
p.addChild("input", new String[] { "id", "type", "name" }, new String[] { "passwordBox", "password", "masterPassword" });
}
}
div.addChild("#", WizardL10n.l10nSec("physicalThreatLevelEnd"));
form.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "submit", "back", NodeL10n.getBase().getString("Toadlet.back")});
form.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "submit", "next", NodeL10n.getBase().getString("Toadlet.next")});
}
/**
* Internal error handler wrapper with the hope of making the code more readable.
* @param request defines which error and information about it
* @param helper creates page, infoboxes, forms.
* @return whether an error page was successfully generated.
*/
private boolean errorHandler(HTTPRequest request, PageHelper helper) {
String physicalThreatLevel = request.getParam("newThreatLevel");
SecurityLevels.PHYSICAL_THREAT_LEVEL newThreatLevel = SecurityLevels.parsePhysicalThreatLevel(physicalThreatLevel);
String error = request.getParam("error");
if (error.equals("pass")) {
//Password prompt requested
PASSWORD_PROMPT type;
try {
type = PASSWORD_PROMPT.valueOf(request.getParam("type"));
} catch (IllegalArgumentException e) {
//Render the default page if unable to parse password prompt type.
return false;
}
final String pageTitleKey;
final String infoboxTitleKey;
final boolean forDowngrade;
final boolean forUpgrade;
final boolean wasWrong = type == PASSWORD_PROMPT.DECRYPT_WRONG;
switch (type) {
case SET_BLANK:
pageTitleKey = "passwordPageTitle";
infoboxTitleKey = "enterPasswordTitle";
forDowngrade = false;
forUpgrade = true;
break;
case DECRYPT_WRONG:
pageTitleKey ="passwordForDecryptTitle";
infoboxTitleKey = "passwordWrongTitle";
forDowngrade = false;
forUpgrade = false;
break;
case DECRYPT_BLANK:
pageTitleKey = "passwordForDecryptTitle";
infoboxTitleKey = "passwordForDecryptTitle";
forDowngrade = true;
forUpgrade = false;
break;
default:
//Unanticipated value for type!
return false;
}
HTMLNode contentNode = helper.getPageContent(WizardL10n.l10nSec(pageTitleKey));
HTMLNode content = helper.getInfobox("infobox-error", WizardL10n.l10nSec(infoboxTitleKey),
contentNode, null, true);
if (type == PASSWORD_PROMPT.SET_BLANK || type == PASSWORD_PROMPT.DECRYPT_BLANK) {
content.addChild("p", WizardL10n.l10nSec("passwordNotZeroLength"));
}
HTMLNode form = helper.addFormChild(content, ".", "masterPasswordForm");
SecurityLevelsToadlet.generatePasswordFormPage(wasWrong, form, content, forDowngrade, forUpgrade,
newThreatLevel.name(), null);
addBackToPhysicalSeclevelsButton(form);
return true;
} else if (error.equals("corrupt")) {
//Password file corrupt
SecurityLevelsToadlet.sendPasswordFileCorruptedPageInner(helper, core.node.getMasterPasswordFile().getPath());
return true;
} else if (error.equals("delete")) {
SecurityLevelsToadlet.sendCantDeleteMasterKeysFileInner(helper, core.node.getMasterPasswordFile().getPath(), newThreatLevel.name());
return true;
}
//Error type was not recognized.
return false;
}
public SecurityLevels.PHYSICAL_THREAT_LEVEL getCurrentLevel() {
return core.node.securityLevels.getPhysicalThreatLevel();
}
@Override
public String postStep(HTTPRequest request) throws IOException {
final String errorCorrupt = FirstTimeWizardToadlet.WIZARD_STEP.SECURITY_PHYSICAL+"&error=corrupt";
String pass = request.getPartAsStringFailsafe("masterPassword", SecurityLevelsToadlet.MAX_PASSWORD_LENGTH);
final boolean passwordIsBlank = pass != null && pass.length() == 0;
String physicalThreatLevel = request.getPartAsStringFailsafe("security-levels.physicalThreatLevel", 128);
SecurityLevels.PHYSICAL_THREAT_LEVEL oldThreatLevel = core.node.securityLevels.getPhysicalThreatLevel();
SecurityLevels.PHYSICAL_THREAT_LEVEL newThreatLevel = SecurityLevels.parsePhysicalThreatLevel(physicalThreatLevel);
if (FirstTimeWizardToadlet.shouldLogMinor()) {
Logger.minor(this, "Old threat level: " + oldThreatLevel + " new threat level: " + newThreatLevel);
}
/*If the user didn't select a network security level before clicking continue, the selected
* security level could not be determined, clicked back from a password error page, redirect to the main page.*/
if (newThreatLevel == null || !request.isPartSet("security-levels.physicalThreatLevel") ||
request.isPartSet("backToMain")) {
return FirstTimeWizardToadlet.WIZARD_STEP.SECURITY_PHYSICAL.name();
}
//Changing to high physical threat level: set password.
if (newThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.HIGH && oldThreatLevel != newThreatLevel) {
if (passwordIsBlank) {
// Must set the password to something non-blank.
return promptPassword(newThreatLevel, PASSWORD_PROMPT.SET_BLANK);
} else {
try {
if(oldThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.NORMAL ||
oldThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.LOW) {
core.node.changeMasterPassword("", pass, true);
} else {
core.node.setMasterPassword(pass, true);
}
} catch (Node.AlreadySetPasswordException e) {
// Do nothing, already set a password.
} catch (MasterKeysWrongPasswordException e) {
throw new IOException("Incorrect password when changing from another level to high", e);
} catch (MasterKeysFileSizeException e) {
return errorCorrupt;
}
}
}
//Decreasing to low or normal from high: remove password.
if ((newThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.LOW || newThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.NORMAL) &&
oldThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.HIGH) {
if (passwordIsBlank) {
//Prompt for the old password, which is needed to decrypt
return promptPassword(newThreatLevel, PASSWORD_PROMPT.DECRYPT_BLANK);
} else if (core.node.getMasterPasswordFile().exists()) {
//Old password for decryption specified.
try {
core.node.changeMasterPassword(pass, "", true);
} catch (IOException e) {
if(!core.node.getMasterPasswordFile().exists()) {
// Ok.
System.out.println("Master password file no longer exists, assuming this is deliberate");
} else {
System.err.println("Cannot change password as cannot write new passwords file: "+e);
e.printStackTrace();
throw new IOException("cantWriteNewMasterKeysFile", e);
}
} catch (MasterKeysWrongPasswordException e) {
return promptPassword(newThreatLevel, PASSWORD_PROMPT.DECRYPT_WRONG);
} catch (MasterKeysFileSizeException e) {
return errorCorrupt;
} catch (Node.AlreadySetPasswordException e) {
System.err.println("Already set a password when changing it - maybe master.keys copied in at the wrong moment???");
}
}
}
//Maximum threat level: remove master keys file.
if (newThreatLevel == SecurityLevels.PHYSICAL_THREAT_LEVEL.MAXIMUM) {
try {
core.node.killMasterKeysFile();
} catch (IOException e) {
return FirstTimeWizardToadlet.WIZARD_STEP.SECURITY_PHYSICAL+
"&error=delete&newThreatLevel="+newThreatLevel.name();
}
}
setThreatLevel(newThreatLevel, oldThreatLevel);
return FirstTimeWizardToadlet.WIZARD_STEP.NAME_SELECTION.name();
}
/**
* Internal utility function for displaying a password prompt.
* @param newThreatLevel the user-selected threat level, to be used in creating the form.
* @param type what type of prompt needed
* @return URL to display the requested page
*/
private String promptPassword(SecurityLevels.PHYSICAL_THREAT_LEVEL newThreatLevel, PASSWORD_PROMPT type) {
if (type == PASSWORD_PROMPT.DECRYPT_WRONG) {
System.err.println("Wrong password!");
}
StringBuilder destination = new StringBuilder(FirstTimeWizardToadlet.WIZARD_STEP.SECURITY_PHYSICAL+
"&error=pass&newThreatLevel=").append(newThreatLevel.name()).append("&type=").append(type.name());
return destination.toString();
}
public void setThreatLevel(SecurityLevels.PHYSICAL_THREAT_LEVEL newThreatLevel, SecurityLevels.PHYSICAL_THREAT_LEVEL oldThreatLevel) throws IOException {
core.node.securityLevels.setThreatLevel(newThreatLevel);
core.storeConfig();
try {
core.node.lateSetupDatabase(null);
} catch (MasterKeysWrongPasswordException e) {
// Ignore, impossible???
System.err.println("Failed starting up database while switching physical security level to "+newThreatLevel+" from "+oldThreatLevel+" : wrong password - this is impossible, it should have been handled by the other cases, suggest you remove master.keys");
} catch (MasterKeysFileSizeException e) {
System.err.println("Failed starting up database while switching physical security level to "+newThreatLevel+" from "+oldThreatLevel+" : "+core.node.getMasterPasswordFile()+" is too " + e.sizeToString());
}
}
private void addBackToPhysicalSeclevelsButton(HTMLNode form) {
form.addChild("p").addChild("input",
new String[] { "type", "name", "value" },
new String[] { "submit", "backToMain", WizardL10n.l10n("backToSecurityLevels")});
}
}