3

I want to execute composer as non root user. So I renamed main composer executable file to composer-call and then create another executable file called composer.

#composer.sh
su nginx -c "composer-call ${@}" 

Now when I execute composer --version it works but while pass some more complex parameters like composer create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction it shows below error

+ su nginx -c 'composer-call create-project' drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction
su: unrecognized option: stability

Update-1

As per below answer by Jeff I tried his answer but it gives me another error. Expecting a better solution.

composer-call: line 2: can't open ?php: no such file
composer-call: line 3: /bin: Permission denied
+ su nginx -- composer-call composer-call docker-php-entrypoint docker-php-ext-configure docker-php-ext-enable docker-php-ext-install docker-php-source drupal drush pear peardev pecl phar phar.phar php php-config phpdbg phpize This file is part of Composer.
su: must be suid to work properly
+ su nginx -- composer-call composer-call docker-php-entrypoint docker-php-ext-configure docker-php-ext-enable docker-php-ext-install docker-php-source drupal drush pear peardev pecl phar phar.phar php php-config phpdbg phpize
su: must be suid to work properly
composer-call: line 6: syntax error: unexpected word (expecting ")")

NOTE: composer-call is a php cli script.

SkyRar
  • 191
  • To tackle the (second) problem first, su: must be suid to work properly -- it appears your su is not setuid. Can you repair that? – Jeff Schaller Oct 18 '18 at 12:47
  • The 3rd problem appears to be a typo of some sort in the composer-call script; perhaps you could share that portion of the script? – Jeff Schaller Oct 18 '18 at 12:47

3 Answers3

2

Use -- to separate the target user's command so that su doesn't try to parse additional arguments:

#composer.sh
su nginx -- composer-call "${@}" 

as an example:

user1's composer.sh:

#!/bin/sh
set -x
su otheruser -- /home/otheruser/composer-call "$@"

/home/otheruser/composer-call:

#!/bin/sh
echo Hi, I am composer-call, with arguments:
printf '%s\n' "$@"

In action:

./composer.sh create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction
+ su otheruser -- /home/otheruser/composer-call create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction
Password:
Hi, I am composer-call, with arguments:
create-project
drupal-composer/drupal-project:8.x-dev
some-dir
--stability
dev
--no-interaction
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • This gives me below error composer-call: line 2: can't open ?php: no such file composer-call: line 3: /bin: Permission denied su: must be suid to work properly su: must be suid to work properly composer-call: line 6: syntax error: unexpected word (expecting ")") – SkyRar Oct 18 '18 at 00:50
  • It seems like it's invoking composer-call correctly, and you've maybe run into another hurdle? – Jeff Schaller Oct 18 '18 at 00:51
  • In my code composer was getting called correctly also. E.g as per my code composer -V was showing proper output. But when passing multiple parameters it shows error. – SkyRar Oct 18 '18 at 00:55
  • it was correctly calling compose-call and passing some arguments, but su was getting in the way when it saw arguments with leading dashes. Using a double-dash tells su that it should stop looking for options and pass everything else along to composer-call. – Jeff Schaller Oct 18 '18 at 01:17
  • Okay. Fine but this answer doesn't solve my issue. Is it working for you ? – SkyRar Oct 18 '18 at 01:19
  • I don't have your exact environment, so I mocked up a simulation. I'll demonstrate the results in an upcoming edit to answer. – Jeff Schaller Oct 18 '18 at 01:23
  • To install composer you just need to execute curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin -- --filename=composer-call – SkyRar Oct 18 '18 at 01:25
  • Does that really work? That's not how su is documented to take the command. – glenn jackman Oct 18 '18 at 12:42
  • That's how I got it to work; I couldn't quote the whole command nor prevent su from attempting to find options that didn't belong to it. – Jeff Schaller Oct 18 '18 at 12:48
2

This is because "$@" is expanded into separate words and the second word (--stability in your case) is considered an option to su. Here is an example using ls as a command:

#composer.sh
set -x
su nginx -c "ls $@"

Output 1:

$ ./composer.sh -l -a
++ su nginx -c 'ls -l' -a
su: invalid option -- 'a'
Try 'su --help' for more information.

Note that only the first parameter (-l) goes into the string passed to -c. This is why your --version case works. Instead of $@ you should use $* which is expanded into a single word when it is placed in double quotes:

#composer.sh
set -x
su nginx -c "ls $*"

Output 2:

$ ./composer.sh -l -a
++ su nginx -c 'ls -l -a'
total 12
drwxr-xr-x 2 root root 4096 Apr 22 11:05 .
drwxr-xr-x 1 root root 4096 Apr 22 11:05 ..
-rwxr-xr-x 1 root root   28 Apr 22 11:05 composer.sh
-rw-r--r-- 1 root root    0 Apr 22 11:05 test.txt

Note that now all parameters go to -c which produces the correct result.

You can read about it in detail in man bash in the Special Parameters section.

  • That does not work if a parameter has a space in it. E.g. ./composer.sh -l -a "directory name with spaces in it" will not work. – Toxiro Feb 12 '24 at 20:51
  • Yes, of course, knowing that all parameters will be combined into one string and separated by spaces you should yourself take care of preserving spaces where needed in the input string, e.g. ./composer.sh -l -a "directory\ name\ with\ spaces\ in\ it" – user3071170 Feb 18 '24 at 07:48
  • Ok, that works, but it is pretty uncomfortable. It means you cannot use the command as supposed to be. You cannot use auto complete for directories and you cannot copy paste commands without escaping every space. – Toxiro Feb 28 '24 at 19:26
0

Finally encapsulating the composer command inside a here-doc block helped me

#!/bin/sh

su nginx -s /bin/sh <<EOF
composer-call "$@"
EOF

I use below code to create above wrapper script programmatically. :)

printf '%s\n' "#!/bin/sh" "su nginx -s /bin/sh <<EOF" "composer-call \$@" "EOF" > /usr/local/bin/composer

Apart from it su-exec is also a nice alternative. It is a tinny software and can be installed like apk add su-exec

SkyRar
  • 191
  • 1
    That will only work as long as the arguments contain no spaces or special characters. Please see my answer here. –  Nov 04 '18 at 12:01
  • It works perfectly for arguments like create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction – SkyRar Nov 04 '18 at 12:04
  • 1
    It may work with those particular arguments in your case, but your answer is public, and that use of $@ is flawed. Try this instead: su nginx -c 'composer-call "$@"' -- argv0 "$@". –  Nov 04 '18 at 12:14
  • Thanks @mosvy I will surely try your answer also. – SkyRar Nov 04 '18 at 12:21
  • Your new version will pass everything as a single argument to composer-call -- ie. it won't work at all (but you could "split" it by passing arguments of the form "; reboot; " ;-)). And it will also expand backquotes, variables, $((...)), etc. in the arguments. –  Nov 04 '18 at 13:21
  • @mosvy The parameters should not be spilted in my case and should be passed as a single argument to composer-call. Atleast for composer everything works perfectly as I tested it. So please don't guess that it will not work without testing it. And I don't understand why it is required to split the parameters ?? – SkyRar Nov 10 '18 at 10:28