Blob Blame History Raw
--- /usr/share/rear/lib/global-functions.sh	2015-09-03 15:43:01.000000000 +0200
+++ /usr/share/rear/lib/global-functions.sh	2017-01-06 12:02:52.000000000 +0100
@@ -2,27 +2,14 @@
 #
 # global functions for Relax-and-Recover
 #
-#    Relax-and-Recover is free software; you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation; either version 2 of the License, or
-#    (at your option) any later version.
-
-#    Relax-and-Recover is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-
-#    You should have received a copy of the GNU General Public License
-#    along with Relax-and-Recover; if not, write to the Free Software
-#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#
-#
+# This file is part of Relax-and-Recover, licensed under the GNU General
+# Public License. Refer to the included COPYING for full text of license.
 
 function read_and_strip_file () {
 # extracts content from config files. In other words: strips the comments and new lines
-	if test -s "$1" ; then
-		sed -e '/^[[:space:]]/d;/^$/d;/^#/d' "$1"
-	fi
+    if test -s "$1" ; then
+        sed -e '/^[[:space:]]/d;/^$/d;/^#/d' "$1"
+    fi
 }
 
 function is_numeric () {
@@ -34,27 +21,143 @@
     fi
 }
 
+# two explicit functions to be able to test explicitly for true and false (see issue #625)
+# because "tertium non datur" (cf. https://en.wikipedia.org/wiki/Law_of_excluded_middle)
+# does not hold for variables because variables could be unset or have empty value
+# and to test if a variable is true or false its value is tested by that functions
+# but the variable may not have a real value (i.e. be unset or have empty value):
+
+function is_true () {
+    # the argument is usually the value of a variable which needs to be tested
+    # only if there is explicitly a 'true' value then is_true returns true
+    # so that an unset variable or an empty value is not true:
+    case "$1" in
+        ([tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE] | 1)
+        return 0 ;;
+    esac
+    return 1
+}
+
+function is_false () {
+    # the argument is usually the value of a variable which needs to be tested
+    # only if there is explicitly a 'false' value then is_false returns true
+    # so that an unset variable or an empty value is not false
+    # caution: for unset or empty variables is_false is false
+    case "$1" in
+        ([fF] | [nN] | [nN][oO] | [fF][aA][lL][sS][eE] | 0)
+        return 0 ;;
+    esac
+    return 1
+}
+
 ######
 ### Functions for dealing with URLs
 ######
+# URL is the most common form of URI
+# see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
+# where a generic URI is of the form
+# scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
+# e.g. for BACKUP_URL=sshfs://user@host/G/rear/
+# url_scheme = 'sshfs' , url_host = 'user@host' , url_hostname = 'host' , url_username = 'user' , url_path = '/G/rear/'
+# e.g. for BACKUP_URL=usb:///dev/sdb1
+# url_scheme = 'usb' , url_host = '' , url_hostname = '' , url_username = '' , url_path = '/dev/sdb1'
+# FIXME: the ulr_* functions are not safe against special characters
+# for example they break when the password contains spaces
+# but on the other hand permitted characters for values in a URI
+# are ASCII letters, digits, dot, hyphen, underscore, and tilde
+# and any other character must be percent-encoded (in particular the
+# characters : / ? # [ ] @ are reserved as delimiters of URI components
+# and must be percent-encoded when used in the value of a URI component)
+# so that what is missing is support for percent-encoded characters
+# but user-friendly support for percent-encoded characters is not possible
+# cf. http://bugzilla.opensuse.org/show_bug.cgi?id=561626#c7
 
-url_scheme() {
+function url_scheme() {
     local url=$1
+    # the scheme is the leading part up to '://'
     local scheme=${url%%://*}
     # rsync scheme does not have to start with rsync:// it can also be scp style
+    # see the comments in usr/share/rear/prep/RSYNC/default/100_check_rsync.sh
     echo $scheme | grep -q ":" && echo rsync || echo $scheme
 }
 
-url_host() {
+function url_host() {
+    local url=$1
+    local url_without_scheme=${url#*//}
+    # the authority part is the part after the scheme (e.g. 'host' or 'user@host')
+    # i.e. after 'scheme://' all up to but excluding the next '/'
+    # which means it breaks if there is a username that contains a '/'
+    # which should not happen because a POSIX-compliant username
+    # should have only characters from the portable filename character set
+    # which is ASCII letters, digits, dot, hyphen, and underscore
+    # (a hostname must not contain a '/' see RFC 952 and RFC 1123)
+    local authority_part=${url_without_scheme%%/*}
+    # for backward compatibility the url_host function returns the whole authority part
+    # see https://github.com/rear/rear/issues/856
+    # to get only hostname or username use the url_hostname and url_username functions
+    echo $authority_part
+}
+
+function url_hostname() {
     local url=$1
-    local host=${url#*//}
-    echo ${host%%/*}
+    local url_without_scheme=${url#*//}
+    local authority_part=${url_without_scheme%%/*}
+    # if authority_part contains a '@' we assume the 'user@host' format and
+    # then we remove the 'user@' part (i.e. all up to and including the last '@')
+    # so that it also works when the username contains a '@'
+    # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/
+    # (a hostname must not contain a '@' see RFC 952 and RFC 1123)
+    local host_and_port=${authority_part##*@}
+    # if host_and_port contains a ':' we assume the 'host:port' format and
+    # then we remove the ':port' part (i.e. all from and including the last ':')
+    # so that it even works when the hostname contains a ':' (in spite of RFC 952 and RFC 1123)
+    echo ${host_and_port%:*}
 }
 
-url_path() {
+function url_username() {
     local url=$1
-    local path=${url#*//}
-    echo /${path#*/}
+    local url_without_scheme=${url#*//}
+    local authority_part=${url_without_scheme%%/*}
+    # authority_part must contain a '@' when a username is specified
+    echo $authority_part | grep -q '@' || return 0
+    # we remove the '@host' part (i.e. all from and including the last '@')
+    # so that it also works when the username contains a '@'
+    # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/
+    # (a hostname must not contain a '@' see RFC 952 and RFC 1123)
+    local user_and_password=${authority_part%@*}
+    # if user_and_password contains a ':' we assume the 'user:password' format and
+    # then we remove the ':password' part (i.e. all from and including the first ':')
+    # so that it works when the password contains a ':'
+    # (a POSIX-compliant username should not contain a ':')
+    echo $user_and_password | grep -q ':' && echo ${user_and_password%%:*} || echo $user_and_password
+}
+
+function url_password() {
+    local url=$1
+    local url_without_scheme=${url#*//}
+    local authority_part=${url_without_scheme%%/*}
+    # authority_part must contain a '@' when a username is specified
+    echo $authority_part | grep -q '@' || return 0
+    # we remove the '@host' part (i.e. all from and including the last '@')
+    # so that it also works when the username contains a '@'
+    # like 'john@doe' in BACKUP_URL=sshfs://john@doe@host/G/rear/
+    # (a hostname must not contain a '@' see RFC 952 and RFC 1123)
+    local user_and_password=${authority_part%@*}
+    # user_and_password must contain a ':' when a password is specified
+    echo $user_and_password | grep -q ':' || return 0
+    # we remove the 'user:' part (i.e. all up to and including the first ':')
+    # so that it works when the password contains a ':'
+    # (a POSIX-compliant username should not contain a ':')
+    echo ${user_and_password#*:}
+}
+
+function url_path() {
+    local url=$1
+    local url_without_scheme=${url#*//}
+    # the path is all from and including the first '/' in url_without_scheme
+    # i.e. the whole rest after the authority part so that
+    # it may contain an optional trailing '?query' and '#fragment'
+    echo /${url_without_scheme#*/}
 }
 
 backup_path() {
@@ -76,7 +179,7 @@
                path="${TMP_DIR}/isofs${path}"
            fi
            ;;
-       (*)     # nfs, cifs, usb, a.o. need a temporary mount-path 
+       (*)     # nfs, cifs, usb, a.o. need a temporary mount-path
            path="${BUILD_DIR}/outputfs/${NETFS_PREFIX}"
            ;;
     esac
@@ -93,7 +196,7 @@
        (file)  # type file needs a local path (must be mounted by user)
            path="$path/${OUTPUT_PREFIX}"
            ;;
-       (*)     # nfs, cifs, usb, a.o. need a temporary mount-path 
+       (*)     # nfs, cifs, usb, a.o. need a temporary mount-path
            path="${BUILD_DIR}/outputfs/${OUTPUT_PREFIX}"
            ;;
     esac
@@ -129,7 +232,8 @@
             ;;
         (cifs)
             if [ x"$options" = x"$defaultoptions" ];then
-                mount_cmd="mount $v -o $options,guest //$(url_host $url)$(url_path $url) $mountpoint"
+                # defaultoptions contains noatime which is not valid for cifs (issue #752)
+                mount_cmd="mount $v -o rw,guest //$(url_host $url)$(url_path $url) $mountpoint"
             else
                 mount_cmd="mount $v -o $options //$(url_host $url)$(url_path $url) $mountpoint"
             fi
@@ -137,19 +241,48 @@
         (usb)
             mount_cmd="mount $v -o $options $(url_path $url) $mountpoint"
             ;;
-	(sshfs)
-	    mount_cmd="sshfs $(url_host $url):$(url_path $url) $mountpoint -o $options"
+        (sshfs)
+            local authority=$( url_host $url )
+            test "$authority" || Error "Cannot run 'sshfs' because no authority '[user@]host' found in URL '$url'."
+            local path=$( url_path $url )
+            test "$path" || Error "Cannot run 'sshfs' because no path found in URL '$url'."
+            # ensure the fuse kernel module is loaded because sshfs is based on FUSE
+            lsmod | grep -q '^fuse' || modprobe $verbose fuse || Error "Cannot run 'sshfs' because 'fuse' kernel module is not loadable."
+            mount_cmd="sshfs $authority:$path $mountpoint -o $options"
+            ;;
+        (ftpfs)
+            local hostname=$( url_hostname $url )
+            test "$hostname" || Error "Cannot run 'curlftpfs' because no hostname found in URL '$url'."
+            local path=$( url_path $url )
+            test "$path" || Error "Cannot run 'curlftpfs' because no path found in URL '$url'."
+            local username=$( url_username $url )
+            # ensure the fuse kernel module is loaded because ftpfs (via CurlFtpFS) is based on FUSE
+            lsmod | grep -q '^fuse' || modprobe $verbose fuse || Error "Cannot run 'curlftpfs' because 'fuse' kernel module is not loadable."
+            if test "$username" ; then
+                local password=$( url_password $url )
+                if test "$password" ; then
+                    # single quoting is a must for the password
+                    mount_cmd="curlftpfs $verbose -o user='$username:$password' ftp://$hostname$path $mountpoint"
+                else
+                    # also single quoting for the plain username so that it also works for non-POSIX-compliant usernames
+                    # (a POSIX-compliant username should only contain ASCII letters, digits, dot, hyphen, and underscore)
+                    mount_cmd="curlftpfs $verbose -o user='$username' ftp://$hostname$path $mountpoint"
+                fi
+            else
+                mount_cmd="curlftpfs $verbose ftp://$hostname$path $mountpoint"
+            fi
+            ;;
+        (davfs)
+            mount_cmd="mount $v -t davfs http://$(url_host $url)$(url_path $url) $mountpoint"
             ;;
-	(davfs)
-	    mount_cmd="mount $v -t davfs http://$(url_host $url)$(url_path $url) $mountpoint"
-	    ;;
         (*)
             mount_cmd="mount $v -t $(url_scheme $url) -o $options $(url_host $url):$(url_path $url) $mountpoint"
             ;;
     esac
 
     Log "Mounting with '$mount_cmd'"
-    $mount_cmd >&2
+    # eval is required when mount_cmd contains single quoted stuff (e.g. see the above mount_cmd for curlftpfs)
+    eval $mount_cmd >&2
     StopIfError "Mount command '$mount_cmd' failed."
 
     AddExitTask "umount -f $v '$mountpoint' >&2"
@@ -182,7 +315,7 @@
             # and delete only the just used cache
             #rm -rf /var/cache/davfs2/*<mountpoint-hash>*
             rm -rf /var/cache/davfs2/*outputfs*
-            
+
 	    ;;
         (var)
             local var=$(url_host $url)
@@ -228,3 +361,4 @@
     Log "Unmounting '$mountpoint' failed."
     return 1
 }
+