From 9bff739d9624d3a4ba1fb488673ec1a18bc6d395 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 16 Sep 2013 21:35:52 -0700 Subject: several oauth fixes - shred doesn't completely work yet, but it also doesn't completely NOT work, so at least there's some improvement --- include/api.php | 11 ++- include/oauth.php | 67 ++++++++------ library/OAuth1.php | 13 +-- util/shred/FriendicaOAuth.sh | 173 ++++++++++++++++++++++++++++++++++ util/shred/JSON.sh | 129 ++++++++++++++++++++++++++ util/shred/OAuth.sh | 214 +++++++++++++++++++++++++++++++++++++++++++ util/shred/shred | 211 ++++++++++++++++++++++++++++++++++++++++++ version.inc | 2 +- 8 files changed, 781 insertions(+), 39 deletions(-) create mode 100755 util/shred/FriendicaOAuth.sh create mode 100755 util/shred/JSON.sh create mode 100755 util/shred/OAuth.sh create mode 100755 util/shred/shred diff --git a/include/api.php b/include/api.php index a49258d18..d76d82626 100644 --- a/include/api.php +++ b/include/api.php @@ -1823,9 +1823,13 @@ require_once('include/photos.php'); function api_oauth_request_token(&$a, $type){ try{ $oauth = new FKOAuth1(); - $r = $oauth->fetch_request_token(OAuthRequest::from_request()); + $req = OAuthRequest::from_request(); +logger('Req: ' . var_export($req,true)); + $r = $oauth->fetch_request_token($req); }catch(Exception $e){ - echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme(); + logger('oauth_exception: ' . print_r($e->getMessage(),true)); + echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); + killme(); } echo $r; killme(); @@ -1833,7 +1837,8 @@ require_once('include/photos.php'); function api_oauth_access_token(&$a, $type){ try{ $oauth = new FKOAuth1(); - $r = $oauth->fetch_access_token(OAuthRequest::from_request()); + $req = OAuthRequest::from_request(); + $r = $oauth->fetch_access_token($req); }catch(Exception $e){ echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme(); } diff --git a/include/oauth.php b/include/oauth.php index 6ec5285e4..b10802ecd 100644 --- a/include/oauth.php +++ b/include/oauth.php @@ -18,11 +18,12 @@ class FKOAuthDataStore extends OAuthDataStore { function lookup_consumer($consumer_key) { logger(__function__.":".$consumer_key); - //echo "
"; var_dump($consumer_key); killme();
-	  
+//      echo "
"; var_dump($consumer_key); killme();
+
 		$r = q("SELECT client_id, pw, redirect_uri FROM clients WHERE client_id='%s'",
 			dbesc($consumer_key)
 		);
+
 		if (count($r))
 			return new OAuthConsumer($r[0]['client_id'],$r[0]['pw'],$r[0]['redirect_uri']);
 		return null;
@@ -30,11 +31,13 @@ class FKOAuthDataStore extends OAuthDataStore {
 
   function lookup_token($consumer, $token_type, $token) {
 		logger(__function__.":".$consumer.", ". $token_type.", ".$token);
+
 		$r = q("SELECT id, secret,scope, expires, uid  FROM tokens WHERE client_id='%s' AND scope='%s' AND id='%s'",
 			dbesc($consumer->key),
 			dbesc($token_type),
 			dbesc($token)
 		);
+
 		if (count($r)){
 			$ot=new OAuthToken($r[0]['id'],$r[0]['secret']);
 			$ot->scope=$r[0]['scope'];
@@ -46,12 +49,14 @@ class FKOAuthDataStore extends OAuthDataStore {
   }
 
   function lookup_nonce($consumer, $token, $nonce, $timestamp) {
-		//echo __file__.":".__line__."
"; var_dump($consumer,$key); killme();
+//		echo __file__.":".__line__."
"; var_dump($consumer,$key); killme();
+
 		$r = q("SELECT id, secret  FROM tokens WHERE client_id='%s' AND id='%s' AND expires=%d",
 			dbesc($consumer->key),
 			dbesc($nonce),
 			intval($timestamp)
 		);
+
 		if (count($r))
 			return new OAuthToken($r[0]['id'],$r[0]['secret']);
 		return null;
@@ -67,13 +72,14 @@ class FKOAuthDataStore extends OAuthDataStore {
 		} else {
 			$k = $consumer;
 		}
-		
+
 		$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d)",
 				dbesc($key),
 				dbesc($sec),
 				dbesc($k),
 				'request',
 				intval(REQUEST_TOKEN_DURATION));
+
 		if (!$r) return null;
 		return new OAuthToken($key,$sec);
   }
@@ -95,6 +101,7 @@ class FKOAuthDataStore extends OAuthDataStore {
 		
 		$key = $this->gen_token();
 		$sec = $this->gen_token();
+
 		$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires, uid) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d, %d)",
 				dbesc($key),
 				dbesc($sec),
@@ -102,6 +109,7 @@ class FKOAuthDataStore extends OAuthDataStore {
 				'access',
 				intval(ACCESS_TOKEN_DURATION),
 				intval($uverifier));
+
 		if ($r)
 			$ret = new OAuthToken($key,$sec);		
 	}
@@ -131,9 +139,9 @@ class FKOAuth1 extends OAuthServer {
 	}
 	
 	function loginUser($uid){
-		logger("FKOAuth1::loginUser $uid");
+		logger("RedOAuth1::loginUser $uid");
 		$a = get_app();
-		$r = q("SELECT * FROM `user` WHERE uid=%d AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1",
+		$r = q("SELECT * FROM channel WHERE channel_id = %d LIMIT 1",
 			intval($uid)
 		);
 		if(count($r)){
@@ -143,35 +151,36 @@ class FKOAuth1 extends OAuthServer {
 		    header('HTTP/1.0 401 Unauthorized');
 		    die('This api requires login');
 		}
-		$_SESSION['uid'] = $record['uid'];
-		$_SESSION['theme'] = $record['theme'];
-		$_SESSION['mobile_theme'] = get_pconfig($record['uid'], 'system', 'mobile_theme');
+		$_SESSION['uid'] = $record['channel_id'];
+		$_SESSION['theme'] = $record['channel_theme'];
+		$_SESSION['account_id'] = $record['channel_account_id'];
+		$_SESSION['mobile_theme'] = get_pconfig($record['channel_id'], 'system', 'mobile_theme');
 		$_SESSION['authenticated'] = 1;
-		$_SESSION['page_flags'] = $record['page-flags'];
-		$_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $record['nickname'];
+//		$_SESSION['page_flags'] = $record['page-flags'];
+		$_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $record['channel_address'];
 		$_SESSION['addr'] = $_SERVER['REMOTE_ADDR'];
+		$_SESSION['allow_api'] = true;
 
-		//notice( t("Welcome back ") . $record['username'] . EOL);
-		$a->user = $record;
+		$a->channel = $record;
 
-		if(strlen($a->user['timezone'])) {
-			date_default_timezone_set($a->user['timezone']);
-			$a->timezone = $a->user['timezone'];
+		if(strlen($a->channel['channel_timezone'])) {
+			date_default_timezone_set($a->channel['channel_timezone']);
+//			$a->timezone = $a->user['timezone'];
 		}
 
-		$r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1",
-			intval($_SESSION['uid']));
-		if(count($r)) {
-			$a->contact = $r[0];
-			$a->cid = $r[0]['id'];
-			$_SESSION['cid'] = $a->cid;
-		}
-		q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1",
-			dbesc(datetime_convert()),
-			intval($_SESSION['uid'])
-		);
-
-		call_hooks('logged_in', $a->user);		
+//		$r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1",
+//			intval($_SESSION['uid']));
+//		if(count($r)) {
+//			$a->contact = $r[0];
+//			$a->cid = $r[0]['id'];
+//			$_SESSION['cid'] = $a->cid;
+//		}
+//		q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1",
+//			dbesc(datetime_convert()),
+//			intval($_SESSION['uid'])
+//		);
+//
+//		call_hooks('logged_in', $a->user);		
 	}
 	
 }
diff --git a/library/OAuth1.php b/library/OAuth1.php
index 0db6fabcb..b790655af 100644
--- a/library/OAuth1.php
+++ b/library/OAuth1.php
@@ -273,6 +273,7 @@ class OAuthRequest {
           && @strstr($request_headers["Content-Type"],
                      "application/x-www-form-urlencoded")
           ) {
+
         $post_data = OAuthUtil::parse_parameters(
           file_get_contents(self::$POST_INPUT)
         );
@@ -286,15 +287,15 @@ class OAuthRequest {
           $request_headers['Authorization']
         );
         $parameters = array_merge($parameters, $header_parameters);
+
       }
 
     }
     // fix for friendica redirect system
-    
+    // FIXME or don't, but figure out if this is absolutely necessary and act accordingly
     $http_url =  substr($http_url, 0, strpos($http_url,$parameters['q'])+strlen($parameters['q']));
     unset( $parameters['q'] );
     
-	//echo "
".__function__."\n"; var_dump($http_method, $http_url, $parameters, $_SERVER['REQUEST_URI']); killme();
     return new OAuthRequest($http_method, $http_url, $parameters);
   }
 
@@ -514,9 +515,7 @@ class OAuthServer {
    */
   public function fetch_request_token(&$request) {
     $this->get_version($request);
-
     $consumer = $this->get_consumer($request);
-
     // no token required for the initial token request
     $token = NULL;
 
@@ -525,7 +524,6 @@ class OAuthServer {
     // Rev A change
     $callback = $request->get_parameter('oauth_callback');
     $new_token = $this->data_store->new_request_token($consumer, $callback);
-
     return $new_token;
   }
 
@@ -796,7 +794,8 @@ class OAuthUtil {
           );
         $out[$key] = $value;
       }
-    } else {
+    }
+	if((! isset($out)) || (! array_key_exists('Authorization',$out))) {
       // otherwise we don't have apache and are just going to have to hope
       // that $_SERVER actually contains what we need
       $out = array();
@@ -806,6 +805,8 @@ class OAuthUtil {
         $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
 
       foreach ($_SERVER as $key => $value) {
+		if($key === 'REDIRECT_REMOTE_USER')
+			$out['Authorization'] = $value;
         if (substr($key, 0, 5) == "HTTP_") {
           // this is chaos, basically it is just there to capitalize the first
           // letter of every word that is not an initial HTTP and strip HTTP
diff --git a/util/shred/FriendicaOAuth.sh b/util/shred/FriendicaOAuth.sh
new file mode 100755
index 000000000..a20da7879
--- /dev/null
+++ b/util/shred/FriendicaOAuth.sh
@@ -0,0 +1,173 @@
+#!/bin/bash
+# Copyright (c) 2012 Fabio Comuni
+# Copyright (c) 2012 Michael Nowack
+# Copyright (c) 2010, 2012 Yu-Jie Lin
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+FRIENDICAOAUTH_VERSION=0.1.1
+
+F_API_VERSION="1"
+
+# Friendica API endpoints
+
+F_ACCOUNF_UPDATE_PROFILE_IMAGE="${redmatrix_url}/api/account/update_profile_image"
+F_STATUSES_UPDATE="${redmatrix_url}/api/statuses/update"
+F_STATUSES_HOME_TIMELINE="${redmatrix_url}/api/statuses/home_timeline"
+
+F_REQUESF_TOKEN=${redmatrix_url}'/api/oauth/request_token'
+F_ACCESS_TOKEN=${redmatrix_url}'/api/oauth/access_token'
+F_AUTHORIZE_TOKEN=${redmatrix_url}'/api/oauth/authorize'
+
+# Source OAuth.sh
+
+OAuth_sh=$(which OAuth.sh)
+(( $? != 0 )) && echo 'Unable to locate OAuth.sh! Make sure it is in searching PATH.' && exit 1
+source "$OAuth_sh"
+
+FO_debug () {
+  # Print out all parameters, each in own line
+  [[ "$FO_DEBUG" == "" ]] && return
+  local t=$(date +%FT%T.%N)
+  while (( $# > 0 )); do
+    echo "[FO][DEBUG][$t] $1"
+    shift 1
+    done
+  }
+
+FO_extract_value () {
+  # $1 key name
+  # $2 string to find
+  egrep -o "$1=[a-zA-Z0-9-]*" <<< "$2" | cut -d\= -f 2
+  }
+
+
+FO_init() {
+  # Initialize FriendicaOAuth
+  oauth_version='1.0'
+  oauth_signature_method='HMAC-SHA1'
+  oauth_basic_params=(
+    $(OAuth_param 'oauth_consumer_key' "$oauth_consumer_key")
+    $(OAuth_param 'oauth_signature_method' "$oauth_signature_method")
+    $(OAuth_param 'oauth_version' "$oauth_version")
+    )
+  }
+
+FO_access_token_helper () {
+  # Help guide user to get access token
+
+  local resp PIN
+
+  # Request Token
+  
+  local auth_header="$(_OAuth_authorization_header 'Authorization' "$redmatrix_url/" "$oauth_consumer_key" "$oauth_consumer_secret" '' '' "$oauth_signature_method" "$oauth_version" "$(OAuth_nonce)" "$(OAuth_timestamp)" 'POST' "$F_REQUESF_TOKEN" "$(OAuth_param 'oauth_callback' 'oob')"), $(OAuth_param_quote 'oauth_callback' 'oob')"
+
+#  echo $auth_header
+#  echo $F_REQUESF_TOKEN
+  
+  resp=$(curl -s -d '' -H "$auth_header" "$F_REQUESF_TOKEN")
+  FO_rval=$?
+  (( $? != 0 )) && return $FO_rval
+
+  local _oauth_token=$(FO_extract_value 'oauth_token' "$resp")
+  local _oauth_token_secret=$(FO_extract_value 'oauth_token_secret' "$resp")
+
+  echo 'Please go to the following link to get the PIN:'
+  echo "  ${F_AUTHORIZE_TOKEN}?oauth_token=$_oauth_token"
+  
+  read -p 'PIN: ' PIN
+
+  # Access Token
+
+  local auth_header="$(_OAuth_authorization_header 'Authorization' "$redmatrix_url/" "$oauth_consumer_key" "$oauth_consumer_secret" "$_oauth_token" "$_oauth_token_secret" "$oauth_signature_method" "$oauth_version" "$(OAuth_nonce)" "$(OAuth_timestamp)" 'POST' "$F_ACCESS_TOKEN" "$(OAuth_param 'oauth_verifier' "$PIN")"), $(OAuth_param_quote 'oauth_verifier' "$PIN")"
+
+  resp=$(curl -s -d "" -H "$auth_header" "$F_ACCESS_TOKEN")
+  FO_rval=$?
+  (( $? != 0 )) && return $FO_rval
+  
+  FO_ret=(
+    $(FO_extract_value 'oauth_token' "$resp")
+    $(FO_extract_value 'oauth_token_secret' "$resp")
+    $(FO_extract_value 'user_id' "$resp")
+    $(FO_extract_value 'screen_name' "$resp")
+    )
+  }
+
+# APIs
+######
+
+FO_statuses_update () {
+  # $1 format
+  # $2 status
+  # $3 in_reply_to_status_id
+  # The followins are not implemented yet:
+  # $4 lat
+  # $5 long
+  # $6 place_id
+  # $7 display_coordinates
+  local format="$1"
+  [[ "$format" == "" ]] && format="xml"
+  
+  local params=(
+    $(OAuth_param 'status' "$2")
+    )
+  
+  params[${#params[@]}]=$(OAuth_param 'source' "fcli")
+  
+  [[ "$3" != "" ]] && params[${#params[@]}]=$(OAuth_param 'in_reply_to_status_id' "$3") && local in_reply_to_status_id=( '--data-urlencode' "in_reply_to_status_id=$3" )
+    
+  
+  local auth_header=$(OAuth_authorization_header 'Authorization' "$redmatrix_url" '' '' 'POST' "$F_STATUSES_UPDATE.$format" ${params[@]})
+    
+  
+  FO_ret=$(curl -s -H "$auth_header" --data-urlencode "status=$2" --data-urlencode "source=fcli" ${in_reply_to_status_id[@]} "$F_STATUSES_UPDATE.$format")
+
+  FO_rval=$?
+  return $FO_rval
+  }
+
+
+# gets the user home_timeline.
+#
+# @sets FO_ret API response
+# @returns status
+# @public
+FO_statuses_home_timeline () {
+  # $1 format
+  # $2 screen_name
+  # $3 count
+  local format="$1"
+  local screen_name="$2"
+  local count="$3"
+  [[ "$format" == "" ]] && format="xml"
+  [[ "$count" == "" ]] && count=1
+
+  local params=(
+    $(OAuth_param 'screen_name' $screen_name)
+    $(OAuth_param 'count' $count)
+    )
+g
+  local auth_header=$(OAuth_authorization_header 'Authorization' "$redmatrix_url" '' '' 'GET' "$F_STATUSES_HOME_TIMELINE.$format" ${params[@]})
+
+  convscreen=$(OAuth_PE "$screen_name");
+  FO_ret=$(curl -s --get "${F_STATUSES_HOME_TIMELINE}.${format}" --data "screen_name=${convscreen}&count=${count}" --header "${auth_header}")
+  FO_rval=$?
+
+  return $FO_rval
+  }
diff --git a/util/shred/JSON.sh b/util/shred/JSON.sh
new file mode 100755
index 000000000..65f5f1f66
--- /dev/null
+++ b/util/shred/JSON.sh
@@ -0,0 +1,129 @@
+# The MIT License
+#
+# Copyright (c) 2011 Dominic Tarr
+#
+# Permission is hereby granted, free of charge,
+# to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to
+# deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify,
+# merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom
+# the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#
+# https://github.com/dominictarr/JSON.sh
+#
+throw () {
+  echo "$*" >&2
+  exit 1
+}
+
+tokenize () {
+  local ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
+  local CHAR='[^[:cntrl:]"\\]'
+  local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
+  local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
+  local KEYWORD='null|false|true'
+  local SPACE='[[:space:]]+'
+  egrep -ao "$STRING|$NUMBER|$KEYWORD|$SPACE|." --color=never |
+    egrep -v "^$SPACE$"  # eat whitespace
+}
+
+parse_array () {
+  local index=0
+  local ary=''
+  read -r token
+  case "$token" in
+    ']') ;;
+    *)
+      while :
+      do
+        parse_value "$1" "$index"
+        let index=$index+1
+        ary="$ary""$value" 
+        read -r token
+        case "$token" in
+          ']') break ;;
+          ',') ary="$ary," ;;
+          *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;;
+        esac
+        read -r token
+      done
+      ;;
+  esac
+  value=`printf '[%s]' "$ary"`
+}
+
+parse_object () {
+  local key
+  local obj=''
+  read -r token
+  case "$token" in
+    '}') ;;
+    *)
+      while :
+      do
+        case "$token" in
+          '"'*'"') key=$token ;;
+          *) throw "EXPECTED string GOT ${token:-EOF}" ;;
+        esac
+        read -r token
+        case "$token" in
+          ':') ;;
+          *) throw "EXPECTED : GOT ${token:-EOF}" ;;
+        esac
+        read -r token
+        parse_value "$1" "$key"
+        obj="$obj$key:$value"        
+        read -r token
+        case "$token" in
+          '}') break ;;
+          ',') obj="$obj," ;;
+          *) throw "EXPECTED , or } GOT ${token:-EOF}" ;;
+        esac
+        read -r token
+      done
+    ;;
+  esac
+  value=`printf '{%s}' "$obj"`
+}
+
+parse_value () {
+  local jpath="${1:+$1,}$2"
+  case "$token" in
+    '{') parse_object "$jpath" ;;
+    '[') parse_array  "$jpath" ;;
+    # At this point, the only valid single-character tokens are digits.
+    ''|[^0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
+    *) value=$token ;;
+  esac
+  printf "[%s]\t%s\n" "$jpath" "$value"
+}
+
+parse () {
+  read -r token
+  parse_value
+  read -r token
+  case "$token" in
+    '') ;;
+    *) throw "EXPECTED EOF GOT $token" ;;
+  esac
+}
+
+if [ $0 = $BASH_SOURCE ];
+then
+  tokenize | parse
+fi
diff --git a/util/shred/OAuth.sh b/util/shred/OAuth.sh
new file mode 100755
index 000000000..4be4ed35b
--- /dev/null
+++ b/util/shred/OAuth.sh
@@ -0,0 +1,214 @@
+#!/bin/bash
+# Copyright (c) 2010, 2012 Yu-Jie Lin
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+BASHOAUTH_VERSION=0.1.2
+
+OAuth_debug () {
+	# Print out all parameters, each in own line
+	[[ "$OAUTH_DEBUG" == "" ]] && return
+	local t=$(date +%FT%T.%N)
+	while (( $# > 0 )); do
+		echo "[OAuth][DEBUG][$t] $1"
+		shift 1
+		done
+	}
+
+OAuth_nonce () {
+	# Return a nonce
+	md5sum <<< "$RANDOM-$(date +%s.%N)" | cut -d' ' -f 1
+	}
+
+OAuth_timestamp () {
+	# Return timestamp
+	echo "$(date +%s)"
+	}
+
+OAuth_PE () {
+	# Encode $1 using Percent-encoding as defined in
+	# http://tools.ietf.org/html/rfc5849#section-3.6
+	# Any character other than [a-zA-Z0-9-._~] is converted into format %XX
+    [ -n "$1" ] \
+	&& echo -n "$1" | perl -p -e 's/([^A-Za-z0-9-._~])/sprintf("%%%02X", ord($1))/seg'
+}
+
+OAuth_PE_file () {
+	# Encode a file $1 using Percent-encoding as defined in
+	# http://tools.ietf.org/html/rfc5849#section-3.6
+	# $1 a filename, not the content of file
+    perl -p -e 's/([^A-Za-z0-9-._~])/sprintf("%%%02X", ord($1))/seg' < "$1"
+}
+
+OAuth_params_string () {
+	# Sort the paramters and join them into one-line string
+	while (( $# > 0 )); do
+		echo $1
+		shift 1
+		done | sort | tr '\n' '&' | sed 's/&$//'
+	}
+
+OAuth_base_string () {
+	# $1 method: "GET", "POST", etc
+	# $2 url
+	# $3-$N params
+	local method=$1
+	local url=$2
+	shift 2
+
+	local params_string=$(OAuth_params_string $@)
+
+	echo "$method&$(OAuth_PE "$url")&$(OAuth_PE "$params_string")"
+	}
+
+OAuth_param () {
+	# Return a percent encoded key-value pair
+	# $1 key
+	# $2 value
+	echo "$(OAuth_PE "$1")=$(OAuth_PE "$2")"
+	}
+
+OAuth_param_quote () {
+	# Return a percent encoded key-value pair, value is quoted
+	# $1 key
+	# $2 value
+	echo "$(OAuth_PE "$1")=\"$(OAuth_PE "$2")\""
+	}
+
+OAuth_param_file () {
+	# Return a percent encoded key-value pair, the value is an encoded file content
+	# $1 key
+	# $2 filename
+	echo "$(OAuth_PE "$1")=$(OAuth_PE_file "$2")"
+	}
+
+OAuth_param_raw_value () {
+	# Return a percent encoded key-value pair, only key will be encoded by this function
+	# $1 key
+	# $2 value
+	echo "$(OAuth_PE "$1")=$2"
+	}
+
+OAuth_HMAC_SHA1 () {
+	# Hash the text $1 with key $2
+    local text="$1"
+	local key="$2"
+    echo -n "$text" | openssl dgst -sha1 -binary -hmac "$key" | base64
+	}
+
+_OAuth_signature () {
+	# Return the signature, note it's necessary to pass to OAuth_PE before add to header
+	# $1 signature_method
+	# $2 base_string
+	# $3 consumer_secret
+	# $4 token_secret
+	local signature_method="OAuth_${1//-/_}"
+	local base_string=$2
+	local c_secret=$3
+	local t_secret=$4
+	$signature_method "$base_string" "$c_secret&$t_secret"
+	}
+
+OAuth_signature () {
+	# Return the signature, note it's necessary to pass to OAuth_PE before add to header
+	# $1 base_string
+	_OAuth_signature "$oauth_signature_method" "$1" "$oauth_consumer_secret" "$oauth_token_secret"
+	}
+
+_OAuth_authorization_header_params_string () {
+	while (( $# > 0 )); do
+		echo -n "$(cut -d\= -f 1 <<< "$1")=\"$(cut -d\= -f 2 <<< "$1")\""
+		shift 1
+		# Use break to prevent error code being returned
+		(( $# > 0 )) && echo -n ', ' || break
+		done
+	}
+
+_OAuth_authorization_header () {
+	# Return header string
+	# $1 header key
+	# $2 OAuth realm, can be empty string
+	# $3 OAuth consumer key
+	# $4 OAuth consumer secret
+	# $5 OAuth token
+	# $6 OAuth token secret
+	# $7 OAuth signature method
+	# $8 OAuth version
+	# $9 nonce
+	# $10 timestamp
+	# $11 method
+	# $12 url
+	# $13-$N params
+	echo -n "$1: OAuth "
+	[[ "$2" != "" ]] && echo -n "realm=\"$2\", "
+	local oauth_consumer_key="$3"
+	local oauth_consumer_secret="$4"
+	local oauth_token="$5"
+	local oauth_token_secret="$6"
+	local oauth_signature_method="$7"
+	local oauth_version="$8"
+	local oauth_nonce="$9"
+	[[ "$oauth_nonce" == "" ]] && oauth_nonce="$(OAuth_nonce)" 
+	local oauth_timestamp="${10}"
+	[[ "$oauth_timestamp" == "" ]] && oauth_timestamp="$(OAuth_timestamp)" 
+	local method="${11}"
+	local url="${12}"
+	shift 12
+	local params=(
+		$(OAuth_param 'oauth_consumer_key' "$oauth_consumer_key")
+		$(OAuth_param 'oauth_signature_method' "$oauth_signature_method")
+		$(OAuth_param 'oauth_version' "$oauth_version")
+		$(OAuth_param 'oauth_nonce' "$oauth_nonce")
+		$(OAuth_param 'oauth_timestamp' "$oauth_timestamp")
+		)
+	[[ "$oauth_token" != "" ]] && params[${#params[@]}]=$(OAuth_param 'oauth_token' "$oauth_token")
+	local sign_params=${params[@]}
+	while (( $# > 0 )); do
+		sign_params[${#sign_params[@]}]="$1"
+		shift 1
+		done
+	local base_string=$(OAuth_base_string "$method" "$url" ${sign_params[@]})
+	local signature=$(_OAuth_signature "$oauth_signature_method" "$base_string" "$oauth_consumer_secret" "$oauth_token_secret")
+	params[${#params[@]}]=$(OAuth_param 'oauth_signature' "$signature")
+	_OAuth_authorization_header_params_string ${params[@]}
+	}
+
+OAuth_authorization_header () {
+	# Return header string
+	# $1 header key
+	# $2 OAuth realm, can be empty string
+	# $3 OAuth nonce
+	# $4 OAuth timestamp
+	# $5 method
+	# $6 url
+	# $7-$N params
+	local header_key="$1"
+	local realm="$2"
+	local oauth_nonce="$3"
+	local oauth_timestamp="$4"
+	local method="$5"
+	local url="$6"
+	shift 6
+	local params=()
+	while (( $# > 0 )); do
+		params[${#params[@]}]="$1"
+		shift 1
+		done
+	_OAuth_authorization_header "$header_key" "$realm" "$oauth_consumer_key" "$oauth_consumer_secret" "$oauth_token" "$oauth_token_secret" "$oauth_signature_method" "$oauth_version" "$oauth_nonce" "$oauth_timestamp" "$method" "$url" ${params[@]}
+	}
diff --git a/util/shred/shred b/util/shred/shred
new file mode 100755
index 000000000..113898ff9
--- /dev/null
+++ b/util/shred/shred
@@ -0,0 +1,211 @@
+#!/bin/bash
+# Copyright (c) 2012 Fabio Comuni
+# Copyright (c) 2010, 2012 Yu-Jie Lin
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+FCLI_RC="$HOME/.shred.rc"
+
+# Source Config
+[[ -f "$FCLI_RC" ]] && . "$FCLI_RC" || show_config_help 1
+
+# Source FriendicaOAuth.sh
+OAuth_sh=$(which FriendicaOAuth.sh)
+(( $? != 0 )) && echo 'Unable to locate FriendicaOAuth.sh! Make sure it is in searching PATH.' && exit 1
+source "$OAuth_sh"
+
+# Source JSON.sh
+JSON_sh=$(which JSON.sh)
+(( $? != 0 )) && echo 'Unable to locate JSON.sh! Make sure it is in searching PATH.' && exit 1
+source "$JSON_sh"
+
+
+usage () {
+	echo "usage: $0 options
+
+OPTIONS:
+  -h      Show this message
+
+  -c      Command
+    
+	Valid Commands:
+      statuses_update
+      home_timeline
+
+Use -h -c command to get options for the command.
+"
+	exit $1
+	}
+
+show_config_help () {
+	echo "Please create $FCLI_RC with:
+redmatrix_url=YOR_SERVER_URL (no trailing /)
+oauth_consumer_key=YOUR_CONSUMER_KEY
+oauth_consumer_secret=YOUR_CONSUMER_SECRET
+
+You can register new app consumer key and secret at
+  http://yourserver.com/settings/oauth
+"
+	exit $1
+	}
+
+
+show_statuses_update () {
+	echo "Command statuses_update
+
+Requires:
+  -s status
+
+Optional:
+  -r in_reply_to_status_id
+"
+	exit $1
+	}
+
+show_home_timeline () {
+    echo "Command home_timeline"
+    
+    exit $1
+    }
+
+#json helper
+#
+# usage:
+#   echo "$parsed_json" | js key1 [key2 [key3 ...]][,] 
+#
+#   echoes the value of json[key1][key2][key3], without surronding quotes
+#   with "," as last argument, no newline is printed
+#
+js () {
+    local arg
+    local rg='^\['
+    local ret
+    for arg in $@
+    do
+        [[ "$arg" == "," ]] && break;
+        if [[ $arg == ${arg//[0-9]/} ]]
+        then
+            rg="${rg}\"$arg\","
+        else
+            rg="${rg}$arg,"
+        fi
+    done
+    rg="${rg%?}\]"
+    ret=$(grep $rg  | cut -f 2 | sed 's/^"\(.*\)"$/\1/' | sed "s/\\\\\//\//g" )
+    if [[ "$arg" == "," ]]
+    then
+        echo -e "$ret" | tr -d '\012\015'
+    else
+        echo -e "$ret"
+    fi
+}
+
+
+load_config () {
+
+	[[ "$oauth_consumer_key" == "" ]] && show_config_help 1
+	[[ "$oauth_consumer_secret" == "" ]] && show_config_help 1
+
+
+	FO_init
+
+	if [[ "$oauth_token" == "" ]] || [[ "$oauth_token_secret" == "" ]]; then
+		FO_access_token_helper
+		if (( $? == 0 )); then
+			oauth_token=${FO_ret[0]}
+			oauth_token_secret=${FO_ret[1]}
+			echo "oauth_token='${FO_ret[0]}'" >> "$FCLI_RC"
+			echo "oauth_token_secret='${FO_ret[1]}'" >> "$FCLI_RC"
+			echo "Token saved."
+		else
+			echo 'Unable to get access token'
+			exit 1
+		fi
+	fi
+	}
+
+main () {
+	load_config
+	
+	fcli_command=
+	fcli_status=
+	fcli_in_reply_to_status_id=
+	fcli_file=
+	fcli_help_flag=
+	while getopts "c:s:r:f:h" name
+	do
+		case $name in
+		c)	fcli_command="$OPTARG";;
+		s)	fcli_status="$OPTARG";;
+		r)	fcli_in_reply_to_status_id="$OPTARG";;
+		f)	fcli_file="$OPTARG";;
+		h)  fcli_help_flag="1";;
+		?)	usage
+			exit 2;;
+		esac
+	done
+
+	if [[ "$fcli_help_flag" == "1" ]]; then case $fcli_command in
+	statuses_update)
+		show_statuses_update 0
+		;;
+	home_timeline)
+	    show_home_timeline 0
+	    ;;
+	*)
+		[[ "$fcli_command" == "" ]] && usage 0
+		usage 1
+	esac ; fi
+
+	case $fcli_command in
+	home_timeline)
+        FO_statuses_home_timeline 'json' '' 5
+        JS_Parsed=$(echo "$FO_ret" | tokenize | parse)
+		for id in 0 1 2 3 4
+		do
+		    echo "$JS_Parsed" | js $id "user" "name" ,
+		    echo -n " - "
+		    echo "$JS_Parsed" | js $id "created_at"
+            echo "$JS_Parsed" | js $id "text"
+		    echo ""
+		    echo "------------------------------------------------------------------------------"
+		done
+        
+		return $FO_rval
+		;;
+	statuses_update)
+		[[ "$fcli_status" == "" ]] && show_statuses_update 1
+		FO_statuses_update 'json' "$fcli_status" "$fcli_in_reply_to_status_id"
+        JS_Parsed=$(echo "$FO_ret" | tokenize | parse)
+        echo "$JS_Parsed" | js "user" "name" ,
+	    echo -n " - "
+	    echo "$JS_Parsed" | js "created_at"
+        echo "$JS_Parsed" | js "text"
+	    echo ""
+	    echo "------------------------------------------------------------------------------"
+		return $FO_rval
+		;;
+	*)
+		usage 1
+		;;
+	esac
+	return 0
+	}
+
+main "$@"
diff --git a/version.inc b/version.inc
index 2ee9add97..186ac999c 100644
--- a/version.inc
+++ b/version.inc
@@ -1 +1 @@
-2013-09-15.437
+2013-09-16.438
-- 
cgit v1.2.3