* * Receive Pingbacks and Trackbacks * * Developed and tested using PmWiki 2.2.x * * To use, add the following to a configuration file: include_once("$FarmD/cookbook/bloge-linkback-server.php"); * To limit receiving linkback pings you should only include this file * on pages which will accept linkbacks, for example by using per-group * customizations. * * This is a part of the Bloge bundle of recipes, but may be used by itself. * For more information, please see the online documentation at * http://www.pmwiki.org/wiki/Cookbook/Bloge and at * http://www.pmwiki.org/wiki/Cookbook/Bloge-Linkback#server * * 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. */ $RecipeInfo['Bloge-LinkbackServer']['Version'] = '2009-08-12'; $HTMLHeaderFmt['linkback'] = 'function:LinkbackHeader'; function LinkbackHeader($pagename) { global $LinkbackHeader; SDVA($LinkbackHeader, array( 'pingback' => 1, 'trackback' => 1 )); $url = PageVar($pagename,'$PageUrl'); if (!empty($LinkbackHeader['pingback'])) echo "\n"; ## NOTE: this is not fully correct, but is much shorter and will be ## recognised by at least Drupal, Movable Type & Pligg ## See also: if (!empty($LinkbackHeader['trackback'])) echo "\n"; } if (($action!='pingback') && ($action!='trackback')) return; SDV($LinkbackPage, 'Site.Linkbacks'); SDV($HandleActions['pingback'], 'HandleLinkback'); SDV($HandleAuth['pingback'], 'read'); SDV($HandleActions['trackback'], 'HandleLinkback'); SDV($HandleAuth['trackback'], 'read'); if (!function_exists('xmlrpc_decode_request')) { function xmlrpc_decode_request($xml, &$method, $encoding='iso-8859-1') { if (!preg_match('#\s*(.*?)\s*#s',$xml,$m_mcall)) return FALSE; if (!preg_match('#\s*([\w.:/]*?)\s*#',$m_mcall[1],$m_name)) return FALSE; $method = $m_name[1]; if (!preg_match('#\s*(.*?)\s*#s',$m_mcall[1],$m_params)) return FALSE; if (!preg_match_all('#\s*\s*<(\w+)>\s*(.*?)\s*\s*\s*#s', $m_params[1], $m_values, PREG_SET_ORDER)) return FALSE; $out = array(); foreach( $m_values as $va ) $out[] = $va[2]; return $out; } } function LinkbackVerifySource($url, $self, &$errstr) { global $LinkbackServerCURLOptions; if ( !function_exists('curl_init') || empty($self) ) return -32400; if (empty($url)) return 16; $host = preg_replace('!^[a-z]+://([^/]+).*$!i', '$1', $url); if (gethostbyname($host) != $_SERVER['REMOTE_ADDR']) { $errstr='source IP mismatch'; return 49; } SDV($LinkbackServerCURLOptions, array( CURLOPT_FOLLOWLOCATION => TRUE, CURLOPT_MAXREDIRS => 10, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_USERAGENT => "$Version Bloge-LinkbackServer-{$RecipeInfo['Bloge-LinkbackServer']['Version']}" )); ## HEAD url $ch = curl_init($url); curl_setopt_array($ch, $LinkbackServerCURLOptions + array( CURLOPT_NOBODY => TRUE, CURLOPT_HEADER => TRUE, CURLOPT_REFERER => $url )); $reply = curl_exec($ch); if (empty($reply)) $errstr = curl_error($ch); curl_close($ch); if (empty($reply)) return 16; else if (!preg_match('/^Content-Type: .*(html|xml)/mi', $reply, $m)) return 17; ## GET url $ch = curl_init($url); curl_setopt_array($ch, $LinkbackServerCURLOptions + array( CURLOPT_REFERER => $url )); $reply = curl_exec($ch); if (empty($reply)) $errstr = curl_error($ch); curl_close($ch); if (empty($reply)) return 16; if (!preg_match('#]*\bhref=([\'"])'.preg_quote($self,'#').'\1[^>]*>\s*\S+\s*#i', $reply)) return 17; return 0; } function LinkbackBlocklist($text) { global $EnableBlocklist, $FarmD, $EnablePost, $EditFunctions, $EnableLinkbackBlocklist, $LinkbackPage; if (!IsEnabled($EnableLinkbackBlocklist,0)) return FALSE; $EnableBlocklist = $EnableLinkbackBlocklist; include_once("$FarmD/scripts/blocklist.php"); $EnablePost &= 1; BlockList($LinkbackPage, $text); if (!$EnablePost) return TRUE; $cbkey = array_search('CheckBlocklist', $EditFunctions); if ($cbkey !== FALSE) unset($EditFunctions[$cbkey]); return FALSE; } function LinkbackExecute($pagename, $auth, &$type, &$errstr) { global $action, $HTTP_RAW_POST_DATA, $Author, $AuthorLink, $IsPagePosted, $CurrentTime, $LinkbackPage, $LinkbackMarkup; $tgtpage = RetrieveAuthPage($pagename, 'read', FALSE, READPAGE_CURRENT); if (!$tgtpage) { $errstr = 'page read error'; return -32500; } $tgt = PageVar($pagename,'$PageUrl'); $lbpage = RetrieveAuthPage($LinkbackPage, $auth, FALSE); if (!$lbpage) { $errstr = 'page read error'; return -32500; } if (empty($_REQUEST['url'])) { if ($action=='trackback') { $type = 'trackback'; $errstr = 'no url parameter'; return -32700; } $type = 'pingback'; $params = xmlrpc_decode_request($HTTP_RAW_POST_DATA, &$method); if ( $params === FALSE ) return -32600; if ( $method != 'pingback.ping' ) return -32601; if ( count($params) != 2 ) return -32602; if ( $params[1] != $tgt ) { $errstr = 'targetURI doesn\'t match Pingback URI'; return 33; } $source = $params[0]; } else { $type = 'trackback'; $source = $_REQUEST['url']; } $sn = str_replace('.','--',$pagename); $lbsection = TextSection($lbpage['text'], "#$sn#"); if (preg_match('#\s'.preg_quote($source,'#').'\s#', $lbsection)) return 48; if (LinkbackBlocklist($source)) { $errstr='blocked'; return 49; } $source_code = LinkbackVerifySource($source, $tgt, $errstr); if ($source_code) return $source_code; SDVA($LinkbackMarkup, array( 'head' => "\$pageanchor\n!!! Linkbacks", 'item' => "\n* \$source ($CurrentTime)" )); $lbnew = $lbpage; foreach ( array('head','item') as $t ) $lbmarkup[$t] = str_replace( array('$pagename', '$pageanchor', '$source'), array( $pagename, "[[#$sn]]", $source), $LinkbackMarkup[$t]); if ($lbsection) $lbnew['text'] = str_replace($lbmarkup['head'], $lbmarkup['head'].$lbmarkup['item'], $lbnew['text']); else $lbnew['text'] .= "\n{$lbmarkup['head']}{$lbmarkup['item']}\n"; $AuthorLink = $Author = "linkback from {$_SERVER['REMOTE_ADDR']}"; UpdatePage($LinkbackPage, $lbpage, $lbnew); if (!$IsPagePosted) { $errstr = 'page write error'; return -32500; } return 0; } function HandleLinkback($pagename, $auth='read') { $errstr = ''; Lock(2); $errcode = LinkbackExecute($pagename, $auth, $type, $errstr); Lock(0); switch($errcode) { //case 0: $str = 'generic fault code'; break; case 16: $str = 'The source URI does not exist.'; break; case 17: $str = 'The source URI does not contain a link to the target URI, and so cannot be used as a source.'; break; case 32: $str = 'The specified target URI does not exist.'; break; case 33: $str = 'The specified target URI cannot be used as a target.'; break; case 48: $str = 'The linkback has already been registered.'; break; case 49: $str = 'Access denied.'; break; //case 50: $str = 'The server could not communicate with an upstream server, or received an error from an upstream server, and therefore could not complete the request.'; break; case -32700: $str = 'parse error. not well formed'; break; //case -32701: $str = 'parse error. unsupported encoding'; break; //case -32702: $str = 'parse error. invalid character for encoding'; break; case -32600: $str = 'server error. invalid xml-rpc. not conforming to spec.'; break; case -32601: $str = 'server error. requested method not found'; break; case -32602: $str = 'server error. invalid method parameters'; break; //case -32603: $str = 'server error. internal xml-rpc error'; break; case -32500: $str = 'application error'; break; case -32400: $str = 'system error'; break; //case -32300: $str = 'transport error'; break; default: $str = 'ok'; } if ($errstr) $str .= " ($errstr)"; if ($type=='trackback') { $response = "\n" . ( $errcode ? "1\n$str [$errcode]" : "0" ) . "\n"; } else { ## default to pingback $response = "\n" . ( $errcode ? "\nfaultCode$errcode\nfaultString$str\n" : "$str" ) . "\n"; } header('Content-type: text/xml'); echo "\n$response\n"; exit(); }