1

I'm writing a bash script that requires several pieces of user input, primarily directory paths and file names. The program will create the specified directories and files.

When writing the directory path, I request the absolute path - ~/DIR/..., or $PATH/DIR/... or ./DIR/... are not allowed. Directory paths should only be provided in the form of /DIR/DIR/..., file paths in the form /DIR/DIR/.../filename, and file names in the form of filename. I also want to assure the user has not added any command-line options (for example, if I run sudo, I want to assure that the user has not appended -u anywhere in their statement) to their input.

However, I'm not entirely certain of the best way to sanitize their input, since directory paths and filenames may contain dashes, underscores, spaces, periods, and alphanumeric characters. Any other character should be detected.

User input is gathered through read. I'm using an Ubuntu system, so as long as it complies with Ubuntu shell then it'll work. Assume that only the default system packages are installed.

Multiple options are acceptable to handle individual sections of validation (e.g. using -- to treat following data as a parameter and not a command [suggested by Siguza], and then another option for handling directory paths).

Absolute paths are required for correct mapping of directory structure. Any other format of path would cause the program to incorrectly interpret the structure when matched or appended to other paths, due to the functions I'm performing on the strings of paths.

Thank you.


What worked: I used the accepted answer, using realpath in some situations and readlink, and using -- to interpret the following text as parameters. If you want to allow relative paths, then use the result of readlink or realpath as your path. If you want to disallow relative paths, then compare the original string to the result of readlink or realpath.

12
  • 3
    Most commands support the -- option, which makes them treat all following parameters as arguments, and not options/flags. Apart from that, enclose all variables in double quotes to avoid expansion. Commented Aug 20, 2016 at 20:01
  • While that does take care of the first two problems, it still remains that the user can entire directory paths in a format my program cannot accept. I either need to reject invalid input, or conform it to valid input. Commented Aug 20, 2016 at 20:04
  • 1
    @LoganHartman: But anyway, if you have legitimate reasons for restricting your inputs, you should explain them in the question. It's possible that your reasons will map to known solutions. (For example, "I need a path that can safely be passed to the foo utility" might be answered with, "oh, there's a special check_path_for_foo utility that can do that safety-check for you.) Commented Aug 20, 2016 at 21:10
  • 1
    @LoganHartman: mkdir, cd, and cp don't have any restrictions on the filenames they accept; what made you think they did? Re: "I am matching them against static full paths": What kind of comparison do you need? Note that two paths can both be absolute, and be completely different, yet point to the same file. So, please put aside your mistaken scruples, and edit your question to add the relevant details about what you actually need. Commented Aug 20, 2016 at 21:19
  • 1
    To be clear: Valid pathnames on UNIX can typically [POSIX doesn't mandate this, but almost every major UNIX filesystem allows it] contain literally any non-NUL character, including newlines. Your while read is thus quite buggy: Filenames with backslash literals have them stripped (unless doubled up); trailing whitespace is removed by the read command itself if IFS isn't cleared; and if an entry does end in a backslash, read takes it to mean that the line after it is part of the same entry! Commented Aug 21, 2016 at 2:21

1 Answer 1

4

You could do something like this:

#!/bin/bash

set -e    

while read path; do
    result=`realpath -m -- "$path"`
    if [ "$result" != "$path" ] && [ "$result/" != "$path" ] ; then
        echo "Rejected: $path"
    else
        last_char_index=$((${#path}-1))
        last_char=${path:$last_char_index:1}
        if [ "$last_char" == "/" ]; then
            echo "New directory: $path"
            mkdir -- "$path"
        else
            echo "New file: $path"
            touch -- "$path"
        fi
    fi
done

exit 0

I use realpath to catch relative paths, then always use user input between double quotes to avoid commands injection (if any is possible). I guess there are better ways of doing but at the moment that's all I can think of.

Edit: as advised in the comments, I added -- to ensure the file/directory names are safely given to touch, mkdir and realpath.

Sign up to request clarification or add additional context in comments.

9 Comments

Quoting variables does not prevent them from being parsed as options. Use -- for that.
@Siguza Could you please provide an example where this script isn't safe?
I never said it was exploitable, but you could set path = -X and have realpath crash on an invalid option. You should also set -e;, otherwise you could set path = '' and, since error messages are printed to stderr and not stdout, realpath would crash, but the else block would be executed. Again, not exploitable, but probably undesirable.
Interesting. I'll have to try this once I am home and see if it will work.
I accepted this as the answer, because with a little variation it did work. I didn't use the "set" part, because I individually check each command for the exit status, in order to show the user what the status code was if the program failed. While 'realpath' works in some situations, I've found that 'readlink -f' works better in others. @yoones Thank you.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.