DEC
5

XMLRPCでeval

Published:2008-12-05 21:24:24 UTC

今回もWordPressのXML-RPCについて。但し、今回はサーバーサイドのお話。XML-RPCを使うと、WordPressのエントリやコメントなどを外部のプログラムから弄ることが出来てなかなか便利ですが、アクセスできるのはあくまでもXML-RPCのメソッドとして公開されている部分だけです。自分でメソッドを追加できたらなー。寧ろ任意のPHPスクリプトを実行できたら便利じゃね?ということで、引数として渡したPHPコードをeval関数で評価した結果を返すXML-RPCサーバーをxmlrpc.phpを改変して作ってみました。思いつきで試しに書いてみただけなので、プラグインとして纏まっていないのはご愛嬌。customrpc.phpとでも名前をつけて、xmlrpc.phpと同じディレクトリに配置すると動きます。

<?php
/**
 * Custom RPC Server
 * created by shiroica
 * @license GPL v2 <./license.txt>
 * Most of this code is based on "xmlrpc.php" file of WordPress.
 */

/**
 * Whether this is a XMLRPC Request
 *
 * @var bool
 */
define('XMLRPC_REQUEST', true);

// Some browser-embedded clients send cookies. We don't want them.
$_COOKIE = array();

// A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
// but we can do it ourself.
if ( !isset( $HTTP_RAW_POST_DATA ) ) {
	$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}

// fix for mozBlog and other cases where '<?xml' isn't on the very first line
if ( isset($HTTP_RAW_POST_DATA) )
	$HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);

/** Include the bootstrap for setting up WordPress environment */
include('./wp-load.php');

include_once(ABSPATH . 'wp-admin/includes/admin.php');
include_once(ABSPATH . WPINC . '/class-IXR.php');

// Turn off all warnings and errors.
// error_reporting(0);

/**
 * Posts submitted via the xmlrpc interface get that title
 * @name post_default_title
 * @var string
 */
$post_default_title = "";

/**
 * Whether to enable XMLRPC Logging.
 *
 * @name xmlrpc_logging
 * @var int|bool
 */
$xmlrpc_logging = 0;

/**
 * logIO() - Writes logging info to a file.
 *
 * @uses $xmlrpc_logging
 * @package WordPress
 * @subpackage Logging
 *
 * @param string $io Whether input or output
 * @param string $msg Information describing logging reason.
 * @return bool Always return true
 */
function logIO($io,$msg) {
	global $xmlrpc_logging;
	if ($xmlrpc_logging) {
		$fp = fopen("../xmlrpc.log","a+");
		$date = gmdate("Y-m-d H:i:s ");
		$iot = ($io == "I") ? " Input: " : " Output: ";
		fwrite($fp, "nn".$date.$iot.$msg);
		fclose($fp);
	}
	return true;
}

if ( isset($HTTP_RAW_POST_DATA) )
	logIO("I", $HTTP_RAW_POST_DATA);

/**
 * error2ExceptionConverter - handle an error and throw it as an exception.
 */
function error2ExceptionConverter($errno, $errstr, $errfile, $errline) {
	throw new Exception($errstr, $errno);
}



/**
 * XMLRPC server implementation that enables users to evaluate passed PHP code.
 */
class custom_rpc_server extends IXR_Server {

	/**
	 * Register all of the XMLRPC methods that XMLRPC server understands.
	 *
	 * PHP4 constructor and sets up server and method property. Passes XMLRPC
	 * methods through the 'xmlrpc_methods' filter to allow plugins to extend
	 * or replace XMLRPC methods.
	 *
	 * @since 1.5.0
	 *
	 * @return wp_xmlrpc_server
	 */
	function custom_rpc_server() {
		$this->methods = array(
			'eval' => 'this:callEval'
		);
		$this->methods = apply_filters('xmlrpc_methods', $this->methods);
		$this->IXR_Server($this->methods);
	}

	/**
	 * Evaluate passed PHP code by "eval()" function.
	 * @param array $args Method Parameters.
	 * @return depends "eval()" function returned.
	 */
	function callEval($args) {

		$user_login = $this->escape($args[0]);
		$user_pass  = $this->escape($args[1]);
		$code_str   = $args[2];

		set_current_user( 0, $user_login );
		if( !current_user_can( 'edit_posts' ) )
			return new IXR_Error( 401, __( 'Sorry, you do not have access to user data on this blog.' ) );

		if (!$this->login_pass_ok($user_login, $user_pass)) {
			return $this->error;
		}
		set_error_handler('error2ExceptionConverter');
		try{
			@$result = eval($code_str);
		}
		catch(Exception $exception){
			restore_error_handler();
			return new IXR_Error( 500, __( 'Runtime error was occurred while executing your code.' ) );					
		}
		restore_error_handler();
		if($result === false){
			return new IXR_Error( 500, __( 'Parse error was occurred while executing your code.' ) );					
		}
		return $result;
	}

	/**
	 * Check user's credentials.
	 *
	 * @since 1.5.0
	 *
	 * @param string $user_login User's username.
	 * @param string $user_pass User's password.
	 * @return bool Whether authentication passed.
	 */
	function login_pass_ok($user_login, $user_pass) {
		if ( !get_option( 'enable_xmlrpc' ) ) {
			$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this blog.  An admin user can enable them at %s'),  admin_url('options-writing.php') ) );
			return false;
		}

		if (!user_pass_ok($user_login, $user_pass)) {
			$this->error = new IXR_Error(403, __('Bad login/pass combination.'));
			return false;
		}
		return true;
	}

	/**
	 * Sanitize string or array of strings for database.
	 *
	 * @since 1.5.2
	 *
	 * @param string|array $array Sanitize single string or array of strings.
	 * @return string|array Type matches $array and sanitized for the database.
	 */
	function escape(&$array) {
		global $wpdb;

		if(!is_array($array)) {
			return($wpdb->escape($array));
		}
		else {
			foreach ( (array) $array as $k => $v ) {
				if (is_array($v)) {
					$this->escape($array[$k]);
				} else if (is_object($v)) {
					//skip
				} else {
					$array[$k] = $wpdb->escape($v);
				}
			}
		}
	}
}

$custom_rpc_server = new custom_rpc_server();

?>

ユーザー名、パスワード、評価させたいPHPコードを引数として渡すと、評価された結果が帰ってきます。戻り値はThe Incutio XML-RPC Library for PHPでパースできるデータ型で構成されていないとまずいかも。自分でも全然使っていないからよく分からない(笑)。あと、eval関数の都合上、戻り値がfalseだと実行時エラーと区別がつかないので、戻り値はbool型はやめましょう。