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
pull/122/head
luccioman 8 years ago
parent df5970df6d
commit cde237b687

@ -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
./protectedPostApiCall.sh "IndexControlURLs_p.html" "deletecomplete=&deleteIndex=on&deleteSolr=on&deleteCrawlQueues=on&deleteRobots=on&deleteSearchFl=on&deleteCache=on"

@ -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="

@ -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"

@ -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"

@ -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)

@ -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

@ -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]#" />

@ -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");

@ -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;

@ -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()) {

@ -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>

@ -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) {

@ -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>

@ -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);
}
}

@ -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="" />

@ -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());

@ -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>

@ -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();

@ -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

@ -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()) {

@ -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">

@ -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));

@ -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.

@ -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>

@ -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;
}

@ -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>

@ -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>

@ -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";

@ -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);
}
}

@ -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.");
}
}
}

@ -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);
}
}

@ -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

@ -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);

@ -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'") || \

Loading…
Cancel
Save