christgau / wsdd

A Web Service Discovery host daemon.
MIT License
814 stars 98 forks source link

Strange behavior when device has a dash "-" in the hostname #121

Closed ricardopadilha closed 2 years ago

ricardopadilha commented 2 years ago

Hi,

I've been trying to run wsdd on a Linux machine, and I noticed that if my hostname contains a dash (e.g., "computer-name"), then Windows 10 machines list the Linux machine with quotes in Windows Explorer. If I try to click the quoted name, the connection fails.

Here's a screen capture of what appears on Windows Explorer: image

If I use a name without a dash, then everything works fine. Is there some option or argument that I should be using?

christgau commented 2 years ago

Thanks for reporting that possible issue.

I've just tested wsdd on a Linux machine with the -n/--hostname option and provided a name with a dash, but I also tried changing the hostname accordingly using hostnamectl --set-hostname and ran wsdd without any arguments. In both cases the host appears in the Network view of the Explorer - without quotes. This even applies to "Eric-5N2".

Could you please provide the following information:

  1. How do you launch wsdd?
  2. Which OS you are running wsdd on?
  3. What is the hostname of the machine you are running wsdd on. Please post the output of the following commands
    • hostname -f
    • python3 -c 'import socket; print(socket.gethostname())'

I further assume you are using Windows 10. Is that correct?

ricardopadilha commented 2 years ago

How do you launch wsdd?

From a shell script, using start-stop-daemon from busybox. The command line is (including the variables generated in the script):

start() {
  local name
  local options="--preserve-case --user nobody:nobody --chroot ${tmp_dir}"
  _firmware_check

  name=$(_host_name)
  if [ ! -z "${name}" ]; then
    options="${options:-} --hostname \"${name}\""
  fi

  name=$(_workgroup_name)
  if [ ! -z "${name}" ]; then
    options="${options:-} --workgroup \"${name}\""
  fi

  name=$(_domain_name)
  if [ ! -z "${name}" ]; then
    options="${options:-} --domain \"${name}\""
  fi

  start-stop-daemon -S --oknodo --background --make-pidfile --pidfile "${pidfile}" --exec "${daemon}" -- ${options} &> "${logfile}"
}

This ends up looking like this in the output of ps axw:

python3 /mnt/DroboFS/Shares/DroboApps/wsdd/sbin/wsdd.py --preserve-case --user nobody:nobody --chroot /tmp/DroboApps/wsdd --hostname "Eric-5N2" --workgroup "WORKGROUP"

Which OS you are running wsdd on?

This is an embedded Linux NAS, running kernel 3.2.96 and busybox. If you need, I can dig the rest, but it is pretty spartan. Python 3.9.7 was cross-compiled from source.

# hostname -f

Eric-5N2

# python3 -c 'import socket; print(socket.gethostname())'

Eric-5N2

I further assume you are using Windows 10. Is that correct?

The client machine is running Windows 10 Home edition, yes.

ricardopadilha commented 2 years ago

Huh, I changed the script so that the start-stop-daemon line ends up like this:

python3 /mnt/DroboFS/Shares/DroboApps/wsdd/sbin/wsdd.py --preserve-case --user nobody:nobody --chroot /tmp/DroboApps/wsdd --hostname Eric-5N2 --workgroup WORKGROUP

(i.e., without the additional quotes) and it worked fine.

This is very strange. The quotes are there as part of safe scripting practices, that is, quote strings values whenever possible. I'm kind of surprised that python3 is getting the quotes at all.

I'm pretty sure that hostnames and workgroup names can't have spaces in them, but what about domain names? Could the --domain argument be a string with spaces?

christgau commented 2 years ago

Alright, you already noticed what's going on here.

This ends up looking like this in the output of ps axw:

python3 /mnt/DroboFS/Shares/DroboApps/wsdd/sbin/wsdd.py --preserve-case --user nobody:nobody --chroot /tmp/DroboApps/wsdd --hostname "Eric-5N2" --workgroup "WORKGROUP"

Apparently, you are actually providing a hostname that contains quotes (and a workgroup containing them as well). Notice that the command line you are seeing in the ps output (or htop, or similar tools) show you what Python receives, not what a shell would interpret. So you pass quotes and... you get quotes.

The quotes are there as part of safe scripting practices, that is, quote strings values whenever possible. I'm kind of surprised that python3 is getting the quotes at all.

In principle quotes are good. But you construct a command from variables here. Notice that a variable is expanded only once by the shell. If a variable contains quotes they are used as they are. If you want another substitution pass you need eval:

$ name=foo
$ options="here comes my hostname \"${name}\" \$SHELL"
$ echo $options  
here comes my hostname "foo" $SHELL
$ eval echo $options
here comes my hostname foo /bin/bash

I'm pretty sure that hostnames and workgroup names can't have spaces in them

I am note sure if the "Computer Name" that is shown in the network view of Explorer can contain spaces, but I doubt it. If it would work for discovery/display then it would not work for resolving the hostname. Same goes for the domain name. I am not sure about the work group name, but that's archaic anyway.

In the startup script you do something like this:

 name=$(_host_name)
 if [ ! -z "${name}" ]; then
   options="${options:-} --hostname \"${name}\""
 fi

I am not sure, what _host_name is doing. But if it just returns the output of hostname then there is actually no need for this block and you can remove it. Wsdd automatically uses the hostname of the machine it runs on.

ricardopadilha commented 2 years ago

So you pass quotes and... you get quotes.

I mean, technically I want the quotes because if there is a chance that a hostname can have spaces, I would rather be safe than sorry. Oh well, if passing a hostname with quotes is too troublesome, then I'll just leave it without.

Side note: I want the outcome of the second line, but in the sense that a string that potentially has spaces remains quoted. It is obvious to me now that I'm going the wrong way about it, but I wonder if there is a way to do that without invoking eval, and adding even more backslashes.

But if it just returns the output of hostname then there is actually no need for this block and you can remove it

It reads the hostname from a config file. In theory, it should match the output of hostname, but it may happen that hostname is not yet configured by the time wsdd is started. The only guarantee that I have is that eventually the hostname will be the value from the config file. I know it is weird, but then again, we're talking about embedded Linux here.

In any case, if neither hostnames, domain names, or workgroup names can have spaces, I guess this issue is solved. I'll try to find a way to get the quoting right, but that is not a wsdd issue. Thanks again for the help.

christgau commented 2 years ago

So you pass quotes and... you get quotes.

I mean, technically I want the quotes because if there is a chance that a hostname can have spaces, I would rather be safe than sorry. Oh well, if passing a hostname with quotes is too troublesome, then I'll just leave it without.

But actually, the way spaces are handled above does not protect you "against" spaces. Instead, it breaks functionality.

Let me demonstrate this with a small shell script named args.sh that is a placeholder for start-stop-daemon or wsdd and just prints the arguments as they would be seen by any program (e.g., like argv in C)

#!/bin/bash
for arg in "$@"; do echo " * arg <${arg}>"; done
$ ./args.sh 1 "2 3" 4
 * arg <1>
 * arg <2 3>
 * arg <4>

Now lets do the stuff from the startup/init script:

$ name="foo"
$ options="--hostname \"${name}\""
$ echo $options
--hostname "foo"
$ ./args.sh $options
 * arg <--hostname>
 * arg <"foo">

That's the original "quote problem". Now lets change the name such that it contains spaces

$ name="foo    bar"   # notice multiple spaces
$ options="--hostname \"${name}\""
$ echo $options
--hostname "foo bar"  # only a single space between foo and bar
$ ./args.sh $options
 * arg <--hostname>
 * arg <"foo>
 * arg <bar">

That's definitely not the intention. Instead of two arguments, the application is presented with three and two of them contain a single instance of a quote character. That's what I meant with my unprecise statement "If a variable contains quotes they are used as they are". The reason why one gets three arguments is the word splitting that happens during expansion.

The simple solution here would be eval, which is often considered evil. If you can trust all inputs, the eval approach might be good. Otherwise you should use arrays or make use of parameter expansion with alternate values. Refer to the Bash FAQ #50 for a clean solution.

But if it just returns the output of hostname then there is actually no need for this block and you can remove it

It reads the hostname from a config file. [...]

Ok. I understand.

ricardopadilha commented 2 years ago

Instead of two arguments, the application is presented with three and two of them contain a single instance of a quote character.

Yep, in retrospect it seems obvious. It goes to show that perhaps the easiest (safest?) way to do this would be to pass the arguments via env variable or a config file.

If you can trust all inputs, the eval approach might be good.

My use case probably fits that description, and if that is indeed the case, I might as well trust my input not to have spaces, as it shouldn't have in the first place.

Regardless, thanks again for the help troubleshooting this.