Alternc  latest
Alternc logiel libre pour l'hébergement
m_cron.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  ----------------------------------------------------------------------
5  LICENSE
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License (GPL)
9  as published by the Free Software Foundation; either version 2
10  of the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  To read the license please visit http://www.gnu.org/copyleft/gpl.html
18  ----------------------------------------------------------------------
19 */
20 
21 /**
22  * This class manage web-cron tasks
23  *
24  * @copyright AlternC-Team 2000-2017 https://alternc.com/
25  */
26 class m_cron {
27 
28  const MAX_SOCKETS = 8;
29  const DEFAULT_CAFILE = "/etc/ssl/certs/ca-certificates.crt";
30 
31 
32  /**
33  *
34  */
35  function schedule() {
36  return Array(
37  Array('unit' => 1440, 'name' => _("Daily")),
38  Array('unit' => 60, 'name' => _("Hour")),
39  Array('unit' => 30, 'name' => _("Half Hour")),
40  );
41  }
42 
43 
44  /**
45  * List the crontab for the current user.
46  * @return array an hash for each crontab.
47  */
48  function lst_cron() {
49  global $cuid, $db, $msg;
50  $msg->debug("cron", "lst_cron");
51  $db->query("SELECT * FROM cron WHERE uid = ? ORDER BY url;", array($cuid));
52  $r = Array();
53  while ($db->next_record()) {
54  $tmp = Array();
55  $tmp['id'] = $db->f('id');
56  $tmp['url'] = urldecode($db->f('url'));
57  $tmp['user'] = urldecode($db->f('user'));
58  $tmp['password'] = urldecode($db->f('password'));
59  $tmp['schedule'] = $db->f('schedule');
60  $tmp['email'] = urldecode($db->f('email'));
61  $tmp['next_execution'] = $db->f('next_execution');
62  $r[] = $tmp;
63  }
64  return $r;
65  }
66 
67  /**
68  * Hook called by menu class to add menu to the left panel
69  */
70  function hook_menu() {
71  $obj = array(
72  'title' => _("Scheduled tasks"),
73  'link' => 'cron.php',
74  'pos' => 120,
75  );
76 
77  return $obj;
78  }
79 
80 
81  /**
82  * update the crontab
83  * @param $arr array the crontab information, including its ID
84  * @return boolean TRUE if the crontab has been edited
85  */
86  function update($arr) {
87  $ok = true;
88  foreach ($arr as $a) {
89  if (!isset($a['id'])) {
90  $a['id'] = null;
91  }
92  if (empty($a['url']) && is_null($a['id'])) {
93  continue;
94  }
95  if (!$this->_update_one($a['url'], $a['user'], $a['password'], $a['email'], $a['schedule'], $a['id'])) {
96  $ok = false;
97  }
98  }
99  return $ok;
100  }
101 
102 
103  /**
104  * delete a crontab
105  * @param $id the id of the crontab to delete
106  * @return boolean TRUE if the crontab has been deleted
107  */
108  function delete_one($id) {
109  global $db, $msg, $cuid;
110  $msg->log("cron", "delete_one");
111  return $db->query("DELETE FROM cron WHERE id= ? AND uid= ? LIMIT 1;", array(intval($id), $cuid));
112  }
113 
114 
115  /**
116  * update a crontab,
117  * @return boolean TRUE if the crontab has been edited
118  */
119  private function _update_one($url, $user, $password, $email, $schedule, $id = null) {
120  global $db, $msg, $quota, $cuid;
121  $msg->log("cron", "update_one");
122 
123  if (empty($url) && !is_null($id)) {
124  return $this->delete_one($id);
125  }
126 
127 
128  if (filter_var($url, FILTER_VALIDATE_URL) === false) {
129  $msg->raise("ERROR", "cron", _("URL not valid"));
130  return false;
131  }
132  $url = urlencode($url);
133  $user = urlencode($user);
134  if (empty($user)) {
135  $password = '';
136  }
137  $password = urlencode($password);
138 
139  //@todo remove checkmail cf functions.php
140  if (!empty($email) && !checkmail($email) == 0) {
141  $msg->raise("ERROR", "cron", _("Email address is not valid"));
142  return false;
143  }
144  $email = urlencode($email);
145  if (!$this->valid_schedule($schedule)) {
146  return false;
147  }
148 
149  if (is_null($id)) { // if a new insert, quotacheck
150  $q = $quota->getquota("cron");
151  if ($q["u"] >= $q["t"]) {
152  $msg->raise("ERROR", "cron", _("You quota of cron entries is over. You cannot create more cron entries"));
153  return false;
154  }
155  } else { // if not a new insert, check the $cuid
156  $db->query("SELECT uid FROM cron WHERE id = ? ;", array($id));
157  if (!$db->next_record()) {
158  return "false";
159  } // return false if pb
160  if ($db->f('uid') != $cuid) {
161  $msg->raise("ERROR", "cron", _("Identity problem"));
162  return false;
163  }
164  }
165  return $db->query("REPLACE INTO cron (id, uid, url, user, password, schedule, email) VALUES (?, ?, ?, ?, ?, ?, ?) ;" , array($id, $cuid, $url, $user, $password, $schedule, $email));
166  }
167 
168 
169  /**
170  * validate a crontab schedule
171  * @param $s array schedule paramters
172  * @return boolean TRUE if the schedule is valid
173  */
174  function valid_schedule($s) {
175  $s2 = intval($s);
176  if ($s2 != $s) {
177  return false;
178  }
179  $r = false;
180  foreach ($this->schedule() as $cs) {
181  if ($cs['unit'] == $s) {
182  return true;
183  }
184  }
185  return $r;
186  }
187 
188 
189  /**
190  * hook for quota computation
191  */
192  function hook_quota_get() {
193  global $cuid, $db, $msg;
194  $msg->debug("cron", "alternc_get_quota");
195  $q = Array("name" => "cron", "description" => _("Scheduled tasks"), "used" => 0);
196  $db->query("select count(*) as cnt from cron where uid = ? ;", array($cuid));
197  if ($db->next_record()) {
198  $q['used'] = $db->f('cnt');
199  }
200  return $q;
201  }
202 
203 
204  /**
205  * Execute the required crontab of AlternC users
206  * this function EXIT at the end.
207  */
208  function execute_cron() {
209  global $db,$msg;
210 
211  $msg->debug("cron", "execute_cron");
212  if (!isset($GLOBALS["DEBUG"])) {
213  $GLOBALS["DEBUG"] = false;
214  }
215  $db->query("SELECT id, url, email, schedule, user, password FROM cron WHERE next_execution <= NOW();");
216  $urllist = array();
217 
218  while ($db->next_record()) {
219  $db->Record["url"] = urldecode($db->Record["url"]);
220  $db->Record["user"] = urldecode($db->Record["user"]);
221  $db->Record["email"] = urldecode($db->Record["email"]);
222  $db->Record["password"] = urldecode($db->Record["password"]);
223 
224  // we support only http or https schemes:
225  if (substr($db->Record["url"], 0, 7) == "http://" || substr($db->Record["url"], 0, 8) == "https://") {
226  $u = array(
227  "url" => $db->Record["url"],
228  "id" => $db->Record["id"], "email" => $db->Record["email"],
229  );
230 
231  if ($db->Record["user"] && $db->Record["password"]) {
232  $u["login"] = $db->Record["user"];
233  $u["password"] = $db->Record["password"];
234  }
235  if ($GLOBALS["DEBUG"])
236  echo "Will run cron :\n" . print_r($u, true) . "\n";
237  $urllist[] = $u;
238  }
239 
240  if (empty($urllist)) { // nothing to do :
241  exit(0);
242  }
243  }
244  // cron_callback($url, $content, $curlobj) will be called at the end of each http call.
245  $this->rolling_curl($urllist, array("m_cron", "cron_callback"));
246  }
247 
248 
249  /**
250  * Callback function called by rolling_curl when a cron resulr has been received
251  * schedule it for next run and send the mail if needed
252  */
253  function cron_callback($url, $content, $curl) {
254  global $db, $L_FQDN;
255  if (empty($url["id"])) {
256  return; // not normal...
257  }
258  $id = intval($url["id"]);
259 
260  if ($curl["http_code"] == 200) {
261  $ok = true;
262  } else {
263  $ok = false;
264  }
265  if (isset($url["email"]) && $url["email"] && $content) {
266  if (!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")) {
267  echo "Error sending mail for cron #$id to address '" . $url["email"] . "'\n";
268  }
269  }
270  // now schedule it for next run:
271  $db->query("UPDATE cron SET next_execution=FROM_UNIXTIME( UNIX_TIMESTAMP(NOW()) + schedule * 60) WHERE id= ?", array($id));
272  }
273 
274 
275  /**
276  * Launch parallel (using MAX_SOCKETS sockets maximum) retrieval
277  * of URL using CURL
278  * @param $urls array of associative array, each having the following keys :
279  * url = url to get (of the form http[s]://login:password@host/path/file?querystring )
280  * login & password = if set, tell the login and password to use as simple HTTP AUTH.
281  * - any other key will be sent as it is to the callback function
282  * @param $callback function called for each request when completing. First argument is the $url object, second is the content (output)
283  * 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 ;)
284  * this function should return as soon as possible to allow other curl calls to complete properly.
285  * @param $cursom_options array of custom CURL options for all transfers
286  */
287  function rolling_curl($urls, $callback, $custom_options = null) {
288  // make sure the rolling window isn't greater than the # of urls
289  if (!isset($GLOBALS["DEBUG"]))
290  $GLOBALS["DEBUG"] = false;
291  $rolling_window = m_cron::MAX_SOCKETS;
292  $rolling_window = (count($urls) < $rolling_window) ? count($urls) : $rolling_window;
293 
294  $master = curl_multi_init();
295 
296  // add additional curl options here
297  $std_options = array(CURLOPT_RETURNTRANSFER => true,
298  CURLOPT_FOLLOWLOCATION => false,
299  CURLOPT_CONNECTTIMEOUT => 5,
300  CURLOPT_TIMEOUT => 240, // 4 minutes timeout for a page
301  CURLOPT_USERAGENT => "AlternC (Cron Daemon)",
302  CURLOPT_MAXREDIRS => 0);
303 
304  if ($GLOBALS["DEBUG"]) {
305  $std_options[CURLOPT_VERBOSE] = true;
306  }
307  $options = ($custom_options) ? ($std_options + $custom_options) : $std_options;
308 
309  // start the first batch of requests
310  for ($i = 0; $i < $rolling_window; $i++) {
311  $ch = curl_init();
312  $options[CURLOPT_URL] = $urls[$i]["url"];
313  if ($GLOBALS["DEBUG"]) {
314  echo "URL: " . $urls[$i]["url"] . "\n";
315  }
316  curl_setopt_array($ch, $options);
317  // Handle custom cafile for some https url
318  if (strtolower(substr($options[CURLOPT_URL], 0, 5)) == "https") {
319  curl_setopt($ch, CURLOPT_CAINFO, m_cron::DEFAULT_CAFILE);
320  if ($GLOBALS["DEBUG"]) {
321  echo "cainfo set to DEFAULT\n";
322  }
323  }
324  if (isset($urls[$i]["login"]) && isset($urls[$i]["password"])) { // set basic http authentication
325  curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
326  curl_setopt($ch, CURLOPT_USERPWD, $urls[$i]["login"] . ":" . $urls[$i]["password"]);
327  if ($GLOBALS["DEBUG"]) {
328  echo "set basic auth\n";
329  }
330  }
331  curl_multi_add_handle($master, $ch);
332  }
333 
334  do {
335  while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
336  if ($execrun != CURLM_OK) {
337  break;
338  }
339  // a request was just completed -- find out which one
340  while ($done = curl_multi_info_read($master)) {
341  $info = curl_getinfo($done['handle']);
342  // 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.
343  if ($GLOBALS["DEBUG"]) {
344  echo "Info for " . $done['handle'] . " \n";
345  print_r($info);
346  }
347  if ($info['http_code'] == 200) {
348  $output = curl_multi_getcontent($done['handle']);
349  } else {
350  // request failed. add error handling.
351  $output = "";
352  }
353  // request terminated. process output using the callback function.
354  // Pass the url array to the callback, so we need to search it
355  foreach ($urls as $url) {
356  if ($url["url"] == $info["url"]) {
357  call_user_func($callback, $url, $output, $info);
358  break;
359  }
360  }
361 
362  // If there is more: start a new request
363  // (it's important to do this before removing the old one)
364  if ($i < count($urls)) {
365  $ch = curl_init();
366  $options[CURLOPT_URL] = $urls[$i++]; // increment i
367  curl_setopt_array($ch, $options);
368  if (strtolower(substr($options[CURLOPT_URL], 0, 5)) == "https") {
369  curl_setopt($ch, CURLOPT_CAINFO, m_cron::DEFAULT_CAFILE);
370  if ($GLOBALS["DEBUG"]) {
371  echo "cainfo set to DEFAULT\n";
372  }
373  }
374  if (isset($urls[$i]["login"]) && isset($urls[$i]["password"])) { // set basic http authentication
375  curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
376  curl_setopt($ch, CURLOPT_USERPWD, urlencode($urls[$i]["login"]) . ":" . urlencode($urls[$i]["password"]));
377  if ($GLOBALS["DEBUG"]) {
378  echo "set basic auth\n";
379  }
380  }
381  curl_multi_add_handle($master, $ch);
382  }
383  // remove the curl handle that just completed
384  curl_multi_remove_handle($master, $done['handle']);
385  }
386  } while ($running);
387 
388  curl_multi_close($master);
389  return true;
390  }
391 
392 } /* Class cron */
exit
Definition: adm_doadd.php:70
global $db
Definition: bootstrap.php:26
$msg
Definition: bootstrap.php:75
$cuid
Definition: bootstrap.php:43
$r
Definition: aws_add.php:75
$content
Definition: bro_editor.php:91
This class manage web-cron tasks.
Definition: m_cron.php:26
update($arr)
update the crontab
Definition: m_cron.php:86
delete_one($id)
delete a crontab
Definition: m_cron.php:108
rolling_curl($urls, $callback, $custom_options=null)
Launch parallel (using MAX_SOCKETS sockets maximum) retrieval of URL using CURL.
Definition: m_cron.php:287
schedule()
Definition: m_cron.php:35
hook_menu()
Hook called by menu class to add menu to the left panel.
Definition: m_cron.php:70
const DEFAULT_CAFILE
Definition: m_cron.php:29
_update_one($url, $user, $password, $email, $schedule, $id=null)
update a crontab,
Definition: m_cron.php:119
lst_cron()
List the crontab for the current user.
Definition: m_cron.php:48
const MAX_SOCKETS
Definition: m_cron.php:28
cron_callback($url, $content, $curl)
Callback function called by rolling_curl when a cron resulr has been received schedule it for next ru...
Definition: m_cron.php:253
valid_schedule($s)
validate a crontab schedule
Definition: m_cron.php:174
execute_cron()
Execute the required crontab of AlternC users this function EXIT at the end.
Definition: m_cron.php:208
hook_quota_get()
hook for quota computation
Definition: m_cron.php:192
checkmail($mail)
Check an email address, use filter_var with emails, which works great ;)
Definition: functions.php:244
$q
Definition: menu_aws.php:32
$user
Definition: bootstrap.php:84
$password
Definition: bootstrap.php:85
$i
if(!isset($delete)) $ok
Definition: ssl_delete.php:41
if(!isset($is_include)) if(! $key &&! $crt) $id
if($error) $info