fixing entirely the cron execution shell. This may fix a potential privilege escalation problem.
This commit is contained in:
parent
0301409dbf
commit
c2d8b317d0
|
@ -28,6 +28,8 @@
|
|||
*/
|
||||
class m_cron {
|
||||
|
||||
const MAX_SOCKETS=8;
|
||||
const DEFAULT_CAFILE="/etc/ssl/certs/ca-certificates.crt";
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/** Constructor
|
||||
|
@ -188,5 +190,169 @@ class m_cron {
|
|||
return $q;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Execute the required crontab of AlternC users
|
||||
* this function EXIT at the end.
|
||||
*/
|
||||
function execute_cron() {
|
||||
global $db;
|
||||
|
||||
$db->query("SELECT id, url, email, schedule, user, password FROM cron WHERE next_execution <= NOW();");
|
||||
$urllist=array();
|
||||
|
||||
while ($db->next_record()) {
|
||||
$db->Record["url"]=urldecode($db->Record["url"]);
|
||||
// we support only http or https schemes:
|
||||
if (substr($db->Record["url"],0,7)=="http://" || substr($db->Record["url"],0,8)=="https://") {
|
||||
$u=array(
|
||||
"url" => $db->Record["url"],
|
||||
"id" => $db->Record["id"], "email" =>$db->Record["email"],
|
||||
);
|
||||
|
||||
if ($db->Record["user"] && $db->Record["password"]) {
|
||||
$u["login"]=$db->Record["user"];
|
||||
$u["password"]=$db->Record["password"];
|
||||
}
|
||||
$urllist[]=$u;
|
||||
}
|
||||
|
||||
if (empty($urllist)) { // nothing to do :
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// cron_callback($url, $content, $curlobj) will be called at the end of each http call.
|
||||
$this->rolling_curl($urllist, array("m_cron","cron_callback"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Callback function called by rolling_curl when a cron resulr has been received
|
||||
* schedule it for next run and send the mail if needed
|
||||
*/
|
||||
function cron_callback($url,$content,$curl) {
|
||||
global $db,$L_FQDN;
|
||||
if (empty($url["id"])) return; // not normal...
|
||||
$id=intval($url["id"]);
|
||||
|
||||
if ($curl["http_code"]==200) {
|
||||
$ok=true;
|
||||
} else {
|
||||
$ok=false;
|
||||
}
|
||||
if (isset($url["email"]) && $url["email"] && $content) {
|
||||
mail($url["email"],"AlternC Cron #$id - Report ".date("%r"),"Please find below the stdout content produced by your cron task.\n------------------------------------------------------------\n\n".$content,"From: postmaster@$L_FQDN");
|
||||
}
|
||||
// now schedule it for next run:
|
||||
$db->query("UPDATE cron SET next_execution=FROM_UNIXTIME( UNIX_TIMESTAMP(NOW()) + schedule * 60) WHERE id=$id");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Launch parallel (using MAX_SOCKETS sockets maximum) retrieval
|
||||
* of URL using CURL
|
||||
* @param $urls array of associative array, each having the following keys :
|
||||
* url = url to get (of the form http[s]://login:password@host/path/file?querystring )
|
||||
* login & password = if set, tell the login and password to use as simple HTTP AUTH.
|
||||
* - any other key will be sent as it is to the callback function
|
||||
* @param $callback function called for each request when completing. First argument is the $url object, second is the content (output)
|
||||
* third is the info structure from curl for the returned page. 200 for OK, 403 for AUTH FAILED, 0 for timeout, dump it to know it ;)
|
||||
* this function should return as soon as possible to allow other curl calls to complete properly.
|
||||
* @param $cursom_options array of custom CURL options for all transfers
|
||||
*/
|
||||
function rolling_curl($urls, $callback, $custom_options = null) {
|
||||
// make sure the rolling window isn't greater than the # of urls
|
||||
if (!isset($GLOBALS["DEBUG"])) $GLOBALS["DEBUG"]=false;
|
||||
$rolling_window = m_cron::MAX_SOCKETS;
|
||||
$rolling_window = (count($urls) < $rolling_window) ? count($urls) : $rolling_window;
|
||||
|
||||
$master = curl_multi_init();
|
||||
$curl_arr = array();
|
||||
|
||||
// add additional curl options here
|
||||
$std_options = array(CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_TIMEOUT => 240, // 4 minutes timeout for a page
|
||||
CURLOPT_USERAGENT => "AlternC (Cron Daemon)",
|
||||
CURLOPT_MAXREDIRS => 0);
|
||||
|
||||
if ($GLOBALS["DEBUG"]) $std_options[CURLOPT_VERBOSE]=true;
|
||||
$options = ($custom_options) ? ($std_options + $custom_options) : $std_options;
|
||||
|
||||
// start the first batch of requests
|
||||
for ($i = 0; $i < $rolling_window; $i++) {
|
||||
$ch = curl_init();
|
||||
$options[CURLOPT_URL] = $urls[$i]["url"];
|
||||
if ($GLOBALS["DEBUG"]) echo "URL: ".$urls[$i]["url"]."\n";
|
||||
curl_setopt_array($ch,$options);
|
||||
// Handle custom cafile for some https url
|
||||
if (strtolower(substr($options[CURLOPT_URL],0,5))=="https") {
|
||||
curl_setopt($ch,CURLOPT_CAINFO,m_cron::DEFAULT_CAFILE);
|
||||
if ($GLOBALS["DEBUG"]) echo "cainfo set to DEFAULT\n";
|
||||
}
|
||||
if (isset($urls[$i]["login"]) && isset($urls[$i]["password"])) { // set basic http authentication
|
||||
curl_setopt($ch,CURLOPT_HTTPAUTH,CURLAUTH_BASIC);
|
||||
curl_setopt($ch,CURLOPT_USERPWD,urlencode($urls[$i]["login"]).":".urlencode($urls[$i]["password"]));
|
||||
if ($GLOBALS["DEBUG"]) echo "set basic auth\n";
|
||||
}
|
||||
curl_multi_add_handle($master, $ch);
|
||||
}
|
||||
|
||||
do {
|
||||
while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
|
||||
if($execrun != CURLM_OK)
|
||||
break;
|
||||
// a request was just completed -- find out which one
|
||||
while($done = curl_multi_info_read($master)) {
|
||||
$info = curl_getinfo($done['handle']);
|
||||
// TODO : since ssl_verify_result is buggy, if we have [header_size] => 0 && [request_size] => 0 && [http_code] => 0, AND https, we can pretend the SSL certificate is buggy.
|
||||
if ($GLOBALS["DEBUG"]) { echo "Info for ".$done['handle']." \n"; print_r($info); }
|
||||
if ($info['http_code'] == 200) {
|
||||
$output = curl_multi_getcontent($done['handle']);
|
||||
} else {
|
||||
// request failed. add error handling.
|
||||
$output="";
|
||||
}
|
||||
// request terminated. process output using the callback function.
|
||||
// Pass the url array to the callback, so we need to search it
|
||||
foreach($urls as $url) {
|
||||
if ($url["url"]==$info["url"]) {
|
||||
call_user_func($callback,$url,$output,$info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is more: start a new request
|
||||
// (it's important to do this before removing the old one)
|
||||
if ($i<count($urls)) {
|
||||
$ch = curl_init();
|
||||
$options[CURLOPT_URL] = $urls[$i++]; // increment i
|
||||
curl_setopt_array($ch,$options);
|
||||
if (strtolower(substr($options[CURLOPT_URL],0,5))=="https") {
|
||||
curl_setopt($ch,CURLOPT_CAINFO,m_cron::DEFAULT_CAFILE);
|
||||
if ($GLOBALS["DEBUG"]) echo "cainfo set to DEFAULT\n";
|
||||
}
|
||||
if (isset($urls[$i]["login"]) && isset($urls[$i]["password"])) { // set basic http authentication
|
||||
curl_setopt($ch,CURLOPT_HTTPAUTH,CURLAUTH_BASIC);
|
||||
curl_setopt($ch,CURLOPT_USERPWD,urlencode($urls[$i]["login"]).":".urlencode($urls[$i]["password"]));
|
||||
if ($GLOBALS["DEBUG"]) echo "set basic auth\n";
|
||||
}
|
||||
curl_multi_add_handle($master, $ch);
|
||||
}
|
||||
// remove the curl handle that just completed
|
||||
curl_multi_remove_handle($master, $done['handle']);
|
||||
}
|
||||
} while ($running);
|
||||
|
||||
curl_multi_close($master);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} /* Class cron */
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/php -q
|
||||
<?php
|
||||
|
||||
# FIXME relecture + commentaires
|
||||
/**
|
||||
* Launch the users crontab for AlternC
|
||||
* php, parallel-curl, secured mode.
|
||||
**/
|
||||
|
||||
for CONFIG_FILE in \
|
||||
/etc/alternc/local.sh \
|
||||
/usr/lib/alternc/functions.sh
|
||||
do
|
||||
if [ ! -r "$CONFIG_FILE" ]; then
|
||||
echo "Can't access $CONFIG_FILE."
|
||||
exit 1
|
||||
fi
|
||||
. "$CONFIG_FILE"
|
||||
done
|
||||
require_once("/usr/share/alternc/panel/class/config_nochk.php");
|
||||
ini_set("display_errors", 1);
|
||||
|
||||
stop_if_jobs_locked
|
||||
|
||||
max_process=2
|
||||
|
||||
tasks () {
|
||||
$MYSQL_DO "select id, url, if(length(email)>0,email,'null'), schedule, UNIX_TIMESTAMP(), user, password as now from cron c where next_execution <= now();" | while read id url email schedule now user password ; do
|
||||
echo $id $url \"$email\" $schedule $now \"$user\" \"$password\"
|
||||
done
|
||||
if (file_exists("/var/run/alternc/jobs-lock")) {
|
||||
echo "jobs-lock exists, did you ran alternc.install?\n";
|
||||
echo "canceling cron_users\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
tasks | xargs -n 7 -P $max_process --no-run-if-empty /usr/lib/alternc/cron_users_doit.sh
|
||||
if (isset($argv[1]) && $argv[1]=="debug") {
|
||||
$GLOBALS["DEBUG"]=true;
|
||||
}
|
||||
|
||||
$cron->execute_cron();
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue