summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluccioman <luccioman@users.noreply.github.com>2017-03-26 11:48:00 +0200
committerluccioman <luccioman@users.noreply.github.com>2017-03-26 11:48:00 +0200
commitcde237b68763c542da20038e5f62bea341ae1d37 (patch)
treea8a55d4425e9ad778e5737d920458a0dd8639abc
parentdf5970df6d4de27ef96641aadc2591c219e87a36 (diff)
Enforced access controls on some administrative actions.
- ensure use of HTTP POST method : HTTP GET should only be used for information retrieval and not to perform server side effect operations (see HTTP standard https://tools.ietf.org/html/rfc7231#section-4.2.1) - a transaction token is now required for these administrative form submissions to ensure the request can not be included in an external site and performed silently/by mistake by the user browser
-rwxr-xr-xbin/clearall.sh2
-rwxr-xr-xbin/clearcache.sh2
-rwxr-xr-xbin/clearindex.sh2
-rwxr-xr-xbin/deleteurl.sh2
-rwxr-xr-xbin/passwd.sh2
-rwxr-xr-xbin/protectedPostApiCall.sh77
-rw-r--r--htroot/ConfigAccounts_p.html4
-rw-r--r--htroot/ConfigAccounts_p.java14
-rw-r--r--htroot/ConfigProperties_p.html1
-rw-r--r--htroot/ConfigProperties_p.java9
-rw-r--r--htroot/ConfigUpdate_p.html11
-rw-r--r--htroot/ConfigUpdate_p.java14
-rw-r--r--htroot/IndexControlRWIs_p.html2
-rw-r--r--htroot/IndexControlRWIs_p.java22
-rw-r--r--htroot/IndexControlURLs_p.html5
-rw-r--r--htroot/IndexControlURLs_p.java37
-rw-r--r--htroot/IndexDeletion_p.html8
-rw-r--r--htroot/IndexDeletion_p.java18
-rw-r--r--htroot/IndexFederated_p.html7
-rw-r--r--htroot/IndexFederated_p.java17
-rw-r--r--htroot/PerformanceQueues_p.html3
-rw-r--r--htroot/PerformanceQueues_p.java10
-rw-r--r--htroot/Performance_p.html12
-rw-r--r--htroot/Steering.html17
-rw-r--r--htroot/Steering.java22
-rw-r--r--htroot/env/templates/header.template65
-rw-r--r--htroot/terminal_p.html2
-rw-r--r--source/net/yacy/cora/protocol/HeaderFramework.java4
-rw-r--r--source/net/yacy/data/BadTransactionException.java47
-rw-r--r--source/net/yacy/data/TransactionManager.java180
-rw-r--r--source/net/yacy/http/servlets/DisallowedMethodException.java47
-rw-r--r--source/net/yacy/http/servlets/YaCyDefaultServlet.java22
-rw-r--r--source/net/yacy/yacy.java86
-rwxr-xr-xstopYACY.sh2
34 files changed, 729 insertions, 46 deletions
diff --git a/bin/clearall.sh b/bin/clearall.sh
index 17cb50e99..885379c9c 100755
--- a/bin/clearall.sh
+++ b/bin/clearall.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
cd "`dirname $0`"
-./apicall.sh "/IndexControlURLs_p.html?deletecomplete=&deleteIndex=on&deleteSolr=on&deleteCrawlQueues=on&deleteRobots=on&deleteSearchFl=on&deleteCache=on" > /dev/null \ No newline at end of file
+./protectedPostApiCall.sh "IndexControlURLs_p.html" "deletecomplete=&deleteIndex=on&deleteSolr=on&deleteCrawlQueues=on&deleteRobots=on&deleteSearchFl=on&deleteCache=on" \ No newline at end of file
diff --git a/bin/clearcache.sh b/bin/clearcache.sh
index 906595bec..926d70e78 100755
--- a/bin/clearcache.sh
+++ b/bin/clearcache.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
cd "`dirname $0`"
-./apicall.sh "/IndexControlURLs_p.html?deleteIndex=off&deleteSolr=off&deleteCache=on&deleteCrawlQueues=off&deleteRobots=on&deleteSearchFl=on&deletecomplete=" > /dev/null
+./protectedPostApiCall.sh "IndexControlURLs_p.html" "deleteIndex=off&deleteSolr=off&deleteCache=on&deleteCrawlQueues=off&deleteRobots=on&deleteSearchFl=on&deletecomplete="
diff --git a/bin/clearindex.sh b/bin/clearindex.sh
index c32abf94a..9c78bb997 100755
--- a/bin/clearindex.sh
+++ b/bin/clearindex.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
cd "`dirname $0`"
-./apicall.sh "/IndexControlURLs_p.html?deletecomplete=&deleteIndex=on&deleteSolr=on&deleteCrawlQueues=on&deleteRobots=on&deleteSearchFl=on&deleteCache=off" > /dev/null
+./protectedPostApiCall.sh "IndexControlURLs_p.html" "deletecomplete=&deleteIndex=on&deleteSolr=on&deleteCrawlQueues=on&deleteRobots=on&deleteSearchFl=on&deleteCache=off"
diff --git a/bin/deleteurl.sh b/bin/deleteurl.sh
index fd9949637..94aefcfb3 100755
--- a/bin/deleteurl.sh
+++ b/bin/deleteurl.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
cd "`dirname $0`"
-./apicall.sh "/IndexControlURLs_p.html?urlhashdeleteall=&urlstring=$1" > /dev/null
+./protectedPostApiCall.sh "IndexControlURLs_p.html" "urlhashdeleteall=&urlstring=$1"
diff --git a/bin/passwd.sh b/bin/passwd.sh
index 62267fc46..45335853e 100755
--- a/bin/passwd.sh
+++ b/bin/passwd.sh
@@ -6,7 +6,7 @@ if [ -z "$1" ]; then
exit 2
fi
-(./apicall.sh "ConfigAccounts_p.html?setAdmin=&adminuser=admin&adminpw1=$1&adminpw2=$1&access=" > /dev/null && \
+(./protectedPostApiCall.sh "ConfigAccounts_p.html" "setAdmin=&adminuser=admin&adminpw1=$1&adminpw2=$1&access=" && \
echo "Password for User Name 'admin' set to '$1'") || \
(echo "Password setting failed" && \
exit 1) \ No newline at end of file
diff --git a/bin/protectedPostApiCall.sh b/bin/protectedPostApiCall.sh
new file mode 100755
index 000000000..652abdf35
--- /dev/null
+++ b/bin/protectedPostApiCall.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env sh
+# Call a YaCy HTTP POST API URL protected by HTTP authentication and transaction token validation
+# $1 : API path
+# $2 : POST parameters (example : "param1=value1&param2=value2")
+#
+# Authentication options :
+# - enable unauthenticated local access as administrator : set adminAccountForLocalhost=true in the DATA/SETTINGS/yacy.conf file
+# - OR use the legacy Basic HTTP authentication mode (unsecured for remote access): set the "auth-method" to BASIC in the defaults/web.xml file
+# - OR use the Digest HTTP authentication mode : set the "auth-method" to DIGEST in the defaults/web.xml file.
+# With that last option, the script will run in interactive mode as default, prompting for the administrator password.
+# To run in batch mode, you must first export an environment variable filled with the clear-text administrator password before using this script :
+# For example with > export YACY_ADMIN_PASSWORD=your_admin_password
+#
+
+cd "`dirname $0`"
+port=$(grep ^port= ../DATA/SETTINGS/yacy.conf |cut -d= -f2)
+admin=$(grep ^adminAccountUserName= ../DATA/SETTINGS/yacy.conf |cut -d= -f2)
+adminAccountForLocalhost=$(grep ^adminAccountForLocalhost= ../DATA/SETTINGS/yacy.conf | cut -d= -f2)
+
+if grep "<auth-method>BASIC</auth-method>" ../defaults/web.xml > /dev/null; then
+ # When authentication method is in basic mode, use directly the password hash from the configuration file
+ YACY_ADMIN_PASSWORD=$(grep ^adminAccountBase64MD5= ../DATA/SETTINGS/yacy.conf |cut -d= -f2)
+fi
+
+if which curl > /dev/null; then
+ if [ "$adminAccountForLocalhost" = "true" ]; then
+ # localhost access as administrator without authentication is enabled
+
+ # retrieve the transaction token from the HTTP GET flavor of the URL
+ transactionToken=$(curl -sSfI "http://127.0.0.1:$port/$1" | grep X-YaCy-Transaction-Token: | awk {'printf $2'} | tr -d '[:space:]')
+ # send POST data including the transaction token
+ curl -sSf -d "$2&transactionToken=$transactionToken" "http://127.0.0.1:$port/$1" > /dev/null
+
+ else
+ if [ -z "$YACY_ADMIN_PASSWORD" ]; then
+ # no password environment variable : we ask interactively for it only once (not using read -s to be POSIX compliant)
+ stty -echo
+ read -p "Enter host password for user '$admin':" YACY_ADMIN_PASSWORD
+ stty echo
+ printf "\n"
+ fi
+
+ # retrieve the transaction token from the HTTP GET flavor of the URL
+ transactionToken=$(curl -sSfI --anyauth -u "$admin:$YACY_ADMIN_PASSWORD" "http://127.0.0.1:$port/$1" | grep X-YaCy-Transaction-Token: | awk {'printf $2'} | tr -d '[:space:]')
+ # send POST data including the transaction token
+ curl -sSf --anyauth -u "$admin:$YACY_ADMIN_PASSWORD" -d "$2&transactionToken=$transactionToken" "http://127.0.0.1:$port/$1" > /dev/null
+
+ fi
+elif which wget > /dev/null; then
+
+ if [ "$adminAccountForLocalhost" = "true" ]; then
+ # localhost access as administrator without authentication is enabled
+
+ # retrieve the transaction token from the HTTP GET flavor of the URL
+ transactionToken=$(wget -q -t 1 -O - --save-headers --timeout=120 "http://127.0.0.1:$port/$1" | grep X-YaCy-Transaction-Token: | awk {'printf $2'} | tr -d '[:space:]')
+ # send POST data including the transaction token
+ wget -nv -t 1 -O /dev/null --timeout=120 --post-data "$2&transactionToken=$transactionToken" "http://127.0.0.1:$port/$1"
+
+ else
+ if [ -z "$YACY_ADMIN_PASSWORD" ]; then
+ # no password environment variable : we ask interactively for it only once (not using read -s to be POSIX compliant)
+ stty -echo
+ read -p "Enter host password for user '$admin':" YACY_ADMIN_PASSWORD
+ stty echo
+ printf "\n"
+ fi
+
+ # retrieve the transaction token from the HTTP GET flavor of the URL
+ transactionToken=$(wget -q -t 1 -O - --http-user "$admin" --http-password "$YACY_ADMIN_PASSWORD" --save-headers --timeout=120 "http://127.0.0.1:$port/$1" | grep X-YaCy-Transaction-Token: | awk {'printf $2'} | tr -d '[:space:]')
+ # send POST data including the transaction token
+ wget -nv -t 1 -O /dev/null --timeout=120 --http-user "$admin" --http-password "$YACY_ADMIN_PASSWORD" --post-data "$2&transactionToken=$transactionToken" "http://127.0.0.1:$port/$1"
+
+ fi
+else
+ printf "Please install curl or wget\n" > /dev/stderr
+ exit 1
+fi \ No newline at end of file
diff --git a/htroot/ConfigAccounts_p.html b/htroot/ConfigAccounts_p.html
index 0d8ade6a9..40bd836aa 100644
--- a/htroot/ConfigAccounts_p.html
+++ b/htroot/ConfigAccounts_p.html
@@ -30,6 +30,7 @@
<fieldset><legend>Admin Account</legend>
<form action="ConfigAccounts_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
<fieldset>
<legend>
<input type="radio" name="access" id="access_localhost" value="localhost"#(localhost.checked)#:: checked="checked"#(/localhost.checked)# />
@@ -60,6 +61,7 @@
<fieldset><legend>Access Rules</legend>
<form action="ConfigAccounts_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
<dl class="userConfig">
<dt>Protection of all pages: if set to on, access to all pages need authorization; if off, only pages with "_p" extension are protected.</dt>
<dd><input type="checkbox" name="adminAccountAllPages" data-size="small"#(adminAccountAllPages.checked)#:: checked="checked"#(/adminAccountAllPages.checked)#></dd>
@@ -74,6 +76,7 @@
<fieldset><legend>User Accounts</legend>
<form action="ConfigAccounts_p.html" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
<fieldset><legend>Select user</legend>
<dl>
<dt><label for="user">Select user</label>:</dt>
@@ -93,6 +96,7 @@
</form>
<form action="ConfigAccounts_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
<fieldset><legend>Edit current user: #[username]#</legend>
<!-- Hidden(text for debugging): <input type="text" name="current_user" value="#[current_user]#" readonly> -->
<input type="hidden" name="current_user" value="#[current_user]#" />
diff --git a/htroot/ConfigAccounts_p.java b/htroot/ConfigAccounts_p.java
index d21ec6d4d..7ab11efc9 100644
--- a/htroot/ConfigAccounts_p.java
+++ b/htroot/ConfigAccounts_p.java
@@ -33,9 +33,11 @@ import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+
import net.yacy.cora.order.Digest;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.TransactionManager;
import net.yacy.data.UserDB;
import net.yacy.data.UserDB.AccessRight;
import net.yacy.http.Jetty9HttpServerImpl;
@@ -45,10 +47,14 @@ import net.yacy.server.serverObjects;
import net.yacy.server.serverSwitch;
public class ConfigAccounts_p {
-
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
final serverObjects prop = new serverObjects();
+
+ /* Acquire a transaction token for the next POST form submission */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
+
final Switchboard sb = Switchboard.getSwitchboard();
UserDB.Entry entry = null;
@@ -56,10 +62,12 @@ public class ConfigAccounts_p {
boolean localhostAccess = sb.getConfigBool(SwitchboardConstants.ADMIN_ACCOUNT_FOR_LOCALHOST, false);
if (post != null && post.containsKey("setAccess")) {
+ TransactionManager.checkPostTransaction(header, post);
sb.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_All_PAGES, post.getBoolean(SwitchboardConstants.ADMIN_ACCOUNT_All_PAGES));
}
if (post != null && post.containsKey("setAdmin")) {
+ TransactionManager.checkPostTransaction(header, post);
localhostAccess = post.get("access", "").equals("localhost");
final String user = post.get("adminuser", "");
final String pw1 = post.get("adminpw1", "");
@@ -149,6 +157,7 @@ public class ConfigAccounts_p {
//user=from userlist
//current_user = edited user
} else if (post.containsKey("user") && !"newuser".equals(post.get("user"))){
+ TransactionManager.checkPostTransaction(header, post);
if (post.containsKey("change_user")) {
//defaults for newuser are set above
entry = sb.userDB.getEntry(post.get("user"));
@@ -174,6 +183,7 @@ public class ConfigAccounts_p {
sb.userDB.removeEntry(post.get("user"));
}
} else if (post.containsKey("change")) { //New User / edit User
+ TransactionManager.checkPostTransaction(header, post);
prop.put("text", "0");
prop.put("error", "0");
diff --git a/htroot/ConfigProperties_p.html b/htroot/ConfigProperties_p.html
index 98699eb78..ed1bc4333 100644
--- a/htroot/ConfigProperties_p.html
+++ b/htroot/ConfigProperties_p.html
@@ -55,6 +55,7 @@
#{/options}#
</select><br />
<form action="ConfigProperties_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<input type="text" id="key" name="key" size="40" onkeyup="filterList()" value="#[keyPosted]#"/>:<input type="text" id="value" name="value" size="40" value="#[valuePosted]#" />
<input type="submit" value="Save" class="btn btn-primary"/>&nbsp;
diff --git a/htroot/ConfigProperties_p.java b/htroot/ConfigProperties_p.java
index 04a900951..39e262a7b 100644
--- a/htroot/ConfigProperties_p.java
+++ b/htroot/ConfigProperties_p.java
@@ -34,20 +34,27 @@ import java.util.Iterator;
import java.util.List;
import net.yacy.cora.protocol.RequestHeader;
+import net.yacy.data.TransactionManager;
import net.yacy.server.serverObjects;
import net.yacy.server.serverSwitch;
public class ConfigProperties_p {
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final serverObjects prop = new serverObjects();
+
+ /* Acquire a transaction token for the next POST form submission */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
String key = "";
String value = "";
//change a key
if (post != null && post.containsKey("key") && post.containsKey("value")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
key = post.get("key").trim();
value = post.get("value").trim();
if (key != null && !key.isEmpty()) {
diff --git a/htroot/ConfigUpdate_p.html b/htroot/ConfigUpdate_p.html
index 2be2f3ee6..e5228281f 100644
--- a/htroot/ConfigUpdate_p.html
+++ b/htroot/ConfigUpdate_p.html
@@ -1,7 +1,6 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
+<!DOCTYPE html>
+<html>
<head>
- #(forwardToSteering)#::<meta http-equiv="REFRESH" content="0; url=/Steering.html?update=1&amp;releaseinstall=#[release]#" />#(/forwardToSteering)#
<title>YaCy '#[clientname]#': System Update</title>
#%env/templates/metas.template%#
</head>
@@ -35,10 +34,11 @@
#(/downloadError)#
</p></form></dd>
<dt><br />Downloaded Releases</dt>
- <dd><form action="ConfigUpdate_p.html" method="get" accept-charset="UTF-8"><p>
+ <dd><form action="Steering.html" method="post" accept-charset="UTF-8"><p>
#(downloadsAvailable)#
No downloaded releases available for deployment.
::
+ <input type="hidden" name="update" value="1"/>
&nbsp;<select name="releaseinstall">
#(/downloadsAvailable)#
#{downloadedreleases}#
@@ -46,8 +46,9 @@
#{/downloadedreleases}#
#(downloadsAvailable)#::</select>#(/downloadsAvailable)#
#(deployenabled)#::no&nbsp;automated installation on development environments::
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
&nbsp;&nbsp;<input type="submit" name="update" class="btn btn-primary" value="Install Release" #(buttonsActive)#disabled="disabled"::#(/buttonsActive)#/>
- &nbsp;&nbsp;<input type="submit" name="deleteRelease" class="btn btn-danger" value="Delete Release" #(buttonsActive)#disabled="disabled"::#(/buttonsActive)#/>
+ &nbsp;&nbsp;<input type="submit" name="deleteRelease" formaction="ConfigUpdate_p.html" class="btn btn-danger" value="Delete Release" #(buttonsActive)#disabled="disabled"::#(/buttonsActive)#/>
#(/deployenabled)#
</p></form></dd>
<dt><br />Automatic Update</dt>
diff --git a/htroot/ConfigUpdate_p.java b/htroot/ConfigUpdate_p.java
index f11e6ebab..7a49807fe 100644
--- a/htroot/ConfigUpdate_p.java
+++ b/htroot/ConfigUpdate_p.java
@@ -35,6 +35,7 @@ import java.util.TreeSet;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.TransactionManager;
import net.yacy.kelondro.util.FileUtils;
import net.yacy.kelondro.util.OS;
import net.yacy.peers.operation.yacyBuildProperties;
@@ -46,7 +47,7 @@ import net.yacy.server.serverSwitch;
public class ConfigUpdate_p {
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final serverObjects prop = new serverObjects();
final Switchboard sb = (Switchboard) env;
@@ -73,15 +74,6 @@ public class ConfigUpdate_p {
prop.put("candeploy_downloadError", "0");
if (post != null) {
- // check if update is supposed to be installed and a release is defined
- if (post.containsKey("update") && !post.get("releaseinstall", "").isEmpty()) {
- prop.put("forwardToSteering", "1");
- prop.putHTML("forwardToSteering_release",post.get("releaseinstall", ""));
- prop.put("deploys", "1");
- prop.put("candeploy", "2"); // display nothing else
- return prop;
- }
-
if (post.containsKey("downloadRelease")) {
// download a release
final String release = post.get("releasedownload", "");
@@ -208,6 +200,8 @@ public class ConfigUpdate_p {
// check if there are any downloaded releases and if there are enable the update buttons
prop.put("candeploy_downloadsAvailable", (downloadedReleases.isEmpty()) ? "0" : "1");
prop.put("candeploy_deployenabled_buttonsActive", (downloadedReleases.isEmpty() || devenvironment) ? "0" : "1");
+ /* Acquire a transaction token for the update operation */
+ prop.put("candeploy_deployenabled_" + TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header, "/Steering.html"));
int relcount = 0;
for(final yacyRelease release : downloadedReleases) {
diff --git a/htroot/IndexControlRWIs_p.html b/htroot/IndexControlRWIs_p.html
index 90f5afaa8..ef9f62738 100644
--- a/htroot/IndexControlRWIs_p.html
+++ b/htroot/IndexControlRWIs_p.html
@@ -28,6 +28,7 @@
#(limitations)#::
<form action="IndexControlRWIs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Limitations</legend>
<dl>
<dt class="TableCellDark">Index Reference Size</dt>
@@ -47,6 +48,7 @@
<p>No entry for word hash #[wordhash]#</p>::
<p>Search result:
<form name="selection" action="IndexControlRWIs_p.html" method="post" enctype="multipart/form-data">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<table border="0">
<tr class="TableHeader">
<td style="background-color:#FFFFFF">&nbsp;</td>
diff --git a/htroot/IndexControlRWIs_p.java b/htroot/IndexControlRWIs_p.java
index cf6b023e2..6c80e4346 100644
--- a/htroot/IndexControlRWIs_p.java
+++ b/htroot/IndexControlRWIs_p.java
@@ -46,6 +46,7 @@ import net.yacy.cora.util.ByteBuffer;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.cora.util.SpaceExceededException;
import net.yacy.data.ListManager;
+import net.yacy.data.TransactionManager;
import net.yacy.document.Tokenizer;
import net.yacy.kelondro.data.meta.URIMetadataNode;
import net.yacy.kelondro.data.word.Word;
@@ -80,7 +81,7 @@ public class IndexControlRWIs_p {
private final static String errmsg = "not possible to compute word from hash";
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();
@@ -89,7 +90,12 @@ public class IndexControlRWIs_p {
prop.putHTML("keystring", "");
prop.put("keyhash", "");
prop.put("result", "");
- prop.put("limitations", post == null || post.containsKey("maxReferencesLimit") ? 1 : 0);
+ final boolean limitationsEnabled = (post == null || post.containsKey("maxReferencesLimit"));
+ prop.put("limitations", limitationsEnabled ? 1 : 0);
+ if(limitationsEnabled) {
+ /* Acquire a transaction token for the next available POST form submission */
+ prop.put("limitations_" + TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
+ }
// switch off all optional forms/lists
prop.put("searchresult", 0);
@@ -152,6 +158,9 @@ public class IndexControlRWIs_p {
// set reference limitation
if ( post.containsKey("maxReferencesLimit") ) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
if ( post.get("maxReferencesRadio", "").equals("on") ) {
ReferenceContainer.maxReferences = post.getInt("maxReferences", 0);
} else {
@@ -162,6 +171,9 @@ public class IndexControlRWIs_p {
// delete word
if ( post.containsKey("keyhashdeleteall") ) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
try {
if ( delurl || delurlref ) {
// generate urlx: an array of url hashes to be deleted
@@ -245,6 +257,9 @@ public class IndexControlRWIs_p {
// transfer to other peer
if ( post.containsKey("keyhashtransfer") ) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
try {
if ( keystring.isEmpty() || !ByteBuffer.equals(Word.word2hash(keystring), keyhash) ) {
prop.put("keystring", "&lt;" + errmsg + "&gt;");
@@ -454,6 +469,9 @@ public class IndexControlRWIs_p {
}
if ( prop.getInt("searchresult", 0) == 3 ) {
+ /* Acquire a transaction token for the next available POST form submissions */
+ prop.put("searchresult_" + TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
+
listHosts(prop, keyhash, sb);
}
}
diff --git a/htroot/IndexControlURLs_p.html b/htroot/IndexControlURLs_p.html
index d1d5964ea..e5b0ff38d 100644
--- a/htroot/IndexControlURLs_p.html
+++ b/htroot/IndexControlURLs_p.html
@@ -94,6 +94,7 @@ function updatepage(str) {
</form>
<form action="IndexControlURLs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset#(cleanup)# disabled="disabled"::#(/cleanup)#><legend>Cleanup</legend>
<dl>
<dt class="TableCellDark">Index Deletion</dt>
@@ -115,6 +116,7 @@ function updatepage(str) {
#(dumprestore)#::
<form action="IndexControlURLs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Optimize Solr</legend>
<dl>
<dt>&nbsp;</dt>
@@ -125,6 +127,7 @@ function updatepage(str) {
</fieldset>
</form>
<form action="IndexControlURLs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Reboot Solr Core</legend>
<dl>
<dt>&nbsp;</dt>
@@ -160,6 +163,7 @@ function updatepage(str) {
<tr class="TableCell#(dark)#Light::Dark#(/dark)#">
<td>
<form action="IndexControlURLs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<div>
<input type="hidden" name="domain" value="#[domain]#" />
<input type="hidden" name="lines" value="#[lines]#" />
@@ -188,6 +192,7 @@ function updatepage(str) {
</p>
<p>
<form action="IndexControlURLs_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<input type="hidden" name="keystring" value="" />
<input type="hidden" name="keyhash" value="" />
<input type="hidden" name="urlstring" value="" />
diff --git a/htroot/IndexControlURLs_p.java b/htroot/IndexControlURLs_p.java
index 3f993365f..42bbacfe8 100644
--- a/htroot/IndexControlURLs_p.java
+++ b/htroot/IndexControlURLs_p.java
@@ -39,6 +39,7 @@ import net.yacy.cora.sorting.ReversibleScoreMap;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.crawler.data.Cache;
import net.yacy.crawler.data.ResultURLs;
+import net.yacy.data.TransactionManager;
import net.yacy.data.WorkTables;
import net.yacy.kelondro.data.meta.URIMetadataNode;
import net.yacy.kelondro.data.word.Word;
@@ -51,11 +52,15 @@ import net.yacy.server.serverSwitch;
public class IndexControlURLs_p {
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();
+
+ /* Acquire a transaction token for the next possible POST form submissions */
+ final String nextTransactionToken = TransactionManager.getTransactionToken(header);
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, nextTransactionToken);
Segment segment = sb.index;
long ucount = segment.fulltext().collectionSize();
@@ -72,6 +77,7 @@ public class IndexControlURLs_p {
prop.put("reload", 0);
prop.put("reload", 0);
prop.put("dumprestore", 1);
+ prop.put("dumprestore_" + TransactionManager.TRANSACTION_TOKEN_PARAM, nextTransactionToken);
List<File> dumpFiles = segment.fulltext().dumpFiles();
prop.put("dumprestore_dumpfile", dumpFiles.size() == 0 ? "" : dumpFiles.get(dumpFiles.size() - 1).getAbsolutePath());
prop.put("dumprestore_optimizemax", 10);
@@ -107,6 +113,9 @@ public class IndexControlURLs_p {
// delete everything
if ( post.containsKey("deletecomplete") ) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
if ( post.get("deleteIndex", "").equals("on") ) {
try {segment.fulltext().clearLocalSolr();} catch (final IOException e) {}
}
@@ -137,12 +146,18 @@ public class IndexControlURLs_p {
}
if (post.containsKey("urlhashdeleteall")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
ClientIdentification.Agent agent = ClientIdentification.getAgent(post.get("agentName", ClientIdentification.yacyInternetCrawlerAgentName));
int i = segment.removeAllUrlReferences(urlhash.getBytes(), sb.loader, agent, CacheStrategy.IFEXIST);
prop.put("result", "Deleted URL and " + i + " references from " + i + " word indexes.");
}
if (post.containsKey("urlhashdelete")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
DigestURL url;
try {
url = segment.fulltext().getURL(urlhash);
@@ -160,6 +175,9 @@ public class IndexControlURLs_p {
}
if (post.containsKey("urldelete")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
try {
urlhash = ASCII.String((new DigestURL(urlstring)).hash());
} catch (final MalformedURLException e) {
@@ -184,7 +202,7 @@ public class IndexControlURLs_p {
prop.putHTML("urlstring", urlstring);
prop.put("urlhash", "");
} else {
- prop.putAll(genUrlProfile(segment, entry, urlhash));
+ prop.putAll(genUrlProfile(segment, entry, urlhash, nextTransactionToken));
prop.put("statistics", 0);
}
} catch (final MalformedURLException e) {
@@ -199,23 +217,32 @@ public class IndexControlURLs_p {
prop.putHTML("result", "No Entry for URL hash " + urlhash);
} else {
prop.putHTML("urlstring", entry.url().toNormalform(true));
- prop.putAll(genUrlProfile(segment, entry, urlhash));
+ prop.putAll(genUrlProfile(segment, entry, urlhash, nextTransactionToken));
prop.put("statistics", 0);
}
}
if (post.containsKey("optimizesolr")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
final int size = post.getInt("optimizemax", 10);
segment.fulltext().optimize(size);
sb.tables.recordAPICall(post, "IndexControlURLs_p.html", WorkTables.TABLE_API_TYPE_STEERING, "solr optimize " + size);
}
if (post.containsKey("rebootsolr")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
segment.fulltext().rebootSolr();
sb.tables.recordAPICall(post, "IndexControlURLs_p.html", WorkTables.TABLE_API_TYPE_STEERING, "solr reboot");
}
if (post.containsKey("deletedomain")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
final String domain = post.get("domain");
Set<String> hostnames = new HashSet<String>();
hostnames.add(domain);
@@ -238,6 +265,7 @@ public class IndexControlURLs_p {
prop.put("statisticslines_domains_" + cnt + "lines", count);
while (statsiter.hasNext() && cnt < count) {
hostname = statsiter.next();
+ prop.put("statisticslines_domains_" + cnt + "_" + TransactionManager.TRANSACTION_TOKEN_PARAM, nextTransactionToken);
prop.put("statisticslines_domains_" + cnt + "_dark", (dark) ? "1" : "0");
prop.put("statisticslines_domains_" + cnt + "_domain", hostname);
prop.put("statisticslines_domains_" + cnt + "_count", stats.get(hostname));
@@ -257,7 +285,7 @@ public class IndexControlURLs_p {
return prop;
}
- private static serverObjects genUrlProfile(final Segment segment, final URIMetadataNode entry, final String urlhash) {
+ private static serverObjects genUrlProfile(final Segment segment, final URIMetadataNode entry, final String urlhash, final String nextTransactionToken) {
final serverObjects prop = new serverObjects();
if (entry == null) {
prop.put("genUrlProfile", "1");
@@ -271,6 +299,7 @@ public class IndexControlURLs_p {
return prop;
}
prop.put("genUrlProfile", "2");
+ prop.put("genUrlProfile_" + TransactionManager.TRANSACTION_TOKEN_PARAM, nextTransactionToken);
prop.putHTML("genUrlProfile_urlNormalform", entry.url().toNormalform(true));
prop.put("genUrlProfile_urlhash", urlhash);
prop.put("genUrlProfile_urlDescr", entry.dc_title());
diff --git a/htroot/IndexDeletion_p.html b/htroot/IndexDeletion_p.html
index 0157773ff..0620d72f5 100644
--- a/htroot/IndexDeletion_p.html
+++ b/htroot/IndexDeletion_p.html
@@ -11,6 +11,7 @@
<h2>Index Deletion</h2>
<p>The search index contains #[doccount]# documents. You can delete them here. Deletions are made concurrently which can cause that recently deleted documents are not yet reflected in the document count.</p>
<form id="IndexDeletionPath" action="IndexDeletion_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Delete by URL Matching</legend>
<p>Delete all documents within a sub-path of the given urls. That means all documents must start with one of the url stubs as given here.</p>
@@ -31,7 +32,8 @@
</fieldset>
</form>
- <form id="IndexDeletionAge" action="IndexDeletion_p.html" method="get" accept-charset="UTF-8">
+ <form id="IndexDeletionAge" action="IndexDeletion_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Delete by Age</legend>
<p>Delete all documents which are older than a given time period.</p>
@@ -78,7 +80,8 @@
</fieldset>
</form>
- <form id="IndexDeletionCollection" action="IndexDeletion_p.html" method="get" accept-charset="UTF-8">
+ <form id="IndexDeletionCollection" action="IndexDeletion_p.html" method="post" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Delete Collections</legend>
<p>Delete all documents which are inside specific collections.</p>
@@ -104,6 +107,7 @@
</form>
<form id="IndexDeletionQuery" action="IndexDeletion_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Delete by Solr Query</legend>
<p>This is the most generic option: select a set of documents using a solr query.</p>
diff --git a/htroot/IndexDeletion_p.java b/htroot/IndexDeletion_p.java
index 4c690b32a..0d755a824 100644
--- a/htroot/IndexDeletion_p.java
+++ b/htroot/IndexDeletion_p.java
@@ -37,6 +37,7 @@ import net.yacy.cora.federate.solr.connector.SolrConnector;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.sorting.ScoreMap;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.TransactionManager;
import net.yacy.data.WorkTables;
import net.yacy.search.Switchboard;
import net.yacy.search.query.QueryModifier;
@@ -47,10 +48,13 @@ import net.yacy.server.serverSwitch;
public class IndexDeletion_p {
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();
+
+ /* Acquire a transaction token for the next POST form submission */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
SolrConnector defaultConnector = sb.index.fulltext().getDefaultConnector();
SolrConnector webgraphConnector = sb.index.fulltext().getWebgraphConnector();
@@ -121,6 +125,9 @@ public class IndexDeletion_p {
int count = post == null ? -1 : post.getInt("count", -1);
if (post != null && (post.containsKey("simulate-urldelete") || post.containsKey("engage-urldelete"))) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean simulate = post.containsKey("simulate-urldelete");
// parse the input
urldelete = urldelete.trim();
@@ -180,6 +187,9 @@ public class IndexDeletion_p {
}
if (post != null && (post.containsKey("simulate-timedelete") || post.containsKey("engage-timedelete"))) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean simulate = post.containsKey("simulate-timedelete");
Date deleteageDate = null;
long t = timeParser(timedelete_number, timedelete_unit); // year, month, day, hour
@@ -206,6 +216,9 @@ public class IndexDeletion_p {
}
if (post != null && (post.containsKey("simulate-collectiondelete") || post.containsKey("engage-collectiondelete"))) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean simulate = post.containsKey("simulate-collectiondelete");
collectiondelete = collectiondelete.replaceAll(" ","").replaceAll(",", "|");
String query = collectiondelete_mode_unassigned_checked ? "-" + CollectionSchema.collection_sxt + AbstractSolrConnector.CATCHALL_DTERM : collectiondelete.length() == 0 ? CollectionSchema.collection_sxt + ":\"\"" : QueryModifier.parseCollectionExpression(collectiondelete);
@@ -228,6 +241,9 @@ public class IndexDeletion_p {
}
if (post != null && (post.containsKey("simulate-querydelete") || post.containsKey("engage-querydelete"))) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean simulate = post.containsKey("simulate-querydelete");
SolrConnector connector = schemaName.equals(CollectionSchema.CORE_NAME) ? defaultConnector : sb.index.fulltext().getWebgraphConnector();
diff --git a/htroot/IndexFederated_p.html b/htroot/IndexFederated_p.html
index 95dc1e1f7..d5e139de3 100644
--- a/htroot/IndexFederated_p.html
+++ b/htroot/IndexFederated_p.html
@@ -14,6 +14,7 @@
</p>
<form id="config" action="IndexFederated_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Solr Search Index</legend>
Solr stores the main search index. It is the home of two cores, the default 'collection1' core for documents and the 'webgraph' core for a web structure graph. Detailed information about the used Solr fields can be edited in the <a href="IndexSchema_p.html">Schema Editor</a>.
@@ -81,7 +82,8 @@
</fieldset>
</form>
- <form id="config" action="IndexFederated_p.html" method="get" enctype="multipart/form-data" accept-charset="UTF-8">
+ <form id="config" action="IndexFederated_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>
Web Structure Index
@@ -100,7 +102,8 @@
</fieldset>
</form>
- <form id="config" action="IndexFederated_p.html" method="get" enctype="multipart/form-data" accept-charset="UTF-8">
+ <form id="config" action="IndexFederated_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>
Peer-to-Peer Operation
diff --git a/htroot/IndexFederated_p.java b/htroot/IndexFederated_p.java
index 89b122153..c6031dc8b 100644
--- a/htroot/IndexFederated_p.java
+++ b/htroot/IndexFederated_p.java
@@ -34,6 +34,7 @@ import net.yacy.cora.federate.solr.instance.RemoteInstance;
import net.yacy.cora.federate.solr.instance.ShardInstance;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.TransactionManager;
import net.yacy.kelondro.util.OS;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
@@ -42,12 +43,15 @@ import net.yacy.server.serverSwitch;
public class IndexFederated_p {
- public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
+ public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {
// return variable that accumulates replacements
final serverObjects prop = new serverObjects();
final Switchboard sb = (Switchboard) env;
-
+
if (post != null && post.containsKey("setrwi")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
//yacy
boolean post_core_rwi = post.getBoolean("core.service.rwi");
final boolean previous_core_rwi = sb.index.connectedRWI() && env.getConfigBool(SwitchboardConstants.CORE_SERVICE_RWI, false);
@@ -61,6 +65,9 @@ public class IndexFederated_p {
}
if (post != null && post.containsKey("setcitation")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean post_core_citation = post.getBoolean(SwitchboardConstants.CORE_SERVICE_CITATION);
final boolean previous_core_citation = sb.index.connectedCitation() && env.getConfigBool(SwitchboardConstants.CORE_SERVICE_CITATION, false);
env.setConfig(SwitchboardConstants.CORE_SERVICE_CITATION, post_core_citation);
@@ -76,6 +83,9 @@ public class IndexFederated_p {
}
if (post != null && post.containsKey("setsolr")) {
+ /* Check the transaction is valid */
+ TransactionManager.checkPostTransaction(header, post);
+
boolean post_core_fulltext = post.getBoolean(SwitchboardConstants.CORE_SERVICE_FULLTEXT);
final boolean previous_core_fulltext = sb.index.fulltext().connectedLocalSolr() && env.getConfigBool(SwitchboardConstants.CORE_SERVICE_FULLTEXT, false);
env.setConfig(SwitchboardConstants.CORE_SERVICE_FULLTEXT, post_core_fulltext);
@@ -155,6 +165,9 @@ public class IndexFederated_p {
boolean lazy = post.getBoolean("solr.indexing.lazy");
env.setConfig(SwitchboardConstants.FEDERATED_SERVICE_SOLR_INDEXING_LAZY, lazy);
}
+
+ /* Acquire a transaction token for the next POST form submission */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
// show solr host table
if (!sb.index.fulltext().connectedRemoteSolr()) {
diff --git a/htroot/PerformanceQueues_p.html b/htroot/PerformanceQueues_p.html
index d438097fe..a5a9c1a52 100644
--- a/htroot/PerformanceQueues_p.html
+++ b/htroot/PerformanceQueues_p.html
@@ -11,6 +11,7 @@
<h2>Performance Settings of Queues and Processes</h2>
<form action="PerformanceQueues_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Scheduled tasks overview and waiting time settings:</legend>
<table border="0">
<tr class="TableHeader" valign="bottom">
@@ -70,6 +71,7 @@
</form>
<form action="PerformanceQueues_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Cache Settings:</legend>
<table border="0">
<tr valign="top" class="TableHeader">
@@ -129,6 +131,7 @@
</form>
<form action="PerformanceQueues_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8" id="ThreadPoolSettings">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Thread Pool Settings:</legend>
<table border="0">
<tr class="TableHeader" valign="bottom">
diff --git a/htroot/PerformanceQueues_p.java b/htroot/PerformanceQueues_p.java
index 23dec663a..539eec912 100644
--- a/htroot/PerformanceQueues_p.java
+++ b/htroot/PerformanceQueues_p.java
@@ -30,6 +30,7 @@ import java.util.Map;
import net.yacy.cora.protocol.ConnectionInfo;
import net.yacy.cora.protocol.RequestHeader;
+import net.yacy.data.TransactionManager;
import net.yacy.kelondro.data.word.WordReference;
import net.yacy.kelondro.rwi.IndexCell;
import net.yacy.kelondro.util.FileUtils;
@@ -50,11 +51,17 @@ public class PerformanceQueues_p {
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();
File defaultSettingsFile = new File(sb.getAppPath(), "defaults/yacy.init");
+
+ /* Acquire a transaction token for the next POST form submission */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
// get segment
Segment indexSegment = sb.index;
if(post != null) {
+ /* Check the transaction is valid : validation apply then for every uses of this post parameter */
+ TransactionManager.checkPostTransaction(header, post);
+
if(post.containsKey("defaultFile")){
// TODO check file-path!
final File value = new File(sb.getAppPath(), post.get("defaultFile", "defaults/yacy.init"));
@@ -70,6 +77,9 @@ public class PerformanceQueues_p {
sb.setConfig("javastart_Xmx", "Xmx" + xmx + "m");
sb.setConfig("javastart_Xms", "Xms" + xms + "m");
prop.put("setStartupCommit", "1");
+
+ /* Acquire a transaction token for the restart operation */
+ prop.put("setStartupCommit_" + TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header, "/Steering.html"));
}
if(post.containsKey("diskFree")) {
sb.setConfig(SwitchboardConstants.RESOURCE_DISK_FREE_MIN_STEADYSTATE, post.getLong("diskFree", SwitchboardConstants.RESOURCE_DISK_FREE_MIN_STEADYSTATE_DEFAULT));
diff --git a/htroot/Performance_p.html b/htroot/Performance_p.html
index fc8bb6671..a0abe5f98 100644
--- a/htroot/Performance_p.html
+++ b/htroot/Performance_p.html
@@ -23,19 +23,28 @@
<form action="Performance_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Memory Settings</legend>
<dl>
<dt><label for="Xmx">Memory reserved for <abbr title="Java Virtual Machine">JVM</abbr></label></dt>
<dd><input name="Xmx" id="Xmx" type="text" size="5" maxlength="5" value="#[Xmx]#" /> MByte&nbsp;
<input type="submit" class="btn btn-primary" name="setStartup" value="Set" />&nbsp;
- #(setStartupCommit)#::<div class="commit">Accepted change. This will take effect after <strong>restart</strong> of YaCy. <br/><a href="Steering.html?restart=" onclick="return confirm('Confirm Restart')">restart now</a></div>::<div class="error"></div>#(/setStartupCommit)#
</dd>
</dl>
</fieldset>
</form>
+ #(setStartupCommit)#::<div class="commit">Accepted change. This will take effect after <strong>restart</strong> of YaCy. <br/>
+ <form action="Steering.html" method="post">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
+ <input type="hidden" name="restart" value="" />
+ <button type="submit" class="btn btn-warning" onclick="return confirm('Confirm Re-Start')" title="Restart now">Restart now</button>
+ </form>
+ </div>
+ ::<div class="error"></div>#(/setStartupCommit)#
<form action="Performance_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset>
<legend>Resource Observer</legend>
<div class="col-sm-6">
@@ -133,6 +142,7 @@
</form>
<form action="Performance_p.html" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#" />
<fieldset><legend>Online Caution Settings:</legend>
<p>
This is the time that the crawler idles when the proxy is accessed, or a local or remote search is done.
diff --git a/htroot/Steering.html b/htroot/Steering.html
index 7f6003222..0055941d9 100644
--- a/htroot/Steering.html
+++ b/htroot/Steering.html
@@ -159,6 +159,23 @@ XDtoU7vQ/wIAAP//AwBb7ktEXQ4nqQAAAABJRU5ErkJggg==" width="128" height="64" alt="K
#(info)#
<p><b>No action submitted</b><br />
Go back to the <a href="Settings_p.html">Settings</a> page.</p>
+ <!-- Restart and Shutdown buttons available here as classical HTML submit buttons for those who eventually disabled JavaScript -->
+ <form action="Steering.html" method="post">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
+ <input type="hidden" name="restart" value="" />
+ <button type="submit" class="btn label-warning" onclick="return confirm('Confirm Re-Start')" title="Restart">
+ <span class="glyphicon glyphicon-fire"/>
+ <span>Re-Start</span>
+ </button>
+ </form>
+ <form action="Steering.html" method="post">
+ <input type="hidden" name="transactionToken" value="#[transactionToken]#"/>
+ <input type="hidden" name="shutdown" value="" />
+ <button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-danger" onclick="return confirm('Confirm Shutdown')" title="Shutdown">
+ <span class="glyphicon glyphicon-off"/>
+ <span>Shutdown</span>
+ </button>
+ </form>
::
<p><b>Your system is not protected by a password</b><br />
Please go to the <a href="ConfigAccounts_p.html">User Administration</a> page and set an administration password.</p>
diff --git a/htroot/Steering.java b/htroot/Steering.java
index fc9dbdfee..e83cb5460 100644
--- a/htroot/Steering.java
+++ b/htroot/Steering.java
@@ -33,6 +33,7 @@ import net.yacy.cora.protocol.Domains;
import net.yacy.cora.protocol.HeaderFramework;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.TransactionManager;
import net.yacy.kelondro.util.FileUtils;
import net.yacy.peers.operation.yacyRelease;
import net.yacy.search.Switchboard;
@@ -42,7 +43,22 @@ import net.yacy.server.serverSwitch;
public class Steering {
public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch ss) {
- if (post == null || ss == null) { return new serverObjects(); }
+ if (post == null || post.isEmpty() || ss == null) {
+ final serverObjects prop = new serverObjects();
+
+ /* For authenticated user only : acquire a transaction token to pass then to the Steering.html post action */
+ if(ss != null && ((Switchboard) ss).verifyAuthentication(header)) {
+ /* YaCyDefaultServlet will detect it and then also fill the custom HTTP response header used by the JavaScript shutdown and restart actions
+ * or any external API requesting tool */
+ prop.put(TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
+ /* Also add to the Steering.html page info block for eventual display of this page without parameter */
+ prop.put("info_" + TransactionManager.TRANSACTION_TOKEN_PARAM, TransactionManager.getTransactionToken(header));
+ } else {
+ prop.authenticationRequired();
+ }
+
+ return prop;
+ }
final Switchboard sb = (Switchboard) ss;
final serverObjects prop = new serverObjects();
@@ -58,6 +74,7 @@ public class Steering {
}
if (post.containsKey("shutdown")) {
+ TransactionManager.checkPostTransaction(header, post);
ConcurrentLog.info("STEERING", "shutdown request from " + requestIP);
sb.terminate(10, "shutdown request from Steering; ip = " + requestIP);
prop.put("info", "3");
@@ -66,6 +83,7 @@ public class Steering {
}
if (post.containsKey("restart")) {
+ TransactionManager.checkPostTransaction(header, post);
ConcurrentLog.info("STEERING", "restart request from " + requestIP);
yacyRelease.restart();
prop.put("info", "4");
@@ -74,6 +92,7 @@ public class Steering {
}
if (post.containsKey("update")) {
+ TransactionManager.checkPostTransaction(header, post);
ConcurrentLog.info("STEERING", "update request from " + requestIP);
final boolean devenvironment = new File(sb.getAppPath(), ".git").exists();
final String releaseFileName = post.get("releaseinstall", "");
@@ -96,6 +115,7 @@ public class Steering {
return prop;
}
+
return prop;
}
diff --git a/htroot/env/templates/header.template b/htroot/env/templates/header.template
index 6c19068d8..9b684a293 100644
--- a/htroot/env/templates/header.template
+++ b/htroot/env/templates/header.template
@@ -22,12 +22,66 @@
<input type="hidden" name="nav" value="all" />
</form>
</div>
+ <script type="text/javascript">
+ /**
+ * Request the server for a valid transaction token and then send a steering request
+ * @param tokenInputId HTML identifier of the transaction token input field
+ * @param formId identifier of the form
+ */
+ function sendSteeringRequest(tokenInputId, formId) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == XMLHttpRequest.DONE) {
+ if(xhr.status != 200) {
+ if(xhr.status == 401) {
+ alert("Please authenticate with administrator credentials.");
+ } else {
+ alert("The server rejected the request or is not responding!");
+ }
+ } else {
+ var requestSent = false;
+ var transactionTokenInput = document.getElementById(tokenInputId);
+ if(transactionTokenInput) {
+ transactionTokenInput.value = xhr.getResponseHeader("X-YaCy-Transaction-Token")
+ var restartForm = document.getElementById(formId);
+ if(restartForm) {
+ requestSent = true;
+ restartForm.submit();
+ }
+ }
+ if(!requestSent) {
+ alert("Could not send the request to the server!");
+ }
+ }
+ }
+ };
+ xhr.open("get", "Steering.html", true);
+ xhr.send();
+ }
+
+ /** Trigger the server restart after asking for user confirm */
+ function confirmRestart() {
+ if(confirm('Confirm Re-Start')) {
+ sendSteeringRequest("restartTransactionToken", "restartForm");
+ }
+ }
+
+ /** Trigger the server restart after asking for user confirm */
+ function confirmShutdown() {
+ if(confirm('Confirm Shutdown')) {
+ sendSteeringRequest("shutdownTransactionToken", "shutdownForm");
+ }
+ }
+ </script>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li id="header_restart">
- <form action="Steering.html" method="get">
+ <form action="Steering.html" method="post" id="restartForm">
+ <input type="hidden" name="transactionToken" value="" id="restartTransactionToken"/>
<input type="hidden" name="restart" value="" />
- <button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-warning" onclick="return confirm('Confirm Re-Start')" title="Restart">
+ </form>
+ <form action="Steering.html" method="get">
+ <button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-warning" onclick="confirmRestart(); return false;" title="Restart">
<span class="glyphicon glyphicon-fire"></span>
<span class="hidden-sm"> Re-Start</span>
</button>
@@ -35,9 +89,12 @@
</li>
<li>&nbsp;</li>
<li id="header_shutdown">
- <form action="Steering.html" method="get">
+ <form action="Steering.html" method="post" id="shutdownForm">
+ <input type="hidden" name="transactionToken" value="" id="shutdownTransactionToken"/>
<input type="hidden" name="shutdown" value="" />
- <button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-danger" onclick="return confirm('Confirm Shutdown')" title="Shutdown">
+ </form>
+ <form action="Steering.html" method="get">
+ <button accesskey="s" type="submit" class="btn btn-inverse navbar-btn label-danger" onclick="confirmShutdown(); return false;" title="Shutdown">
<span class="glyphicon glyphicon-off"></span>
<span class="hidden-sm"> Shutdown</span>
</button>
diff --git a/htroot/terminal_p.html b/htroot/terminal_p.html
index d4ef88134..2042b39b0 100644
--- a/htroot/terminal_p.html
+++ b/htroot/terminal_p.html
@@ -44,7 +44,7 @@ function init() {
<a href="index.html">&lt;Search Form&gt;</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="CrawlStartSite.html">&lt;Crawl Start&gt;</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="Status.html">&lt;Status Page&gt;</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="Steering.html?shutdown=" onclick="return confirm('Confirm Shutdown')">&lt;Shutdown&gt;</a>
+<a href="Steering.html">&lt;Shutdown&gt;</a>
</div>
<div class="bars"></div>
diff --git a/source/net/yacy/cora/protocol/HeaderFramework.java b/source/net/yacy/cora/protocol/HeaderFramework.java
index b049def16..f0bf457f0 100644
--- a/source/net/yacy/cora/protocol/HeaderFramework.java
+++ b/source/net/yacy/cora/protocol/HeaderFramework.java
@@ -103,6 +103,10 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
/** Added when generating legacy request header to allow template servlets to know the original request scheme : "http" or "https" */
@Deprecated /** use getScheme() (header not used in any request, 2017-02-22) */
public static final String X_YACY_REQUEST_SCHEME = "X-YaCy-Request-Scheme";
+
+ /** Added to responses embedding a hidden HTML field containing a transaction token,
+ * to allow easier retrieval (without HTML parsing) of the token value by external tools such as bash scripts */
+ public static final String X_YACY_TRANSACTION_TOKEN = "X-YaCy-Transaction-Token";
public static final String SET_COOKIE = "Set-Cookie";
public static final String SET_COOKIE2 = "Set-Cookie2";
diff --git a/source/net/yacy/data/BadTransactionException.java b/source/net/yacy/data/BadTransactionException.java
new file mode 100644
index 000000000..cfe270055
--- /dev/null
+++ b/source/net/yacy/data/BadTransactionException.java
@@ -0,0 +1,47 @@
+// BadTransactionException.java
+// Copyright 2017 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package net.yacy.data;
+
+/**
+ * Exception indicating a transaction is not valid.
+ *
+ */
+public class BadTransactionException extends RuntimeException {
+
+ /** Generated serialization id */
+ private static final long serialVersionUID = -8128202785152200358L;
+
+ /**
+ * Default constructor : use generic message
+ */
+ public BadTransactionException() {
+ super("Transaction is not valid");
+ }
+
+ /**
+ * @param message detail message
+ */
+ public BadTransactionException(String message) {
+ super(message);
+ }
+
+}
diff --git a/source/net/yacy/data/TransactionManager.java b/source/net/yacy/data/TransactionManager.java
new file mode 100644
index 000000000..e91145b48
--- /dev/null
+++ b/source/net/yacy/data/TransactionManager.java
@@ -0,0 +1,180 @@
+// TransactionManager.java
+// Copyright 2017 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package net.yacy.data;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.UUID;
+
+import org.apache.commons.codec.digest.HmacUtils;
+
+import net.yacy.cora.order.Base64Order;
+import net.yacy.cora.protocol.HeaderFramework;
+import net.yacy.cora.protocol.RequestHeader;
+import net.yacy.http.servlets.DisallowedMethodException;
+import net.yacy.http.servlets.TemplateMissingParameterException;
+import net.yacy.search.Switchboard;
+import net.yacy.search.SwitchboardConstants;
+import net.yacy.server.serverObjects;
+
+/**
+ * This class provides transaction tokens generation and checking for protected operations.
+ * These tokens should be designed to be hard to forge by an unauthenticated user.
+ */
+public class TransactionManager {
+
+ /** Parameter name of the transaction token */
+ public static final String TRANSACTION_TOKEN_PARAM = "transactionToken";
+
+ /** Secret signing key valid until next server restart */
+ private static final String SIGNING_KEY = UUID.randomUUID().toString();
+
+ /** Random token seed valid until next server restart */
+ private static final String TOKEN_SEED = UUID.randomUUID().toString();
+
+ /**
+ * @param header
+ * current request header. Must not be null.
+ * @return the name of the currently authenticated user (administrator user
+ * name when the request comes from local host and unauthenticated local
+ * access as administrator is enabled), or null when no authenticated.
+ * @throws NullPointerException
+ * when header parameter is null.
+ */
+ private static String getCurrentUserName(final RequestHeader header) {
+ String userName = header.getRemoteUser();
+
+ if (userName == null && header.accessFromLocalhost() && Switchboard.getSwitchboard() != null) {
+ final String adminAccountUserName = Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, "admin");
+ final String adminAccountBase64MD5 = Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "");
+
+ if(Switchboard.getSwitchboard()
+ .getConfigBool(SwitchboardConstants.ADMIN_ACCOUNT_FOR_LOCALHOST, false)) {
+ /* Unauthenticated local access as administrator can be enabled */
+ userName = adminAccountUserName;
+ } else {
+ /* authorization by encoded password, only for localhost access (used by bash scripts)*/
+ String pass = Base64Order.standardCoder.encodeString(adminAccountUserName + ":" + adminAccountBase64MD5);
+
+ /* get the authorization string from the header */
+ final String realmProp = (header.get(RequestHeader.AUTHORIZATION, "")).trim();
+ final String realmValue = realmProp.isEmpty() ? null : realmProp.substring(6); // take out "BASIC "
+
+ if (pass.equals(realmValue)) { // assume realmValue as is in cfg
+ userName = adminAccountUserName;
+ }
+ }
+ }
+
+ return userName;
+ }
+
+ /**
+ * Get a transaction token to be used later on a protected HTTP post method
+ * call on the same path with the currently authenticated user.
+ *
+ * @param header
+ * current request header
+ * @return a transaction token
+ * @throws IllegalArgumentException
+ * when header parameter is null or when the user is not authenticated.
+ */
+ public static String getTransactionToken(final RequestHeader header) {
+ if (header == null) {
+ throw new IllegalArgumentException("Missing required header parameter");
+ }
+
+ return getTransactionToken(header, header.getPathInfo());
+ }
+
+ /**
+ * Get a transaction token to be used later on a protected HTTP post method
+ * call on the specified path with the currently authenticated user.
+ *
+ * @param header
+ * current request header
+ * @param path the relative path for which the token will be valid
+ * @return a transaction token for the specified path
+ * @throws IllegalArgumentException
+ * when a parameter is null or when the user is not authenticated.
+ */
+ public static String getTransactionToken(final RequestHeader header, final String path) {
+ if (header == null) {
+ throw new IllegalArgumentException("Missing required header parameter");
+ }
+
+ /* Check this comes from an authenticated user */
+ final String userName = getCurrentUserName(header);
+ if (userName == null) {
+ throw new IllegalArgumentException("User is not authenticated");
+ }
+
+ /* Produce a token by signing a message with the server secret key :
+ * The token is not unique per request and thus keeps the service stateless
+ * (no need to store tokens until they are consumed).
+ * On the other hand, it is supposed to remain hard enough to forge because the secret key and token seed
+ * are initialized with a random value at each server startup */
+ final String token = HmacUtils.hmacSha1Hex(SIGNING_KEY, TOKEN_SEED + userName + path);
+
+
+ return token;
+ }
+
+ /**
+ * Check the current request is a valid HTTP POST transaction : the current user is authenticated,
+ * and the request post parameters contain a valid transaction token.
+ * @param header current request header
+ * @param post request parameters
+ * @throws IllegalArgumentException when a parameter is null.
+ * @throws DisallowedMethodException when the HTTP method is something else than post
+ * @throws TemplateMissingParameterException when the transaction token is missing
+ * @throws BadTransactionException when a condition for valid transaction is not met.
+ */
+ public static void checkPostTransaction(final RequestHeader header, final serverObjects post) {
+ if (header == null || post == null) {
+ throw new IllegalArgumentException("Missing required parameters.");
+ }
+
+ if(!HeaderFramework.METHOD_POST.equals(header.getMethod())) {
+ throw new DisallowedMethodException("HTTP POST method is the only one authorized.");
+ }
+
+ String userName = getCurrentUserName(header);
+ if (userName == null) {
+ throw new BadTransactionException("User is not authenticated.");
+ }
+
+ final String transactionToken = post.get(TRANSACTION_TOKEN_PARAM);
+ if(transactionToken == null) {
+ throw new TemplateMissingParameterException("Missing transaction token.");
+ }
+
+ final String token = HmacUtils.hmacSha1Hex(SIGNING_KEY, TOKEN_SEED + userName + header.getPathInfo());
+
+ /* Compare the server generated token with the one received in the post parameters,
+ * using a time constant function */
+ if(!MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8), transactionToken.getBytes(StandardCharsets.UTF_8))) {
+ throw new BadTransactionException("Invalid transaction token.");
+ }
+ }
+
+}
diff --git a/source/net/yacy/http/servlets/DisallowedMethodException.java b/source/net/yacy/http/servlets/DisallowedMethodException.java
new file mode 100644
index 000000000..3245abaf7
--- /dev/null
+++ b/source/net/yacy/http/servlets/DisallowedMethodException.java
@@ -0,0 +1,47 @@
+// DisallowedMethodException.java
+// Copyright 2017 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package net.yacy.http.servlets;
+
+/**
+ * Exception indicating a request HTTP method is not allowed.
+ *
+ */
+public class DisallowedMethodException extends RuntimeException {
+
+ /** Generated serialization id */
+ private static final long serialVersionUID = -8128202785152200358L;
+
+ /**
+ * Default constructor : use generic message
+ */
+ public DisallowedMethodException() {
+ super("HTTP method is not allowed");
+ }
+
+ /**
+ * @param message detail message
+ */
+ public DisallowedMethodException(String message) {
+ super(message);
+ }
+
+}
diff --git a/source/net/yacy/http/servlets/YaCyDefaultServlet.java b/source/net/yacy/http/servlets/YaCyDefaultServlet.java
index 9749a2a51..f297bb8e6 100644
--- a/source/net/yacy/http/servlets/YaCyDefaultServlet.java
+++ b/source/net/yacy/http/servlets/YaCyDefaultServlet.java
@@ -69,7 +69,9 @@ import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.protocol.ResponseHeader;
import net.yacy.cora.util.ByteBuffer;
import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.data.BadTransactionException;
import net.yacy.data.InvalidURLLicenceException;
+import net.yacy.data.TransactionManager;
import net.yacy.kelondro.util.FileUtils;
import net.yacy.kelondro.util.MemoryControl;
import net.yacy.kelondro.util.NamePrefixThreadFactory;
@@ -882,15 +884,27 @@ public class YaCyDefaultServlet extends HttpServlet {
}
} catch(InvocationTargetException e) {
if(e.getCause() instanceof InvalidURLLicenceException) {
- /* A non authaurized user is trying to fetch a image with a bad or already released license code */
+ /* A non authorized user is trying to fetch a image with a bad or already released license code */
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getCause().getMessage());
return;
}
+ if(e.getCause() instanceof BadTransactionException) {
+ /* A request for a protected page with server-side effects failed because the transaction is not valid :
+ * for example because missing or invalid transaction token*/
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getCause().getMessage()
+ + " If you sent this request with a web browser, please refresh the origin page.");
+ return;
+ }
if(e.getCause() instanceof TemplateMissingParameterException) {
/* A template is used but miss some required parameter */
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getCause().getMessage());
return;
}
+ if(e.getCause() instanceof DisallowedMethodException) {
+ /* The request was sent using an disallowed HTTP method */
+ response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, e.getCause().getMessage());
+ return;
+ }
ConcurrentLog.logException(e);
throw new ServletException(targetFile.getAbsolutePath());
} catch (IllegalArgumentException | IllegalAccessException e) {
@@ -983,6 +997,12 @@ public class YaCyDefaultServlet extends HttpServlet {
} else {
templatePatterns = new servletProperties((serverObjects) tmp);
}
+
+ if(templatePatterns.containsKey(TransactionManager.TRANSACTION_TOKEN_PARAM)) {
+ /* The response contains a transaction token : we also write the transaction token as a custom header
+ * to allow usage by external tools (such as curl or wget) without the need to parse HTML */
+ response.setHeader(HeaderFramework.X_YACY_TRANSACTION_TOKEN, templatePatterns.get(TransactionManager.TRANSACTION_TOKEN_PARAM));
+ }
// handle YaCy http commands
// handle action auth: check if the servlets requests authentication
diff --git a/source/net/yacy/yacy.java b/source/net/yacy/yacy.java
index dfba00ca0..6adab08a3 100644
--- a/source/net/yacy/yacy.java
+++ b/source/net/yacy/yacy.java
@@ -38,24 +38,34 @@ import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import org.apache.http.Header;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.mime.content.ContentBody;
+
import com.google.common.io.Files;
import net.yacy.cora.date.GenericFormatter;
+import net.yacy.cora.document.encoding.UTF8;
import net.yacy.cora.document.id.DigestURL;
+import net.yacy.cora.document.id.MultiProtocolURL;
import net.yacy.cora.federate.yacy.CacheStrategy;
import net.yacy.cora.order.Digest;
import net.yacy.cora.protocol.ClientIdentification;
import net.yacy.cora.protocol.ConnectionInfo;
+import net.yacy.cora.protocol.HeaderFramework;
import net.yacy.cora.protocol.TimeoutRequest;
import net.yacy.cora.protocol.http.HTTPClient;
import net.yacy.cora.sorting.Array;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.crawler.retrieval.Response;
+import net.yacy.data.TransactionManager;
import net.yacy.data.Translator;
import net.yacy.document.Document;
import net.yacy.gui.YaCyApp;
@@ -545,7 +555,10 @@ public final class yacy {
// start up
System.out.println(copyright);
System.out.println(hline);
- submitURL(homePath, "Steering.html?shutdown=", "Terminate YaCy");
+
+ final LinkedHashMap<String,ContentBody> post = new LinkedHashMap<String,ContentBody>();
+ post.put("shutdown", UTF8.StringBody(""));
+ submitPostURL(homePath, "Steering.html", "Terminate YaCy", post);
}
public static void update(final File homePath) {
@@ -555,6 +568,77 @@ public final class yacy {
submitURL(homePath, "ConfigUpdate_p.html?autoUpdate=", "Update YaCy to most recent version");
}
+ /**
+ * Submits post data to the local peer URL, authenticating as administrator
+ * @param homePath directory containing YaCy DATA folder
+ * @param path url relative path part
+ * @param processdescription description of the operation for logging purpose
+ * @param post data to post
+ */
+ private static void submitPostURL(final File homePath, final String path, final String processdescription, final Map<String, ContentBody> post) {
+ final Properties config = configuration("COMMAND-STEERING", homePath);
+
+ // read port
+ final int port = Integer.parseInt(config.getProperty(SwitchboardConstants.SERVER_PORT, "8090"));
+
+ // read password
+ final String encodedPassword = config.getProperty(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "");
+ final String adminUser = config.getProperty(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, "admin");
+
+ // send 'wget' to web interface
+ final HTTPClient con = new HTTPClient(ClientIdentification.yacyInternetCrawlerAgent);
+ // con.setHeader(requestHeader.entrySet());
+ try {
+ /* First get a valid transaction token using HTTP GET */
+ con.GETbytes("http://localhost:"+ port +"/" + path, adminUser, encodedPassword, false);
+
+ if (con.getStatusCode() != HttpStatus.SC_OK) {
+ throw new IOException("Error response from YACY socket: " + con.getHttpResponse().getStatusLine());
+ }
+
+ final Header transactionTokenHeader = con.getHttpResponse().getFirstHeader(HeaderFramework.X_YACY_TRANSACTION_TOKEN);
+ if(transactionTokenHeader == null) {
+ throw new IOException("Could not retrieve a valid transaction token");
+ }
+
+ /* Then POST the request */
+ post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, UTF8.StringBody(transactionTokenHeader.getValue()));
+ con.POSTbytes(new MultiProtocolURL("http://localhost:"+ port +"/" + path), null, post, adminUser, encodedPassword, false, false);
+ if (con.getStatusCode() >= HttpStatus.SC_OK && con.getStatusCode() < HttpStatus.SC_MULTIPLE_CHOICES) {
+ ConcurrentLog.config("COMMAND-STEERING", "YACY accepted steering command: " + processdescription);
+ } else {
+ ConcurrentLog.severe("COMMAND-STEERING", "error response from YACY socket: " + con.getHttpResponse().getStatusLine());
+
+ try {
+ HTTPClient.closeConnectionManager();
+ } catch (final InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ System.exit(-1);
+ }
+ } catch (final IOException e) {
+ ConcurrentLog.severe("COMMAND-STEERING", "could not establish connection to YACY socket: " + e.getMessage());
+
+ try {
+ HTTPClient.closeConnectionManager();
+ } catch (final InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ System.exit(-1);
+ }
+
+ try {
+ HTTPClient.closeConnectionManager();
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // finished
+ ConcurrentLog.config("COMMAND-STEERING", "SUCCESSFULLY FINISHED COMMAND: " + processdescription);
+ }
+
private static void submitURL(final File homePath, final String path, final String processdescription) {
final Properties config = configuration("COMMAND-STEERING", homePath);
diff --git a/stopYACY.sh b/stopYACY.sh
index 1109eaad0..e643bdcb2 100755
--- a/stopYACY.sh
+++ b/stopYACY.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
cd `dirname $0`
-(bin/apicall.sh "Steering.html?shutdown=true" > /dev/null && \
+(bin/protectedPostApiCall.sh "Steering.html" "shutdown=true" > /dev/null && \
echo "Please wait until the YaCy daemon process terminates [wget]" && \
echo "You can monitor this with 'tail -f DATA/LOG/yacy00.log' and 'fuser log/yacy00.log'") || \