From 027ac79654b26d39f409b4aab9c69cf7df6e7589 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 12 Dec 2013 11:12:22 +0000 Subject: [PATCH] first commit --- conf/nginx.conf | 16 + conf/settings.php | 26 ++ manifest.json | 33 ++ scripts/install | 46 ++ scripts/remove | 10 + sources/.gitignore | 2 + sources/README.md | 81 ++++ sources/TODO | 7 + sources/WBOJsonOutput.php | 154 +++++++ sources/index.php | 329 +++++++++++++ sources/setup.php | 399 ++++++++++++++++ sources/test/hash.php | 59 +++ sources/user.php | 275 +++++++++++ sources/weave_basic_object.php | 249 ++++++++++ sources/weave_hash.php | 191 ++++++++ sources/weave_storage.php | 815 +++++++++++++++++++++++++++++++++ sources/weave_utils.php | 286 ++++++++++++ 17 files changed, 2978 insertions(+) create mode 100644 conf/nginx.conf create mode 100644 conf/settings.php create mode 100644 manifest.json create mode 100644 scripts/install create mode 100644 scripts/remove create mode 100644 sources/.gitignore create mode 100644 sources/README.md create mode 100644 sources/TODO create mode 100644 sources/WBOJsonOutput.php create mode 100644 sources/index.php create mode 100644 sources/setup.php create mode 100644 sources/test/hash.php create mode 100644 sources/user.php create mode 100644 sources/weave_basic_object.php create mode 100644 sources/weave_hash.php create mode 100644 sources/weave_storage.php create mode 100644 sources/weave_utils.php diff --git a/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..175966b --- /dev/null +++ b/conf/nginx.conf @@ -0,0 +1,16 @@ +location PATHTOCHANGE { + alias ALIASTOCHANGE; + if ($scheme = http) { + rewrite ^ https://$server_name$request_uri? permanent; + } + index index.php; + try_files $uri $uri/ /index.php?$args; + location ~ [^/]\.php(/|$) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_pass unix:/var/run/php5-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_param PATH_INFO $fastcgi_path_info; + } +} diff --git a/conf/settings.php b/conf/settings.php new file mode 100644 index 0000000..455441f --- /dev/null +++ b/conf/settings.php @@ -0,0 +1,26 @@ + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..e112e88 --- /dev/null +++ b/manifest.json @@ -0,0 +1,33 @@ +{ + "name": "Firefox Sync Server", + "id": "ffsync", + "description": { + "en": "", + "fr": "" + }, + "developer": { + "name": "beudbeud", + "email": "beudbeud@beudibox.fr", + "url": "https://github.com/balu-/FSyncMS" + }, + "multi_instance": "false", + "arguments": { + "install" : [ + { + "name": "domain", + "ask": { + "en": "Choose a domain for Firefox-Sync Server" + }, + "example": "domain.org" + }, + { + "name": "path", + "ask": { + "en": "Choose a path for Firefox-Sync Server" + }, + "example": "/ffsync", + "default": "/ffsync" + } + ] + } +} diff --git a/scripts/install b/scripts/install new file mode 100644 index 0000000..c2ccedc --- /dev/null +++ b/scripts/install @@ -0,0 +1,46 @@ +#!/bin/bash + +# Retrieve arguments +domain=$1 +path=$2 + +# Check domain/path availability +sudo yunohost app checkurl $domain$path -a ffsync +if [[ ! $? -eq 0 ]]; then + exit 1 +fi + +# Generate random password +db_pwd=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' | sed -n 's/\(.\{24\}\).*/\1/p') + +# Use 'FSyncMS' as database name and user +db_user=ffsync + +# Initialize database and store mysql password for upgrade +sudo yunohost app initdb $db_user -p $db_pwd +sudo yunohost app setting ffsync mysqlpwd -v $db_pwd + +# Copy files to the right place +final_path=/var/www/ffsync +sudo mkdir -p $final_path +sudo cp -a ../sources/* $final_path +sudo cp ../conf/settings.php $final_path/ + +# Change variables in FSyncMS configuration +sudo sed -i "s/yunouser/$db_user/g" $final_path/settings.php +sudo sed -i "s/yunopass/$db_pwd/g" $final_path/settings.php +sudo sed -i "s/yunobase/$db_user/g" $final_path/settings.php +sudo sed -i "s@URLFFSYNC@$domain$path@g" $final_path/settings.php + +# Set permissions to roundcube directory +sudo chown -R www-data: $final_path + +# Modify Nginx configuration file and copy it to Nginx conf directory +sed -i "s@PATHTOCHANGE@$path@g" ../conf/nginx.conf* +sed -i "s@ALIASTOCHANGE@$final_path/@g" ../conf/nginx.conf* +sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/wordpress.conf + +# Reload Nginx and regenerate SSOwat conf +sudo service nginx reload +sudo yunohost app setting ffsync skipped_uris -v "/" +sudo yunohost app ssowatconf diff --git a/scripts/remove b/scripts/remove new file mode 100644 index 0000000..1f13cff --- /dev/null +++ b/scripts/remove @@ -0,0 +1,10 @@ +#!/bin/bash + +db_user=wordpress +db_name=wordpress +root_pwd=$(sudo cat /etc/yunohost/mysql) +domain=$(sudo yunohost app setting ffsync domain) + +mysql -u root -p$root_pwd -e "DROP DATABASE $db_name ; DROP USER $db_user@localhost ;" +sudo rm -rf /var/www/ffsync +sudo rm -f /etc/nginx/conf.d/$domain.d/ffsync.conf diff --git a/sources/.gitignore b/sources/.gitignore new file mode 100644 index 0000000..b72f9be --- /dev/null +++ b/sources/.gitignore @@ -0,0 +1,2 @@ +*~ +*.swp diff --git a/sources/README.md b/sources/README.md new file mode 100644 index 0000000..bdec77e --- /dev/null +++ b/sources/README.md @@ -0,0 +1,81 @@ +FSyncMS +======= + +PHP Sync Server für Firefox Sync +Eine Erweiterung des Weave-Minimal Server (dessen Support leider eingestellt wurde). + +Die derzeit aktuelle Versionen, +sowie alte Versionen und Anleitungen sind hier: + +https://www.ohnekontur.de/category/technik/sync/fsyncms/ + +zu finden. +Weitere Erweiterungen sind in Planung. +Stay tuned. + + +Visit http://www.ohnekontur.de/2011/07/24/how-to-install-fsyncms-firefox-sync-eigener-server/ for install instructions +Visit http://www.ohnekontur.de for the newest version + + +FSyncMS v013 +====== +Database upgrade +for more information and some migration notice see +http://www.ohnekontur.de/2013/07/05/fsyncms-version-0-13-database-upgrade/ + + +FSyncMS v012 +====== +Compatibility update + +FSyncMS v011 +====== +Added dedicated setup script, which will create the database and the config file: settings.php + +If you want to create it by your own, just generate the settings.php with the following content + + + + +FSyncMS v010 +====== +MYSQL Support + +FSyncMS v 09 +====== +Change Password now supported +working with firefox 12 (and lower) + +Changelog: +Added change Password feature + +FSyncMS v 08 +====== +Should be working with firefox 11 and lower (tested with 11) + +Changelog: +Fixed user registration process, +fixed some delete problems diff --git a/sources/TODO b/sources/TODO new file mode 100644 index 0000000..b22ef93 --- /dev/null +++ b/sources/TODO @@ -0,0 +1,7 @@ +== SOME TODOS == + +- Ein Präfix für die Tabellen, damit sie in eine vorhandene DB gelegt werden können. + + +- NOTIZ: Update der md5 Spalte der Usertabelle von length 64 auf length 124 +- >ALTER TABLE `users` CHANGE `md5` `md5` VARCHAR( 124 ) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL diff --git a/sources/WBOJsonOutput.php b/sources/WBOJsonOutput.php new file mode 100644 index 0000000..01139d5 --- /dev/null +++ b/sources/WBOJsonOutput.php @@ -0,0 +1,154 @@ +_full = $full; + if (array_key_exists('HTTP_ACCEPT', $_SERVER) + && !preg_match('/\*\/\*/', $_SERVER['HTTP_ACCEPT']) + && !preg_match('/application\/json/', $_SERVER['HTTP_ACCEPT'])) + { + if (preg_match('/application\/whoisi/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/whoisi"); + $this->_output_format = 'whoisi'; + } + elseif (preg_match('/application\/newlines/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/newlines"); + $this->_output_format = 'newlines'; + } + + } + } + + function set_format($format) + { + $this->_output_format = $format; + } + + + function output($sth) + { + if (($rowcount = $sth->rowCount()) > 0) + { + header('X-Weave-Records: ' . $rowcount); + } + if ($this->_output_format == 'newlines') + { + return $this->output_newlines($sth); + } + elseif ($this->_output_format == 'whoisi') + { + return $this->output_whoisi($sth); + } + else + { + return $this->output_json($sth); + } + } + + function output_json($sth) + { + echo '['; + + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_comma_flag) { echo ','; } else { $this->_comma_flag = 1; } + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo $wbo->json(); + } + else + echo json_encode($result{'id'}); + } + + echo ']'; + return 1; + } + + function output_whoisi($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + $output = $wbo->json(); + } + else + $output = json_encode($result{'id'}); + echo pack('N', mb_strlen($output, '8bit')) . $output; + } + return 1; + } + + function output_newlines($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo preg_replace('/\n/', '\u000a', $wbo->json()); + } + else + echo json_encode($result{'id'}); + echo "\n"; + } + return 1; + } +} +?> diff --git a/sources/index.php b/sources/index.php new file mode 100644 index 0000000..e59d393 --- /dev/null +++ b/sources/index.php @@ -0,0 +1,329 @@ +

Maybe the setup is not completed, missing settings.php


"; + exit; + + } else if ( file_exists("setup.php") ) { + echo "

Maybe the setup is not completed, else please delete setup.php


"; + exit; + } + + require_once 'weave_storage.php'; + require_once 'weave_basic_object.php'; + require_once 'weave_utils.php'; + require_once 'weave_hash.php'; + + require_once "WBOJsonOutput.php"; + //header("Content-type: application/json"); + + $server_time = round(microtime(1), 2); + header("X-Weave-Timestamp: " . $server_time); + + # Basic path extraction and validation. No point in going on if these are missing + $path = '/'; + if (!empty($_SERVER['PATH_INFO'])) + $path = $_SERVER['PATH_INFO']; + else if (!empty($_SERVER['ORIG_PATH_INFO'])) + $path = $_SERVER['ORIG_PATH_INFO']; + else if (!empty($_SERVER["REQUEST_URI"])) + { + log_error("experimental path"); + # this is kind of an experimental try, i needed it so i build it, + # but that doesent mean that it does work... well it works for me + # and it shouldnt break anything... + $path = $_SERVER["REQUEST_URI"]; + $lastfolder = substr(FSYNCMS_ROOT,strrpos(FSYNCMS_ROOT, "/",-2)); + $path = substr($path, (strpos($path,$lastfolder) + strlen($lastfolder)-1)); #chop the lead slash + if(strpos($path,'?') != false) + $path = substr($path, 0, strpos($path,'?')); //remove php arguments + log_error("path_exp:".$path); + } + else + report_problem("No path found", 404); + + $path = substr($path, 1); #chop the lead slash + log_error("start request_____" . $path); + // ensure that we got a valid request + if ( !$path ) + report_problem("Invalid request, this was not a firefox sync request!", 400); + + // split path into parts and make sure that all values are properly initialized + list($version, $username, $function, $collection, $id) = array_pad(explode('/', $path.'///'), 5, ''); + + if($version == 'user' || $version == 'misc') + { + //asking for userApi -> user.php + $include = true; + require 'user.php'; + exit(); // should not get here, but how knows + } + + header("Content-type: application/json"); + + if ($version != '1.0' && $version != '1.1') + report_problem('Function not found', 404); + + if ($function != "info" && $function != "storage") + report_problem(WEAVE_ERROR_FUNCTION_NOT_SUPPORTED, 400); + + if (!validate_username($username)) + report_problem(WEAVE_ERROR_INVALID_USERNAME, 400); + + #only a delete has meaning without a collection + if ($collection) + { + if (!validate_collection($collection)) + report_problem(WEAVE_ERROR_INVALID_COLLECTION, 400); + } + else if ($_SERVER['REQUEST_METHOD'] != 'DELETE') + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + + + #quick check to make sure that any non-storage function calls are just using GET + if ($function != 'storage' && $_SERVER['REQUEST_METHOD'] != 'GET') + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + + + + #user passes preliminaries, connections made, onto actually getting the data + try + { + $db = new WeaveStorage($username); + + #Auth the user + verify_user($username, $db); + + #user passes preliminaries, connections made, onto actually getting the data + if ($_SERVER['REQUEST_METHOD'] == 'GET') + { + if ($function == 'info') + { + switch ($collection) + { + case 'quota': + exit(json_encode(array($db->get_storage_total()))); + case 'collections': + exit(json_encode($db->get_collection_list_with_timestamps())); + case 'collection_counts': + exit(json_encode($db->get_collection_list_with_counts())); + case 'collection_usage': + $results = $db->get_collection_storage_totals(); + foreach (array_keys($results) as $collection) + { + $results[$collection] = ceil($results[$collection] / 1024); #converting to k from bytes + } + exit(json_encode($results)); + default: + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + } + } + elseif ($function == 'storage') + { + log_error("function storage"); + if ($id) #retrieve a single record + { + $wbo = $db->retrieve_objects($collection, $id, 1); #get the full contents of one record + if (count($wbo) > 0) + { + $item = array_shift($wbo); + echo $item->json(); + } + else + report_problem("record not found", 404); + } + else #retrieve a batch of records. Sadly, due to potential record sizes, have the storage object stream the output... + { + log_error("retrieve a batch"); + $full = array_key_exists('full', $_GET) && $_GET['full']; + + $outputter = new WBOJsonOutput($full); + + $params = validate_search_params(); + + $ids = $db->retrieve_objects($collection, null, $full, $outputter, + $params['parentid'], $params['predecessorid'], + $params['newer'], $params['older'], + $params['sort'], + $params['limit'], $params['offset'], + $params['ids'], + $params['index_above'], $params['index_below'], $params['depth'] + ); + } + } + } + else if ($_SERVER['REQUEST_METHOD'] == 'PUT') #add a single record to the server + { + $wbo = new wbo(); + if (!$wbo->extract_json(get_json())) + report_problem(WEAVE_ERROR_JSON_PARSE, 400); + + check_quota($db); + check_timestamp($collection, $db); + + #use the url if the json object doesn't have an id + if (!$wbo->id() && $id) { $wbo->id($id); } + + $wbo->collection($collection); + $wbo->modified($server_time); #current microtime + + if ($wbo->validate()) + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + $db->store_object($wbo); + else + $db->update_object($wbo); + } + else + { + report_problem(WEAVE_ERROR_INVALID_WBO, 400); + } + echo json_encode($server_time); + } + else if ($_SERVER['REQUEST_METHOD'] == 'POST') + { + $json = get_json(); + + check_quota($db); + check_timestamp($collection, $db); + + $success_ids = array(); + $failed_ids = array(); + + $db->begin_transaction(); + + foreach ($json as $wbo_data) + { + $wbo = new wbo(); + + if (!$wbo->extract_json($wbo_data)) + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + continue; + } + + $wbo->collection($collection); + $wbo->modified($server_time); + + + if ($wbo->validate()) + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + { + $db->store_object($wbo); + } + else + { + $db->update_object($wbo); + } + $success_ids[] = $wbo->id(); + } + else + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + } + } + $db->commit_transaction(); + + echo json_encode(array('success' => $success_ids, 'failed' => $failed_ids)); + } + else if ($_SERVER['REQUEST_METHOD'] == 'DELETE') + { + check_timestamp($collection, $db); + + if ($id) + { + $db->delete_object($collection, $id); + } + else if ($collection) + { + $params = validate_search_params(); + + $db->delete_objects($collection, null, + $params['parentid'], $params['predecessorid'], + $params['newer'], $params['older'], + $params['sort'], + $params['limit'], $params['offset'], + $params['ids'], + $params['index_above'], $params['index_below'] + ); + } + else if($function == 'storage') // ich vermute mal storage reinigen + { + if (!array_key_exists('HTTP_X_CONFIRM_DELETE', $_SERVER)) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + $db->delete_storage($username); + } + else + { + if (!array_key_exists('HTTP_X_CONFIRM_DELETE', $_SERVER)) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + log_error("delete "."Server ".print_r( $_SERVER, true)); + $db->delete_user($username); + } + + echo json_encode($server_time); + + } + else + { + #bad protocol. There are protocols left? HEAD, I guess. + report_problem(1, 400); + } + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + +#The datasets we might be dealing with here are too large for sticking it all into an array, so +#we need to define a direct-output method for the storage class to use. If we start producing multiples +#(unlikely), we can put them in their own class. + +#include_once "WBOJsonOutput.php"; +?> diff --git a/sources/setup.php b/sources/setup.php new file mode 100644 index 0000000..27916d1 --- /dev/null +++ b/sources/setup.php @@ -0,0 +1,399 @@ + +# balu +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +// -------------------------------------------- +// variables start +// -------------------------------------------- +$action = null; +$dbType = null; + +$dbUser = null; +$dbName = null; +$dbPass = null; +$dbHost = null; + + +// -------------------------------------------- +// variables end +// -------------------------------------------- + + +// -------------------------------------------- +// post handling start +// -------------------------------------------- +if ( isset( $_POST['action'] ) ) { + $action = check_input($_POST['action']); +} + +if ( isset( $_POST['dbType'] ) ) { + $dbType = check_input($_POST['dbType']); +} + +if ( isset( $_POST['dbhost'] ) ) { + $dbHost = check_input($_POST['dbhost']); +} + +if ( isset( $_POST['dbname'] ) ) { + $dbName = check_input($_POST['dbname']); +} + +if ( isset( $_POST['dbuser'] ) ) { + $dbUser = check_input($_POST['dbuser']); +} + +if ( isset( $_POST['dbpass'] ) ) { + $dbPass = check_input($_POST['dbpass']); +} + +// -------------------------------------------- +// post handling end +// -------------------------------------------- + + +// -------------------------------------------- +// functions start +// -------------------------------------------- + +/* + ensure that the input is not total waste +*/ +function check_input( $data ) { + $data = trim($data); + $data = stripslashes($data); + $data = htmlspecialchars($data); + return $data; +} + + +/* + create the config file with the database type + and the given connection credentials +*/ +function write_config_file($dbt, $dbh, $dbn, $dbu, $dbp, $fsRoot) { + + // construct the name of config file + // + $path = explode('/', $_SERVER['SCRIPT_FILENAME']); + array_pop($path); + array_push($path, 'settings.php'); + $cfg_file_name = implode('/', $path); + + if ( file_exists($cfg_file_name) && filesize( $cfg_file_name ) > 0 ) { + echo "
The config file $cfg_file_name is already present"; + return; + } + + echo "Creating cfg file: " . $cfg_file_name; + + // now build the content of the config file + // + $cfg_content = "\n"; + + // now write everything + // + $cfg_file = fopen($cfg_file_name, "a"); + fputs($cfg_file, "$cfg_content"); + fclose($cfg_file); +} + + +/* + print the html header for the form +*/ +function print_header( $title ) { + if ( ! isset( $title ) ) { + $title = ""; + } + print '
' . $title . ' +

Setup FSyncMS

+
'; +} + + +/* + print the html footer +*/ +function print_footer() { + print '
'; +} + + +/* + print the html for for the mysql connection credentials +*/ +function print_mysql_connection_form() { + print_header("MySQL database connection setup"); + print 'MySQL database connection setup + + + + + + + + + + + + + + + + + +
Host
Instance name
Username
Password
+ + + +

'; + print_footer(); +} +// -------------------------------------------- +// functions end +// -------------------------------------------- + +// check if we have no configuration at the moment +// +if ( file_exists("settings.php") && filesize( "settings.php" ) > 0 ) { + echo "

The setup looks like it's completed, please delete settings.php


"; + exit; +} + + +// inital page - select the database type +// +if ( ! $action ) { + + // first check if we have pdo installed (untested) + // + if ( ! extension_loaded('PDO') ) { + print "ERROR - PDO is missing in the php installation!"; + exit(); + } + + $validPdoDriver = 0; + + print_header("Setup FSyncMS - DB Selection"); + + print 'Which database type should be used?
'; + if ( extension_loaded('pdo_mysql') ) { + print ' MySQL
'; + $validPdoDriver++; + } else { + print 'MySQL not possible (Driver missing)
'; + } + + if ( extension_loaded('pdo_sqlite') ) { + print ' SQLite '; + $validPdoDriver++; + } else { + print 'SQLite not possible (Driver missing)
'; + } + + if ( $validPdoDriver < 1 ) { + print '
No valid pdo driver found! Please install a valid pdo driver first
'; + } else { + print ' +

'; + } + + // ensure we bail out at this point ;) + exit(); +}; + + +// step 2 (connection data) below +// +if ( $action == "step1" ) { + + // now check if the database is in place + // + print_header("Setup FSyncMS - DB Setup: $dbType!"); + switch ( $dbType ) { + case "sqlite": + $action = "step2"; + break; + + case "mysql": + print_mysql_connection_form(); + break; + + default: + print "ERROR - This type of database ($dbType) is not valid at the moment!"; + exit(); + break; + } + +} + +// now generate the database +// +if ( $action == "step2" ) { + + $dbInstalled = false; + $dbHandle = null; + try { + + if ( $dbType == "sqlite" ) { + + $path = explode('/', $_SERVER['SCRIPT_FILENAME']); + $db_name = 'weave_db'; + array_pop($path); + array_push($path, $db_name); + $db_name = implode('/', $path); + + if ( file_exists($db_name) && filesize( $db_name ) > 0 ) { + $dbInstalled = true; + } else { + echo("Creating sqlite weave storage: ". $db_name ."
"); + $dbHandle = new PDO('sqlite:' . $db_name); + $dbHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + } else if ( $dbType == "mysql" ) { + + $dbHandle = new PDO("mysql:host=". $dbHost .";dbname=". $dbName, $dbUser, $dbPass); + $select_stmt = "show tables like 'wbo'"; + $sth = $dbHandle->prepare($select_stmt); + $sth->execute(); + $count = $sth->rowCount(); + if ( $count > 0 ) { + $dbInstalled = true; + } + + }; + + } catch ( PDOException $exception ) { + echo("database unavailable " . $exception->getMessage()); + throw new Exception("Database unavailable " . $exception->getMessage() , 503); + } + + if ( $dbInstalled ) { + echo "DB is already installed!
"; + + } else { + echo "Now going to install the new database! Type is: $dbType
"; + + try { + $create_statement = " create table wbo ( username varchar(100), id varchar(65), collection varchar(100), + parentid varchar(65), predecessorid int, modified real, sortindex int, + payload text, payload_size int, ttl int, primary key (username,collection,id))"; + $create_statement2 = " create table users ( username varchar(255), md5 varchar(124), primary key (username)) "; + $index1 = 'create index parentindex on wbo (username, parentid)'; + $index2 = 'create index predecessorindex on wbo (username, predecessorid)'; + $index3 = 'create index modifiedindex on wbo (username, collection, modified)'; + + $sth = $dbHandle->prepare($create_statement); + $sth->execute(); + $sth = $dbHandle->prepare($create_statement2); + $sth->execute(); + $sth = $dbHandle->prepare($index1); + $sth->execute(); + $sth = $dbHandle->prepare($index2); + $sth->execute(); + $sth = $dbHandle->prepare($index3); + $sth->execute(); + echo "Database created
"; + + } catch( PDOException $exception ) { + throw new Exception("Database unavailable", 503); + } + + } + + //guessing fsroot + // get the FSYNC_ROOT url + // + $fsRoot ="https://"; + if ( ! isset($_SERVER['HTTPS']) ) { + $fsRoot = "http://"; + } + $fsRoot .= $_SERVER['SERVER_NAME'] . dirname($_SERVER['SCRIPT_NAME']) . "/"; + if( strpos( $_SERVER['REQUEST_URI'], 'index.php') !== 0 ) { + $fsRoot .= "index.php/"; + } + + // write settings.php, if not possible, display the needed contant + // + write_config_file($dbType, $dbHost, $dbName, $dbUser, $dbPass, $fsRoot); + + echo "

Finished the setup, please delete setup.php and go on with the FFSync

"; + echo <<
+

This script has guessed the Address of your installation, this might not be accurate,
+ Please check if this script can be reached by $fsRoot .
+ If thats not the case you have to ajust the settings.php
+

+EOT; +} + +?> diff --git a/sources/test/hash.php b/sources/test/hash.php new file mode 100644 index 0000000..a7652b3 --- /dev/null +++ b/sources/test/hash.php @@ -0,0 +1,59 @@ +hash($pwd) . "\n"; + $time = microtime(true) - $time_start; + echo "Hashing took " . $time . " seconds\n"; + + if (!$hash->verify($pwd, '$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash compare failed"); + } + + if (!$hash->needsUpdate(md5($pwd))) { + throw new Exception("bcrypt hash needs update."); + } + + if ($hash->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash doesn't needs update."); + } + + if (!$hash->verify($pwd, 'a96b71c678b01b98b9f7a0d8ec4b633b')) { + throw new Exception("bcrypt hash compare with md5 failed"); + } + + $hash2 = new WeaveHashBCrypt(6); + + if (!$hash2->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("bcrypt hash needs update because of different rounds."); + } + + $hashmd5 = new WeaveHashMD5(); + if (!$hashmd5->verify($pwd, 'a96b71c678b01b98b9f7a0d8ec4b633b')) { + throw new Exception("md5 hash compare failed"); + } + + if (!$hashmd5->needsUpdate('$2a$12$O2Bn6lDUYS5NDIJ1uCZjGezSI/jeGTD7Ow0bd3PFMRBcGIqfqI4Oi')) { + throw new Exception("md5 hash needs update."); + } + + if ($hashmd5->needsUpdate(md5($pwd))) { + throw new Exception("md5 hash doesn't need update."); + } + + echo "all tests ok\n"; + exit(0); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; + exit(1); +} + +?> \ No newline at end of file diff --git a/sources/user.php b/sources/user.php new file mode 100644 index 0000000..4c8e905 --- /dev/null +++ b/sources/user.php @@ -0,0 +1,275 @@ +create_user($name, $pwd)) + { + log_error("successfully created user"); + exit(json_encode(strtolower($name))); + } + else + { + log_error("create user failed"); + report_problem(WEAVE_ERROR_NO_OVERWRITE, 503); + } + } + catch(Exception $e) + { + log_error("db exception create user"); + header("X-Weave-Backoff: 1800"); + report_problem($e->getMessage(), $e->getCode()); + } + + } + else + { + log_error("register not enabled"); + report_problem(WEAVE_ERROR_FUNCTION_NOT_SUPPORTED,400); + } + } // ende put + else if($_SERVER['REQUEST_METHOD'] == 'POST') + { + if($username == '') + { + log_error("user.php : Post no username"); + report_problem(WEAVE_ERROR_INVALID_USERNAME, 400); + } + $db = new WeaveStorage($username); + log_error("user.php: POST"); + if($function == "password") + { + #Auth the user + verify_user($username, $db); + $new_pwd = get_phpinput(); + log_error("user.php: POST password "); + //to do + // change pw in db + if($db->change_password($new_pwd)) + exit("success"); + else + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 503); //server db messed up somehow + // return success + // report_problem(7, 400); + } + else if($function == "email") + { + //change email adr + } + else + { + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + } + // exit('success'); + } + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } +#The datasets we might be dealing with here are too large for sticking it all into an array, so +#we need to define a direct-output method for the storage class to use. If we start producing multiples +#(unlikely), we can put them in their own class. + +#include_once "WBOJsonOutput.php"; + +?> diff --git a/sources/weave_basic_object.php b/sources/weave_basic_object.php new file mode 100644 index 0000000..305dfd5 --- /dev/null +++ b/sources/weave_basic_object.php @@ -0,0 +1,249 @@ +_error[] = "unable to extract from json"; + return false; + } + + #must have an id, or all sorts of badness happens. However, it can be added later + if (array_key_exists('id', $extracted)) + { + $this->id($extracted['id']); + } + + if (array_key_exists('parentid', $extracted)) + { + $this->parentid($extracted['parentid']); + } + + if (array_key_exists('predecessorid', $extracted)) + { + $this->predecessorid($extracted['predecessorid']); + } + + if (array_key_exists('sortindex', $extracted)) + { + # Due to complicated logic in the getter, we need to validate + # the value space of sortindex here. + if (!is_numeric($extracted['sortindex'])) { + $this->_error[] = "invalid sortindex"; + return false; + } + $this->sortindex($extracted['sortindex']); + } + + if (array_key_exists('payload', $extracted)) + { + $this->payload($extracted['payload']); + } + return true; + } + + function populate(&$datahash) + { + if (array_key_exists('id', $datahash)) + $this->id($datahash['id']); + + if (array_key_exists('collection', $datahash)) + $this->collection($datahash['collection']); + + if (array_key_exists('parentid', $datahash)) + $this->parentid($datahash['parentid']); + + if (array_key_exists('modified', $datahash)) + $this->modified($datahash['modified']); + + if (array_key_exists('predecessorid', $datahash)) + $this->predecessorid($datahash['predecessorid']); + + if (array_key_exists('sortindex', $datahash)) + $this->sortindex($datahash['sortindex']); + + if (array_key_exists('payload', $datahash)) + $this->payload($datahash['payload']); + } + + function id($id = null) + { + if (!is_null($id)) { $this->wbo_hash['id'] = (string)$id; } + return array_key_exists('id', $this->wbo_hash) ? $this->wbo_hash['id'] : null; + } + + function collection($collection = null) + { + if (!is_null($collection)){ $this->_collection = $collection; } + return $this->_collection; + } + + function parentid($parentid = null) + { + if (!is_null($parentid)){ $this->wbo_hash['parentid'] = (string)$parentid; } + return array_key_exists('parentid', $this->wbo_hash) ? $this->wbo_hash['parentid'] : null; + } + + function parentid_exists() + { + return array_key_exists('parentid', $this->wbo_hash); + } + + function predecessorid($predecessorid = null) + { + if (!is_null($predecessorid)){ $this->wbo_hash['predecessorid'] = (string)$predecessorid; } + return array_key_exists('predecessorid', $this->wbo_hash) ? $this->wbo_hash['predecessorid'] : null; + } + + function predecessorid_exists() + { + return array_key_exists('predecessorid', $this->wbo_hash); + } + + function modified($modified = null) + { + if (!is_null($modified)){ $this->wbo_hash['modified'] = round((float)$modified, 2); } + return array_key_exists('modified', $this->wbo_hash) ? $this->wbo_hash['modified'] : null; + } + + function modified_exists() + { + return array_key_exists('modified', $this->wbo_hash); + } + + function payload($payload = null) + { + if (!is_null($payload)){ $this->wbo_hash['payload'] = $payload; } + return array_key_exists('payload', $this->wbo_hash) ? $this->wbo_hash['payload'] : null; + } + + function payload_exists() + { + return array_key_exists('payload', $this->wbo_hash); + } + + function payload_size() + { + return mb_strlen($this->wbo_hash['payload'], '8bit'); + } + + function sortindex($index = null) + { + if (!is_null($index)){ + $this->wbo_hash['sortindex'] = (int)($index); + } + return array_key_exists('sortindex', $this->wbo_hash) ? $this->wbo_hash['sortindex'] : null; + } + + function sortindex_exists() + { + return array_key_exists('sortindex', $this->wbo_hash); + } + + + function validate() + { + + if (!$this->id() || mb_strlen($this->id(), '8bit') > 64 || strpos($this->id(), '/') !== false) + { $this->_error[] = "invalid id"; } + + if ($this->parentid_exists() && mb_strlen($this->parentid(), '8bit') > 64) + { $this->_error[] = "invalid parentid"; } + + if ($this->predecessorid_exists() && mb_strlen($this->predecessorid(), '8bit') > 64) + { $this->_error[] = "invalid predecessorid"; } + + if (!is_numeric($this->modified())) + { $this->_error[] = "invalid modified date"; } + + if (!$this->modified()) + { $this->_error[] = "no modification date"; } + + if (!$this->_collection || mb_strlen($this->_collection, '8bit') > 64) + { $this->_error[] = "invalid collection"; } + + if ($this->sortindex_exists() && + (!is_numeric($this->wbo_hash['sortindex']) || + intval($this->sortindex()) > 999999999 || + intval($this->sortindex()) < -999999999 )) + { $this->_error[] = "invalid sortindex"; } + + if ($this->payload_exists()) + { + if (!is_string($this->wbo_hash['payload'])) + { $this->_error[] = "payload needs to be json-encoded"; } + } + + return !$this->get_error(); + } + + function get_error() + { + return $this->_error; + } + + function clear_error() + { + $this->_error = array(); + } + + function raw_hash() + { + return $this->wbo_hash; + } + + function json() + { + return json_encode($this->wbo_hash); + } +} + + +?> \ No newline at end of file diff --git a/sources/weave_hash.php b/sources/weave_hash.php new file mode 100644 index 0000000..fc951a5 --- /dev/null +++ b/sources/weave_hash.php @@ -0,0 +1,191 @@ + +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +interface WeaveHash { + public function hash($input); + public function verify($input, $existingHash); + public function needsUpdate($existingHash); +} + +class WeaveHashMD5 implements WeaveHash { + public function hash($input) { + return md5($input); + } + + public function verify($input, $existingHash) { + return $this->hash($input) == $existingHash; + } + + public function needsUpdate($existingHash) { + return substr($existingHash, 0, 4) == "$2a$"; + } +} + +class WeaveHashBCrypt implements WeaveHash { + private $_rounds; + + public function __construct($rounds = 12) { + if(CRYPT_BLOWFISH != 1) { + throw new Exception("bcrypt not available"); + } + + $this->_rounds = $rounds; + } + + public function hash($input) { + $hash = crypt($input, $this->getSalt()); + + if (strlen($hash) <= 13) { + throw new Exception("error while generating hash"); + } + + return $hash; + } + + public function verify($input, $existingHash) { + if ($this->isMD5($existingHash)) { + $md5 = new WeaveHashMD5(); + return $md5->verify($input, $existingHash); + } + + $hash = crypt($input, $existingHash); + + return $hash === $existingHash; + } + + public function needsUpdate($existingHash) { + $identifier = $this->getIdentifier(); + return substr($existingHash, 0, strlen($identifier)) != $identifier; + } + + private function isMD5($existingHash) { + return substr($existingHash, 0, 4) != "$2a$"; + } + + private function getSalt() { + $salt = $this->getIdentifier(); + + $bytes = $this->getRandomBytes(16); + + $salt .= $this->encodeBytes($bytes); + + return $salt; + } + + private function getIdentifier() { + return sprintf("$2a$%02d$", $this->_rounds); + } + + private $randomState; + private function getRandomBytes($count) { + $bytes = ''; + + if(function_exists('openssl_random_pseudo_bytes') && + (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win + $bytes = openssl_random_pseudo_bytes($count); + } + + if($bytes === '' && is_readable('/dev/urandom') && + ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) { + $bytes = fread($hRand, $count); + fclose($hRand); + } + + if(strlen($bytes) < $count) { + $bytes = ''; + + if($this->randomState === null) { + $this->randomState = microtime(); + if(function_exists('getmypid')) { + $this->randomState .= getmypid(); + } + } + + for($i = 0; $i < $count; $i += 16) { + $this->randomState = md5(microtime() . $this->randomState); + + if (PHP_VERSION >= '5') { + $bytes .= md5($this->randomState, true); + } else { + $bytes .= pack('H*', md5($this->randomState)); + } + } + + $bytes = substr($bytes, 0, $count); + } + + return $bytes; + } + + private function encodeBytes($input) { + // The following is code from the PHP Password Hashing Framework + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = ''; + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } +} + +class WeaveHashFactory { + public static function factory() { + if (defined("BCRYPT") && BCRYPT) { + return new WeaveHashBCrypt(BCRYPT_ROUNDS); + } else { + return new WeaveHashMD5(); + } + } +} + +?> \ No newline at end of file diff --git a/sources/weave_storage.php b/sources/weave_storage.php new file mode 100644 index 0000000..9afaa9a --- /dev/null +++ b/sources/weave_storage.php @@ -0,0 +1,815 @@ + +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +require_once 'weave_basic_object.php'; +require_once 'weave_utils.php'; +require_once 'settings.php'; + +class WeaveStorage +{ + private $_username; + private $_dbh; + + function __construct($username) + { + + $this->_username = $username; + + log_error("Initalizing DB connecion!"); + + try + { + if ( ! MYSQL_ENABLE ) + { + $path = explode('/', $_SERVER['SCRIPT_FILENAME']); + $db_name = SQLITE_FILE; + array_pop($path); + array_push($path, $db_name); + $db_name = implode('/', $path); + + if ( ! file_exists($db_name) ) + { + log_error("The required sqllite database is not present! $db_name"); + } + + log_error("Starting SQLite connection"); + $this->_dbh = new PDO('sqlite:' . $db_name); + $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + else if ( MYSQL_ENABLE ) + { + log_error("Starting MySQL connection"); + $this->_dbh = new PDO("mysql:host=". MYSQL_HOST .";dbname=". MYSQL_DB, MYSQL_USER, MYSQL_PASSWORD); + } + + } + + catch( PDOException $exception ) + { + log_error("database unavailable " . $exception->getMessage()); + throw new Exception("Database unavailable " . $exception->getMessage() , 503); + } + + } + + function get_connection() + { + return $this->_dbh; + } + + function begin_transaction() + { + try + { + $this->_dbh->beginTransaction(); + } + catch( PDOException $exception ) + { + error_log("begin_transaction: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function commit_transaction() + { + $this->_dbh->commit(); + return 1; + } + + function get_max_timestamp($collection) + { + if (!$collection) + { + return 0; + } + + try + { + $select_stmt = 'select max(modified) from wbo where username = :username and collection = :collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':collection', $collection); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_max_timestamp: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetchColumn(); + return round((float)$result, 2); + } + + function get_collection_list() + { + try + { + $select_stmt = 'select distinct(collection) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetchColumn()) + { + $collections[] = $result; + } + + return $collections; + } + + + function get_collection_list_with_timestamps() + { + try + { + $select_stmt = 'select collection, max(modified) as timestamp from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (float)$result[1]; + } + + return $collections; + } + + function get_collection_list_with_counts() + { + try + { + $select_stmt = 'select collection, count(*) as ct from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list_with_counts: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (int)$result[1]; + } + + return $collections; + } + + function store_object(&$wbo) + { + + try + { + $insert_stmt = 'replace into wbo (username, id, collection, parentid, predecessorid, sortindex, modified, payload, payload_size) + values (:username, :id, :collection, :parentid, :predecessorid, :sortindex, :modified, :payload, :payload_size)'; + $sth = $this->_dbh->prepare($insert_stmt); + + $username = $this->_username; + $id = $wbo->id(); + $collection = $wbo->collection(); + $parentid = $wbo->parentid(); + $predecessorid = $wbo->predecessorid(); + $sortindex = $wbo->sortindex(); + $modified = $wbo->modified(); + $payload = $wbo->payload(); + $payload_size = $wbo->payload_size(); + + $sth->bindParam(':username', $username); + $sth->bindParam(':id', $id); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':parentid', $parentid); + $sth->bindParam(':predecessorid', $predecessorid); + $sth->bindParam(':sortindex', $sortindex); + $sth->bindParam(':modified', $modified); + $sth->bindParam(':payload', $payload); + $sth->bindParam(':payload_size', $payload_size); + + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("store_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + + function update_object(&$wbo) + { + $update = "update wbo set "; + $params = array(); + $update_list = array(); + + #make sure we have an id and collection. No point in continuing otherwise + if (!$wbo->id() || !$wbo->collection()) + { + error_log('Trying to update without a valid id or collection!'); + return 0; + } + + if ($wbo->parentid_exists()) + { + $update_list[] = "parentid = ?"; + $params[] = $wbo->parentid(); + } + + if ($wbo->predecessorid_exists()) + { + $update_list[] = "predecessorid = ?"; + $params[] = $wbo->predecessorid(); + } + + if ($wbo->sortindex_exists()) + { + $update_list[] = "sortindex = ?"; + $params[] = $wbo->sortindex(); + } + + if ($wbo->payload_exists()) + { + $update_list[] = "payload = ?"; + $update_list[] = "payload_size = ?"; + $params[] = $wbo->payload(); + $params[] = $wbo->payload_size(); + } + +# Don't modify the timestamp on a non-payload/non-parent change change + if ($wbo->parentid_exists() || $wbo->payload_exists()) + { +#better make sure we have a modified date. Should have been handled earlier + if (!$wbo->modified_exists()) + { + error_log("Called update_object with no defined timestamp. Please check"); + $wbo->modified(microtime(1)); + } + $update_list[] = "modified = ?"; + $params[] = $wbo->modified(); + + } + + + if (count($params) == 0) + { + return 0; + } + + $update .= join($update_list, ","); + + $update .= " where username = ? and collection = ? and id = ?"; + $params[] = $this->_username; + $params[] = $wbo->collection(); + $params[] = $wbo->id(); + + try + { + $sth = $this->_dbh->prepare($update); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("update_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_object($collection, $id) + { + try + { + $delete_stmt = 'delete from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($delete_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("delete_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_objects($collection, $id = null, $parentid = null, $predecessorid = null, $newer = null, + $older = null, $sort = null, $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null) + { + $params = array(); + $select_stmt = ''; + + if ($limit || $offset || $sort) + { +#sqlite can't do sort or limit deletes without special compiled versions +#so, we need to grab the set, then delete it manually. + + $params = $this->retrieve_objects($collection, $id, 0, 0, $parentid, $predecessorid, $newer, $older, $sort, $limit, $offset, $ids, $index_above, $index_below); + if (!count($params)) + { + return 1; #nothing to delete + } + $paramqs = array(); + $select_stmt = "delete from wbo where username = ? and collection = ? and id in (" . join(", ", array_pad($paramqs, count($params), '?')) . ")"; + array_unshift($params, $collection); + array_unshift($params, $username); + } + else + { + + $select_stmt = "delete from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $parentid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("delete_objects: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function retrieve_object($collection, $id) + { + try + { + $select_stmt = 'select * from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("retrieve_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetch(PDO::FETCH_ASSOC); + + $wbo = new wbo(); + $wbo->populate($result); + return $wbo; + } + + function retrieve_objects($collection, $id = null, $full = null, $direct_output = null, $parentid = null, + $predecessorid = null, $newer = null, $older = null, $sort = null, + $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null) + { + $full_list = $full ? '*' : 'id'; + + + $select_stmt = "select $full_list from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $predecessorid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + if ($limit) + { + $select_stmt .= " limit " . intval($limit); + if ($offset) + { + $select_stmt .= " offset " . intval($offset); + } + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("retrieve_collection: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if ($direct_output) + return $direct_output->output($sth); + + $ids = array(); + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($full) + { + $wbo = new wbo(); + $wbo->populate($result); + $ids[] = $wbo; + } + else + $ids[] = $result{'id'}; + } + return $ids; + } + + function get_storage_total() + { + try + { + $select_stmt = 'select round(sum(length(payload))/1024) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_storage_total: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + return (int)$sth->fetchColumn(); + } + + function get_collection_storage_totals() + { + try + { + $select_stmt = 'select collection, sum(payload_size) from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_storage_total (" . $this->connection_details_string() . "): " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + $results = $sth->fetchAll(PDO::FETCH_NUM); + $sth->closeCursor(); + + $collections = array(); + foreach ($results as $result) + { + $collections[$result[0]] = (int)$result[1]; + } + return $collections; + } + + + function get_user_quota() + { + return null; + } + + function delete_storage($username) + { + log_error("delete storage"); + if (!$username) + { + throw new Exception("3", 404); + } + try + { + $delete_stmt = 'delete from wbo where username = :username'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + $sth->closeCursor(); + } + catch( PDOException $exception ) + { + error_log("delete_user: " . $exception->getMessage()); + return 0; + } + return 1; + + } + + function delete_user($username) + { + log_error("delete User"); + if (!$username) + { + throw new Exception("3", 404); + } + + try + { + $delete_stmt = 'delete from users where username = :username'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + $sth->closeCursor(); + + $delete_wbo_stmt = 'delete from wbo where username = :username'; + $sth = $this->_dbh->prepare($delete_wbo_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("delete_user: " . $exception->getMessage()); + return 0; + } + return 1; + } + + function create_user($username, $password) + { + log_error("Create User - Username: ".$username."|".$password); + + try + { + $create_statement = "insert into users values (:username, :md5)"; + + $sth = $this->_dbh->prepare($create_statement); + $hash = WeaveHashFactory::factory(); + $password = $hash->hash($password); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', $password); + $sth->execute(); + } + catch( PDOException $exception ) + { + log_error("create_user:" . $exception->getMessage()); + error_log("create_user:" . $exception->getMessage()); + return 0; + } + return 1; + } + + function change_password($hash) + { + try + { + $update_statement = "update users set md5 = :md5 where username = :username"; + + $sth = $this->_dbh->prepare($update_statement); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':md5', $hash); + $sth->execute(); + } + catch( PDOException $exception ) + { + log_error("change_password:" . $exception->getMessage()); + return 0; + } + return 1; + } + + #function checks if user exists + function exists_user() + { + try + { + $select_stmt = 'select username from users where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("exists_user: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if (!$result = $sth->fetch(PDO::FETCH_ASSOC)) + { + return null; + } + return 1; + } + + + function get_password_hash() + { + log_error("auth-user: " . $this->_username); + try + { + $select_stmt = 'select md5 from users where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_password_hash: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetchColumn(); + if ($result === FALSE) $result = ""; + + return $result; + } + +} + + +?> diff --git a/sources/weave_utils.php b/sources/weave_utils.php new file mode 100644 index 0000000..a2b1047 --- /dev/null +++ b/sources/weave_utils.php @@ -0,0 +1,286 @@ + +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + + #Error constants + define ('WEAVE_ERROR_INVALID_PROTOCOL', 1); + define ('WEAVE_ERROR_INCORRECT_CAPTCHA', 2); + define ('WEAVE_ERROR_INVALID_USERNAME', 3); + define ('WEAVE_ERROR_NO_OVERWRITE', 4); + define ('WEAVE_ERROR_USERID_PATH_MISMATCH', 5); + define ('WEAVE_ERROR_JSON_PARSE', 6); + define ('WEAVE_ERROR_MISSING_PASSWORD', 7); + define ('WEAVE_ERROR_INVALID_WBO', 8); + define ('WEAVE_ERROR_BAD_PASSWORD_STRENGTH', 9); + define ('WEAVE_ERROR_INVALID_RESET_CODE', 10); + define ('WEAVE_ERROR_FUNCTION_NOT_SUPPORTED', 11); + define ('WEAVE_ERROR_NO_EMAIL', 12); + define ('WEAVE_ERROR_INVALID_COLLECTION', 13); + + + define ('LOG_THE_ERROR', 0); + + function log_error($msg) + { + if ( LOG_THE_ERROR == 1 ) + { + $datei = fopen("/tmp/FSyncMS-error.txt","a"); + $fmsg = sprintf("$msg\n"); + fputs($datei,$fmsg); + fputs($datei,"Server ".print_r( $_SERVER, true)); + fclose($datei); + } + } + + function report_problem($message, $code = 503) + { + $headers = array('400' => '400 Bad Request', + '401' => '401 Unauthorized', + '403' => '403 Forbidden', + '404' => '404 Not Found', + '412' => '412 Precondition Failed', + '503' => '503 Service Unavailable'); + header('HTTP/1.1 ' . $headers{$code},true,$code); + + if ($code == 401) + { + header('WWW-Authenticate: Basic realm="Weave"'); + } + log_error($message); + exit(json_encode($message)); + } + + + function fix_utf8_encoding($string) + { + if(mb_detect_encoding($string . " ", 'UTF-8,ISO-8859-1') == 'UTF-8') + return $string; + else + return utf8_encode($string); + } + + function get_phpinput() + { + #stupid php being helpful with input data... + $putdata = fopen("php://input", "r"); + $string = ''; + while ($data = fread($putdata,2048)) {$string .= $data;} //hier will man ein limit einbauen + return $string; + } + function get_json() + { + $jsonstring = get_phpinput(); + $json = json_decode(fix_utf8_encoding($jsonstring), true); + + if ($json === null) + report_problem(WEAVE_ERROR_JSON_PARSE, 400); + + return $json; + } + + function validate_username($username) + { + if (!$username) + return false; + + if (strlen($username) > 32) + return false; + + return preg_match('/[^A-Z0-9._-]/i', $username) ? false : true; + } + + function validate_collection($collection) + { + if (!$collection) + return false; + + if (strlen($collection) > 32) + return false; + + // allow characters '?' and '=' in the collection string which e.g. + // appear if the following request is send from firefox: + // http:///weave/1.1//storage/clients?full=1 + return preg_match('/[^A-Z0-9?=._-]/i', $collection) ? false : true; + } + + #user exitsts + function exists_user( $db) + { + #$user = strtolower($user); + try{ + + if(!$db->exists_user()) + return 0; + return 1; + } + catch(Exception $e) + { + header("X-Weave-Backoff: 1800"); + report_problem($e->getMessage(), $e->getCode()); + } + } + # Gets the username and password out of the http headers, and checks them against the auth + function verify_user($url_user, $db) + { + if (!$url_user || !preg_match('/^[A-Z0-9._-]+$/i', $url_user)) + report_problem(WEAVE_ERROR_INVALID_USERNAME, 400); + + $auth_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : null; + $auth_pw = array_key_exists('PHP_AUTH_PW', $_SERVER) ? $_SERVER['PHP_AUTH_PW'] : null; + + if (is_null($auth_user) || is_null($auth_pw)) + { + /* CGI/FCGI auth workarounds */ + $auth_str = null; + if (array_key_exists('Authorization', $_SERVER)) + /* Standard fastcgi configuration */ + $auth_str = $_SERVER['Authorization']; + else if (array_key_exists('AUTHORIZATION', $_SERVER)) + /* Alternate fastcgi configuration */ + $auth_str = $_SERVER['AUTHORIZATION']; + else if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) + /* IIS/ISAPI and newer (yet to be released) fastcgi */ + $auth_str = $_SERVER['HTTP_AUTHORIZATION']; + else if (array_key_exists('REDIRECT_HTTP_AUTHORIZATION', $_SERVER)) + /* mod_rewrite - per-directory internal redirect */ + $auth_str = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + if (!is_null($auth_str)) + { + /* Basic base64 auth string */ + if (preg_match('/Basic\s+(.*)$/', $auth_str)) + { + $auth_str = substr($auth_str, 6); + $auth_str = base64_decode($auth_str, true); + if ($auth_str != FALSE) { + $tmp = explode(':', $auth_str); + if (count($tmp) == 2) + { + $auth_user = $tmp[0]; + $auth_pw = $tmp[1]; + } + } + } + } + } + + if ( ! $auth_user || ! $auth_pw) #do this first to avoid the cryptic error message if auth is missing + { + log_error("Auth failed 1 {"); + log_error(" User pw: ". $auth_user ." | ". $auth_pw); + log_error(" Url_user: ". $url_user); + log_error("}"); + report_problem('Authentication failed', '401'); + } + $url_user = strtolower($url_user); + if (strtolower($auth_user) != $url_user) + { + log_error("(140) Missmatch:".strtolower($auth_user)."|".$url_user); + report_problem(WEAVE_ERROR_USERID_PATH_MISMATCH, 400); + } + + try + { + $existingHash = $db->get_password_hash(); + $hash = WeaveHashFactory::factory(); + + if ( ! $hash->verify(fix_utf8_encoding($auth_pw), $existingHash) ) + { + log_error("Auth failed 2 {"); + log_error(" User pw: ". $auth_user ."|".$auth_pw ."|md5:". md5($auth_pw) ."|fix:". fix_utf8_encoding($auth_pw) ."|fix md5 ". md5(fix_utf8_encoding($auth_pw))); + log_error(" Url_user: ".$url_user); + log_error(" Existing hash: ".$existingHash); + log_error("}"); + report_problem('Authentication failed', '401'); + } else { + if ( $hash->needsUpdate($existingHash) ) { + $db->change_password($hash->hash(fix_utf8_encoding($auth_pw))); + } + } + } + catch(Exception $e) + { + header("X-Weave-Backoff: 1800"); + log_error($e->getMessage(), $e->getCode()); + report_problem($e->getMessage(), $e->getCode()); + } + + return true; + } + + function check_quota(&$db) + { + return; + } + + function check_timestamp($collection, &$db) + { + if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER) + && $db->get_max_timestamp($collection) > $_SERVER['HTTP_X_IF_UNMODIFIED_SINCE']) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + } + + function validate_search_params() + { + $params = array(); + $params['parentid'] = (array_key_exists('parentid', $_GET) && mb_strlen($_GET['parentid'], '8bit') <= 64 && strpos($_GET['parentid'], '/') === false) ? $_GET['parentid'] : null; + $params['predecessorid'] = (array_key_exists('predecessorid', $_GET) && mb_strlen($_GET['predecessorid'], '8bit') <= 64 && strpos($_GET['predecessorid'], '/') === false) ? $_GET['predecessorid'] : null; + + $params['newer'] = (array_key_exists('newer', $_GET) && is_numeric($_GET['newer'])) ? round($_GET['newer'],2) : null; + $params['older'] = (array_key_exists('older', $_GET) && is_numeric($_GET['older'])) ? round($_GET['older'],2) : null; + + $params['sort'] = (array_key_exists('sort', $_GET) && ($_GET['sort'] == 'oldest' || $_GET['sort'] == 'newest' || $_GET['sort'] == 'index')) ? $_GET['sort'] : null; + $params['limit'] = (array_key_exists('limit', $_GET) && is_numeric($_GET['limit']) && $_GET['limit'] > 0) ? (int)$_GET['limit'] : null; + $params['offset'] = (array_key_exists('offset', $_GET) && is_numeric($_GET['offset']) && $_GET['offset'] > 0) ? (int)$_GET['offset'] : null; + + $params['ids'] = null; + if (array_key_exists('ids', $_GET)) + { + $params['ids'] = array(); + foreach(explode(',', $_GET['ids']) as $id) + { + if (mb_strlen($id, '8bit') <= 64 && strpos($id, '/') === false) + $params['ids'][] = $id; + } + } + + $params['index_above'] = (array_key_exists('index_above', $_GET) && is_numeric($_GET['index_above']) && $_GET['index_above'] > 0) ? (int)$_GET['index_above'] : null; + $params['index_below'] = (array_key_exists('index_below', $_GET) && is_numeric($_GET['index_below']) && $_GET['index_below'] > 0) ? (int)$_GET['index_below'] : null; + $params['depth'] = (array_key_exists('depth', $_GET) && is_numeric($_GET['depth']) && $_GET['depth'] > 0) ? (int)$_GET['depth'] : null; + + return $params; + } + +?>