2012-04-06 10:10:36 +00:00
< ? php
/*
2012-08-26 10:30:38 +00:00
----------------------------------------------------------------------
AlternC - Web Hosting System
Copyright ( C ) 2000 - 2012 by the AlternC Development Team .
https :// alternc . org /
----------------------------------------------------------------------
2012-04-06 10:10:36 +00:00
LICENSE
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License ( GPL )
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 .
To read the license please visit http :// www . gnu . org / copyleft / gpl . html
----------------------------------------------------------------------
Purpose of file : Manage hook system .
----------------------------------------------------------------------
*/
2012-08-26 10:30:38 +00:00
2012-04-06 10:10:36 +00:00
/**
2012-08-26 10:30:38 +00:00
* This class manage web - cron tasks
2012-04-06 10:10:36 +00:00
*/
class m_cron {
2014-11-25 13:36:20 +00:00
const MAX_SOCKETS = 8 ;
const DEFAULT_CAFILE = " /etc/ssl/certs/ca-certificates.crt " ;
2012-08-26 10:30:38 +00:00
2012-04-06 10:10:36 +00:00
/*---------------------------------------------------------------------------*/
/** Constructor
*/
function m_cron () {
}
2012-09-13 14:23:18 +00:00
2012-04-06 10:10:36 +00:00
function schedule () {
2012-08-26 10:30:38 +00:00
return Array (
Array ( 'unit' => 1440 , 'name' => _ ( " Daily " )),
Array ( 'unit' => 60 , 'name' => _ ( " Hour " )),
Array ( 'unit' => 30 , 'name' => _ ( " Half Hour " )),
);
2012-04-06 10:10:36 +00:00
}
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** List the crontab for the current user .
* @ return array an hash for each crontab .
*/
2012-04-06 10:10:36 +00:00
function lst_cron () {
global $cuid , $db , $err ;
$err -> log ( " cron " , " lst_cron " );
2012-08-26 10:30:38 +00:00
$db -> query ( " SELECT * FROM cron WHERE uid = $cuid ORDER BY url; " );
2012-04-06 10:10:36 +00:00
$r = Array ();
while ( $db -> next_record ()) {
$tmp = Array ();
$tmp [ 'id' ] = $db -> f ( 'id' );
$tmp [ 'url' ] = urldecode ( $db -> f ( 'url' ));
$tmp [ 'user' ] = urldecode ( $db -> f ( 'user' ));
$tmp [ 'password' ] = urldecode ( $db -> f ( 'password' ));
$tmp [ 'schedule' ] = $db -> f ( 'schedule' );
$tmp [ 'email' ] = urldecode ( $db -> f ( 'email' ));
2012-10-16 14:31:56 +00:00
$tmp [ 'next_execution' ] = $db -> f ( 'next_execution' );
2012-04-06 10:10:36 +00:00
$r [] = $tmp ;
}
return $r ;
}
2013-02-18 10:01:28 +00:00
function hook_menu () {
$obj = array (
'title' => _ ( " Scheduled tasks " ),
'ico' => 'images/schedule.png' ,
'link' => 'cron.php' ,
'pos' => 90 ,
) ;
return $obj ;
}
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** update the crontab
* @ param $arr array the crontab information , including its ID
* @ return boolean TRUE if the crontab has been edited
*/
2012-04-06 10:10:36 +00:00
function update ( $arr ) {
$ok = true ;
foreach ( $arr as $a ) {
if ( ! isset ( $a [ 'id' ])) $a [ 'id' ] = null ;
if ( empty ( $a [ 'url' ]) && is_null ( $a [ 'id' ])) continue ;
2012-08-26 10:30:38 +00:00
if ( ! $this -> _update_one ( $a [ 'url' ], $a [ 'user' ], $a [ 'password' ], $a [ 'email' ], $a [ 'schedule' ], $a [ 'id' ]) ) {
2012-04-06 10:10:36 +00:00
$ok = false ;
}
}
return $ok ;
}
2012-08-26 10:30:38 +00:00
2012-04-06 10:10:36 +00:00
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** delete a crontab
* @ param $id the id of the crontab to delete
* @ return boolean TRUE if the crontab has been deleted
*/
2012-04-06 10:10:36 +00:00
function delete_one ( $id ) {
global $db , $err , $cuid ;
$err -> log ( " cron " , " delete_one " );
2012-08-26 10:30:38 +00:00
return $db -> query ( " DELETE FROM cron WHERE id= " . intval ( $id ) . " AND uid= $cuid LIMIT 1; " );
2012-04-06 10:10:36 +00:00
}
2012-08-26 10:30:38 +00:00
2012-04-06 10:10:36 +00:00
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** update a crontab ,
* @ return boolean TRUE if the crontab has been edited
*/
private function _update_one ( $url , $user , $password , $email , $schedule , $id = null ) {
2012-04-06 10:10:36 +00:00
global $db , $err , $quota , $cuid ;
$err -> log ( " cron " , " update_one " );
if ( empty ( $url ) && ! is_null ( $id )) {
return $this -> delete_one ( $id );
}
2012-09-13 14:23:18 +00:00
if ( filter_var ( $url , FILTER_VALIDATE_URL ) === false ){
$err -> raise ( " cron " , _ ( " URL not valid " ));
return false ;
}
2012-10-16 14:31:56 +00:00
$url = urlencode ( $url );
$user = urlencode ( $user );
2012-04-06 10:10:36 +00:00
if ( empty ( $user )) $password = '' ;
2012-10-16 14:31:56 +00:00
$password = urlencode ( $password );
2014-03-26 13:29:00 +00:00
//@todo remove checkmail cf functions.php
2012-10-17 11:54:42 +00:00
if ( ! empty ( $email ) && ! checkmail ( $email ) == 0 ){
2012-09-13 14:23:18 +00:00
$err -> raise ( " cron " , _ ( " Email address is not valid " ));
return false ;
}
2012-10-16 14:31:56 +00:00
$email = urlencode ( $email );
2012-04-06 10:10:36 +00:00
if ( ! $this -> valid_schedule ( $schedule )) return false ;
if ( is_null ( $id )) { // if a new insert, quotacheck
$q = $quota -> getquota ( " cron " );
if ( $q [ " u " ] >= $q [ " t " ] ) {
2012-10-18 07:52:54 +00:00
$err -> raise ( " cron " , _ ( " You quota of cron entries is over. You cannot create more cron entries " ));
2012-04-06 10:10:36 +00:00
return false ;
}
} else { // if not a new insert, check the $cuid
2012-08-26 10:30:38 +00:00
$db -> query ( " SELECT uid FROM cron WHERE id = $id ; " );
2012-09-13 14:23:18 +00:00
if ( ! $db -> next_record ()) {
return " false " ;
} // return false if pb
2012-04-06 10:10:36 +00:00
if ( $db -> f ( 'uid' ) != $cuid ) {
2012-08-26 13:31:38 +00:00
$err -> raise ( " cron " , _ ( " Identity problem " ));
2012-04-06 10:10:36 +00:00
return false ;
}
}
2012-08-26 10:44:34 +00:00
$query = " REPLACE INTO cron (id, uid, url, user, password, schedule, email) VALUES (' $id ', ' $cuid ', ' $url ', ' $user ', ' $password ', ' $schedule ', ' $email ') ; " ;
2012-04-06 10:10:36 +00:00
return $db -> query ( " $query " );
}
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** validate a crontab schedule
* @ param $s array schedule paramters
* @ return boolean TRUE if the schedule is valid
*/
2012-04-06 10:10:36 +00:00
function valid_schedule ( $s ) {
$s2 = intval ( $s );
if ( $s2 != $s ) return false ;
$r = false ;
foreach ( $this -> schedule () as $cs ) {
if ( $cs [ 'unit' ] == $s ) return true ;
}
return $r ;
}
2012-08-26 10:30:38 +00:00
/*---------------------------------------------------------------------------*/
/** hook for quota computation
*/
function hook_quota_get () {
2012-04-06 10:10:36 +00:00
global $cuid , $db , $err ;
$err -> log ( " cron " , " alternc_get_quota " );
2012-08-26 16:11:53 +00:00
$q = Array ( " name " => " cron " , " description " => _ ( " Scheduled tasks " ), " used " => 0 );
2012-04-06 10:10:36 +00:00
$db -> query ( " select count(*) as cnt from cron where uid = $cuid ; " );
if ( $db -> next_record ()) {
2012-08-26 16:11:53 +00:00
$q [ 'used' ] = $db -> f ( 'cnt' );
2012-04-06 10:10:36 +00:00
}
2012-08-26 16:11:53 +00:00
return $q ;
2012-04-06 10:10:36 +00:00
}
2014-11-25 13:36:20 +00:00
/*---------------------------------------------------------------------------*/
/**
* Execute the required crontab of AlternC users
* this function EXIT at the end .
*/
function execute_cron () {
global $db ;
2014-11-25 14:10:09 +00:00
if ( ! isset ( $GLOBALS [ " DEBUG " ])) $GLOBALS [ " DEBUG " ] = false ;
2014-11-25 13:36:20 +00:00
$db -> query ( " SELECT id, url, email, schedule, user, password FROM cron WHERE next_execution <= NOW(); " );
$urllist = array ();
while ( $db -> next_record ()) {
2014-11-25 14:10:09 +00:00
$db -> Record [ " url " ] = urldecode ( $db -> Record [ " url " ]); $db -> Record [ " user " ] = urldecode ( $db -> Record [ " user " ]);
$db -> Record [ " email " ] = urldecode ( $db -> Record [ " email " ]); $db -> Record [ " password " ] = urldecode ( $db -> Record [ " password " ]);
2014-11-25 13:36:20 +00:00
// 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 " ];
}
2014-11-25 14:10:09 +00:00
if ( $GLOBALS [ " DEBUG " ]) echo " Will run cron : \n " . print_r ( $u , true ) . " \n " ;
2014-11-25 13:36:20 +00:00
$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 ) {
2014-11-25 14:10:09 +00:00
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 " )) {
echo " Error sending mail for cron # $id to address ' " . $url [ " email " ] . " ' \n " ;
}
2014-11-25 13:36:20 +00:00
}
// 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 );
2014-11-25 14:10:09 +00:00
curl_setopt ( $ch , CURLOPT_USERPWD , $urls [ $i ][ " login " ] . " : " . $urls [ $i ][ " password " ]);
2014-11-25 13:36:20 +00:00
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 ;
}
2012-04-06 10:10:36 +00:00
2012-08-26 10:30:38 +00:00
} /* Class cron */