diff --git a/bureau/class/m_cron.php b/bureau/class/m_cron.php index 3a0f2eb6..74b29c07 100644 --- a/bureau/class/m_cron.php +++ b/bureau/class/m_cron.php @@ -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 ($i0,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(); + +