Package: thunderbird
Version: 1:45.7.1-2
Severity: grave
Justification: renders package unusable (for new installations)

Steps to reproduce:

* Use a new account that has never run thunderbird or icedove
* Start thunderbird

Expected result:

* Account creation wizard or something (I can't actually remember
  what the new user UX is)

Actual result:

"""
An existing profile folder (or symlink) '.thunderbird' was found ...
"""

However, my test user definitely does not have ~/.thunderbird or ~/.icedove,
and has never run either program before today (I used a fresh VM to verify).

Looking at the /usr/bin/thunderbird script, this issue seems to be caused by
trying to combine comments with backslash line continuations. I'm honestly
not sure what that parses as, but I'm sure it is not what you intended.

In this conditional:

elif { [ -d "${ID_PROFILE_FOLDER}" ] || [ -L "${ID_PROFILE_FOLDER}" ]; } && \   
    # .icedove exists as folder or symlink
     { [ -d "${TB_PROFILE_FOLDER}" ] || [ -L "${TB_PROFILE_FOLDER}" ]; } && \   
    # .thunderbird exists as folder or symlink
       [ "$(readlink -e "${TB_PROFILE_FOLDER}")" != "${ID_PROFILE_FOLDER}" ]; 
then  # compare if canonical name of both folders equal

the overall expression somehow evaluates to true for my test account,
because the last [ ] expression evaluates to true.

Here is a simplified form of the same thing:

if { echo "eval 1"; false; } && \ #
     { echo "eval 2"; false; } && \ #
       [ "$(readlink -e "${TB_PROFILE_FOLDER}")" != "${ID_PROFILE_FOLDER}" ]; 
then #
echo "wtf"
fi

which outputs "eval 1", "eval 2", "wtf", indicating that the first two
{ } blocks were evaluated but the overall expression did not short-circuit,
and the last expression was used for the "if".

I think this *might* be parsing as the equivalent of this (redundant
semicolons inserted for clarity):

    if {
        { echo "eval 1"; false; } && " ";
        { echo "eval 2"; false; } && " ";
        [ "$(readlink -e ...)" != "..." ];
    }
    then
    echo "wtf"
    fi

in which the whole {} block evaluates to the result of its last statement.
The backslash turns the following space into an unusual command name
containing only a space, then the comment eats everything until the end
of the line; and you don't see an error for the nonexistent executable " "
because the false statement before it causes the && operator to
short-circuit.

Shell scripts are less a programming language, and more a historical
accident. They are a terrible implementation language for anything
complicated or affecting data-integrity, particularly if you do not know
every last corner of the language - a bit like the bad reputation PHP has
built up, they make it easy to construct a script that mostly works most
of the time, but extremely difficult to construct a script that works
correctly in every case.

There are several things you could do to mitigate this:

* Have an expert on the pitfalls of shell scripts review your scripts
  before releasing them to unstable - it's somewhat too late for this now,
  since these scripts are already in unstable

* Write as conservatively as possible, avoiding anything that might be
  considered weird or subtle (and trying to combine backslash escapes with
  comment-until-end-of-line is definitely that)

* Use a structured programming language instead, perhaps Python 3 (the
  subprocess module is excellent) or Perl (IPC::Run is a bit weird but
  works) - either is relatively small when compared with the Gecko engine

* Have a test plan enumerating the supported scenarios
  (as a starting point: new user, user with ~/.icedove, user with
  ~/.thunderbird, error case with both ~/.icedove and ~/.thunderbird), and
  test all supported scenarios for each change to the scripts

Policy ยง10.4 contains some excellent advice on shell scripts
https://www.debian.org/doc/debian-policy/ch-files.html#s-scripts
including: "Shell scripts (sh and bash) other than init.d scripts should
almost certainly start with set -e so that errors are detected ...
Every script should use set -e or check the exit status of *every* command."

My personal rule of thumb is that if the entire content of a shell script
doesn't all fit on your screen (and in your short-term memory!) at the
same time, or if it uses ". /some/library/code", then it is probably too
long to be a shell script and should probably be rewritten in a
programming language. I like Python 3 with the subprocess module as an
implementation of that programming language.

    S

Reply via email to