2 cat > /dev
/null
<< EndOfLicence
4 Copyright 2007 Raphael James Cohn
6 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
12 Unless required by applicable law or agreed to in writing, software distributed under the License
13 is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14 or implied. See the License for the specific language governing permissions and limitations under
23 readonly version
="0.02"
24 readonly userSpecifiedDataErrorExitCode
=1
25 readonly invalidCommandLineOption
=2
26 readonly internalErrorExitCode
=3
27 readonly invalidEnvironmentExitCode
=4
28 readonly ipadXorByte
=0x36
29 readonly opadXorByte
=0x5c
31 # Command-like aliases
32 readonly sha1
="openssl dgst -sha1 -binary"
33 readonly base64encode
="openssl enc -base64 -e -in"
34 readonly base64decode
="openssl enc -base64 -d -in"
37 declare -a temporaryFiles
39 function base64EncodedMD5
41 openssl dgst
-md5 -binary "$1" | openssl enc
-e -base64
44 function printErrorMessage
46 printf "%s: %s\n" "$1" "$2" 1>&2
49 function printErrorHelpAndExit
51 printErrorMessage
"$weAreKnownAs" "$1"
55 function checkProgramIsInEnvironment
57 if [ ! -x "$(which $1)" ]; then
58 printErrorHelpAndExit
"Environment Error: $1 not found on the path or not executable" $invalidEnvironmentExitCode
62 # Do not use this from directly. Due to a bug in bash, array assignments do not work when the function is used with command substitution
63 function createTemporaryFile
65 local temporaryFile
="$(mktemp "$temporaryDirectory/$$.
$1.XXXXXXXX
")" || printErrorHelpAndExit
"Environment Error: Could not create a temporary file. Please check you /tmp folder permissions allow files and folders to be created and disc space." $invalidEnvironmentExitCode
66 local length
="${#temporaryFiles[@]}"
67 temporaryFiles
[$length]="$temporaryFile"
70 function mostRecentTemporaryFile
72 local length
="${#temporaryFiles[@]}"
74 ((lastIndex
= --length))
75 echo "${temporaryFiles[$lastIndex]}"
78 function deleteTemporaryFile
80 rm -f "$1" || printErrorHelpAndExit
"Environment Error: Could not delete a temporary file ($1)." $invalidEnvironmentExitCode
83 function removeTemporaryFiles
85 length
="${#temporaryFiles[@]}"
86 if [ $length -eq 0 ]; then
89 for temporaryFile
in ${temporaryFiles[@]}; do
90 deleteTemporaryFile
"$temporaryFile"
93 length
="${#temporaryFiles[@]}"
96 function checkEnvironment
98 programs
=(openssl curl
od dd printf sed awk sort mktemp
rm grep cp ls env bash
)
99 for program
in "${programs[@]}"; do
100 checkProgramIsInEnvironment
"$program"
103 local temporaryFolder
="${TMPDIR:-/tmp}"
104 if [ ! -x "$temporaryFolder" ]; then
105 printErrorHelpAndExit
"Environment Error: The temporary directory ($temporaryFolder) does not exist. Please set the TMPDIR environment variable to your temporary directory" $invalidEnvironmentExitCode
107 readonly temporaryDirectory
="$temporaryFolder/s3-bash/$weAreKnownAs"
108 mkdir
-p "$temporaryDirectory" || printErrorHelpAndExit
"Environment Error: Could not create a temporary directory ($temporaryDiectory). Please check you /tmp folder permissions allow files and folders to be created and you have sufficient disc space" $invalidEnvironmentExitCode
110 #Check we can create and delete temporary files
111 createTemporaryFile
"check"
112 temporaryFileCheck
="$(mostRecentTemporaryFile)"
113 echo "Checking we can write to temporary files. If this is still here then we could not delete temporary files." > "$temporaryFileCheck"
117 function setErrorTraps
119 trap "removeTemporaryFiles; exit $internalErrorExitCode" INT TERM EXIT
122 function unsetErrorTraps
129 if [ -z "$url" ]; then
130 printErrorHelpAndExit
"URL not specified" $userSpecifiedDataErrorExitCode
131 elif echo $url |
grep -q http
://; then
132 printErrorHelpAndExit
"URL starts with http://" $userSpecifiedDataErrorExitCode
133 elif echo $url |
grep -q https
://; then
134 printErrorHelpAndExit
"URL starts with https://" $userSpecifiedDataErrorExitCode
135 elif echo $url |
grep -v ^
/; then
136 printErrorHelpAndExit
"URL does not start with /" $userSpecifiedDataErrorExitCode
142 local fileToHash
="$1"
143 local fileToWriteTo
="$2"
144 $sha1 "$fileToHash" >> "$fileToWriteTo"
149 local fileToHash
="$1"
150 local fileToWriteTo
="$2"
151 $sha1 -out "$fileToWriteTo" "$fileToHash"
156 local originalKeyFile
="$1"
157 local keySize
="$(ls -l "$originalKeyFile" | awk '{ print $5 }')"
158 if [ ! $keySize -eq 40 ]; then
159 printErrorHelpAndExit
"We do not understand Amazon AWS secret keys which are not 40 bytes long. Have you included a carriage return or line feed by mistake at the end of the secret key file?" $userSpecifiedDataErrorExitCode
163 function padDecodedKeyTo
165 local originalKeyFile
="$1"
167 cp "$originalKeyFile" "$keyFile"
169 local keySize
=$
(ls -l "$keyFile" |
awk '{ print $5 }')
170 if [ $keySize -lt 64 ]; then
171 local zerosToWrite
=$
((64 - $keySize))
172 dd if=/dev
/zero of
=$keyFile bs
=1 count
=$zerosToWrite seek
=$keySize 2> /dev
/null
173 elif [ $keySize -gt 64 ]; then
174 echo "Warning: Support for hashing keys bigger than the SHA1 block size of 64 bytes is untested" 1>&2
175 writeHash
"$originalKeyFile" "$keyFile"
176 local keySize
=$
(ls -l "$keyFile" |
awk '{ print $5 }')
177 if [ $keySize -lt 64 ]; then
178 local zerosToWrite
=$
((64 - $keySize))
179 dd if=/dev
/zero of
=$keyFile bs
=1 count
=$zerosToWrite seek
=$keySize 2> /dev
/null
187 function writeLongAsByte
191 printf "\\$(printf "%o
" $byte)" >> "$file"
194 function readBytesAndXorAndWriteAsBytesTo
198 local outputFile
="$3"
200 od -v -A n
-t uC
"$inputFile" |
awk '{ OFS="\n"; for (i = 1; i <= NF; i++) print $i }' |
202 ((xord
= byte ^ xorByte
))
203 writeLongAsByte
$xord "$outputFile"
207 function writeHexByte
211 printf "\\$(printf "%o
" 0x$byte)" >> "$file"
214 function writeHexString
217 for byte
in $
(echo $hexString |
sed 's/../& /g'); do
218 writeHexByte
"$byte" "$2"
222 function writeStringToSign
224 local outputFile
="$1"
225 echo $verb >> "$outputFile"
226 echo "$contentMD5" >> "$outputFile"
227 echo "$contentType" >> "$outputFile"
228 echo "$currentDateTime" >> "$outputFile"
230 writeStringToSignAmazonHeaders
"$outputFile"
232 urlPath
="$(echo "$url" | awk 'BEGIN { FS="[?
]"} { print $1 }')"
233 urlQueryString
="$(echo "$url" | awk 'BEGIN { FS="[?
]"} { print $2 }')"
234 printf "$urlPath" >> "$outputFile"
235 if [ "$urlQueryString" = "acl" ] ||
[ "$urlQueryString" = "torrent" ]; then
236 printf "?" >> "$outputFile"
237 printf "$urlQueryString" >> "$outputFile"
241 function writeStringToSignAmazonHeaders
()
243 local outputFile
="$1"
245 #Convert all headers to lower case
248 #Add LF to each header
249 awk 'BEGIN { FS=": " } NF == 2 { print tolower($1) ":" $2 }' "$amazonHeaderFile" |
sort >> "$outputFile"
250 #TODO: RFC 2616, section 4.2 (combine repeated headers' values)
251 #TODO: Unfold long lines (not supported elsewhere)
254 function computeAwsAuthorizationHeader
256 checkAwsKey
"$awsAccessSecretKeyIdFile"
258 createTemporaryFile
"key"
259 local tempKeyFile
="$(mostRecentTemporaryFile)"
261 createTemporaryFile
"ipad"
262 local ipadHashingFile
="$(mostRecentTemporaryFile)"
264 createTemporaryFile
"opad"
265 local opadHashingFile
="$(mostRecentTemporaryFile)"
267 createTemporaryFile
"HMAC-SHA1"
268 local hmacSha1File
="$(mostRecentTemporaryFile)"
270 padDecodedKeyTo
"$awsAccessSecretKeyIdFile" "$tempKeyFile"
271 readBytesAndXorAndWriteAsBytesTo
"$tempKeyFile" ipadXorByte
"$ipadHashingFile"
273 writeStringToSign
"$ipadHashingFile"
275 readBytesAndXorAndWriteAsBytesTo
"$tempKeyFile" opadXorByte
"$opadHashingFile"
276 appendHash
"$ipadHashingFile" "$opadHashingFile"
277 writeHash
"$opadHashingFile" "$hmacSha1File"
279 local signature
="$($base64encode "$hmacSha1File")"
281 echo "Authorization: AWS $awsAccessKeyId:$signature"
284 function writeAmazonHeadersForCurl
286 if [ ! -e "$amazonHeaderFile" ]; then
287 printErrorHelpAndExit
"Amazon Header file does not exist" $userSpecifiedDataErrorExitCode
288 elif grep -q ^X-Amz-Date
: "$amazonHeaderFile"; then
289 printErrorHelpAndExit
"X-Amz-Date header not allowed" $userSpecifiedDataErrorExitCode
291 # Consider using sed...
292 awk 'BEGIN { ORS=" "; FS="\0" } { print "--header \"" $1 "\""}' "$amazonHeaderFile" >> "$1"
297 local verbAndAnyData
="$1"
298 local fullUrl
="$protocol://s3.amazonaws.com$url"
299 createTemporaryFile
"curl"
300 local tempCurlCommand
="$(mostRecentTemporaryFile)"
301 local cleanUpCommand
="rm -f "$tempCurlCommand""
303 echo "#! /usr/bin/env bash" >> "$tempCurlCommand"
304 printf "curl %s %s --dump-header \"%s\" " "$verbose" "$verbAndAnyData" "$dumpHeaderFile" >> "$tempCurlCommand"
305 writeAmazonHeadersForCurl
"$tempCurlCommand"
306 printf " --header \"%s\"" "Date: $currentDateTime" >> "$tempCurlCommand"
307 printf " --header \"%s\"" "$authorizationHeader" >> "$tempCurlCommand"
308 if [ ! -z "$contentType" ]; then
309 printf " --header \"Content-Type: %s\"" "$contentType" >> "$tempCurlCommand"
311 if [ ! -z "$contentMD5" ]; then
312 printf " --header \"Content-MD5: %s\"" "$contentMD5" >> "$tempCurlCommand"
314 printf " \"%s\"\n" "$fullUrl" >> "$tempCurlCommand"
317 exec env bash
"$tempCurlCommand"
330 readonly currentDateTime
="$(LC_TIME=C date "+%a
, %d
%h
%Y
%T
%z
")"
332 readonly authorizationHeader
="$(computeAwsAuthorizationHeader)"
333 runCurl
"$verbToPass"