diff options
| author | luccioman <luccioman@users.noreply.github.com> | 2017-03-26 11:48:00 +0200 |
|---|---|---|
| committer | luccioman <luccioman@users.noreply.github.com> | 2017-03-26 11:48:00 +0200 |
| commit | cde237b68763c542da20038e5f62bea341ae1d37 (patch) | |
| tree | a8a55d4425e9ad778e5737d920458a0dd8639abc | |
| parent | df5970df6d4de27ef96641aadc2591c219e87a36 (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
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¶m2=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"/>
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&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"/> <select name="releaseinstall"> #(/downloadsAvailable)# #{downloadedreleases}# @@ -46,8 +46,9 @@ #{/downloadedreleases}# #(downloadsAvailable)#::</select>#(/downloadsAvailable)# #(deployenabled)#::no automated installation on development environments:: + <input type="hidden" name="transactionToken" value="#[transactionToken]#"/> <input type="submit" name="update" class="btn btn-primary" value="Install Release" #(buttonsActive)#disabled="disabled"::#(/buttonsActive)#/> - <input type="submit" name="deleteRelease" class="btn btn-danger" value="Delete Release" #(buttonsActive)#disabled="disabled"::#(/buttonsActive)#/> + <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"> </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", "<" + errmsg + ">"); @@ -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> </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> </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
<input type="submit" class="btn btn-primary" name="setStartup" value="Set" />
- #(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> </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"><Search Form></a>
<a href="CrawlStartSite.html"><Crawl Start></a>
<a href="Status.html"><Status Page></a>
-<a href="Steering.html?shutdown=" onclick="return confirm('Confirm Shutdown')"><Shutdown></a>
+<a href="Steering.html"><Shutdown></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'") || \ |
