Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Unix sockets writable for user group #56

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,31 @@ The following configuration values are available:

- ``mpd/hostname``:
Which address the MPD server should bind to.
This can be a network address or the path toa Unix socket:
This can be a network address or the path to a Unix socket:

- ``127.0.0.1``: Listens only on the IPv4 loopback interface (default).
- ``::1``: Listens only on the IPv6 loopback interface.
- ``0.0.0.0``: Listens on all IPv4 interfaces.
- ``::``: Listens on all interfaces, both IPv4 and IPv6.
- ``unix:/path/to/unix/socket.sock``: Listen on the Unix socket at the
- ``unix:/var/run/mopidy/mpd.sock``: Listen on the Unix socket at the
specified path. Must be prefixed with ``unix:``.
If `Mopidy is run as a system service <https://docs.mopidy.com/en/latest/running/service/>`_,
``mpd/socket_permissions`` must allow group write access (default)
and users must be added to the ``mopidy`` group (``usermod -a -G mopidy user``)
to communicate with the MPD server over a Unix socket.

- ``mpd/port``:
Which TCP port the MPD server should listen to.
Default: 6600.

- ``mpd/socket_permissions``:
The octal permission value used for the Unix socket created by the MPD server
(only applies if ``mpd/hostname`` is a unix socket).

- ``775``: rwx for user and group, r-x for others (default).
- ``777``: rwx for all users.
- ``755``: rwx for user, r-x for others.

- ``mpd/password``:
The password required for connecting to the MPD server.
If blank, no password is required.
Expand Down
1 change: 1 addition & 0 deletions mopidy_mpd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def get_config_schema(self):
schema = super().get_config_schema()
schema["hostname"] = config.Hostname()
schema["port"] = config.Port(optional=True)
schema["socket_permissions"] = config.String(optional=True)
schema["password"] = config.Secret(optional=True)
schema["max_connections"] = config.Integer(minimum=1)
schema["connection_timeout"] = config.Integer(minimum=1)
Expand Down
2 changes: 2 additions & 0 deletions mopidy_mpd/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, config, core):

self.hostname = network.format_hostname(config["mpd"]["hostname"])
self.port = config["mpd"]["port"]
self.socket_permissions = config["mpd"]["socket_permissions"]
self.uri_map = uri_mapper.MpdUriMapper(core)

self.zeroconf_name = config["mpd"]["zeroconf"]
Expand All @@ -52,6 +53,7 @@ def _setup_server(self, config, core):
},
max_connections=config["mpd"]["max_connections"],
timeout=config["mpd"]["connection_timeout"],
socket_permissions=self.socket_permissions,
)
except OSError as exc:
raise exceptions.FrontendError(f"MPD server startup failed: {exc}")
Expand Down
1 change: 1 addition & 0 deletions mopidy_mpd/ext.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
enabled = true
hostname = 127.0.0.1
port = 6600
socket_permissions = 775
password =
max_connections = 20
connection_timeout = 60
Expand Down
35 changes: 32 additions & 3 deletions mopidy_mpd/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ def get_socket_address(host, port):
else:
return (host, port)

def get_socket_umask(perms):
default_umask = 0o002
if perms is None:
return default_umask
all_perms = 0o777
mask = all_perms - int(perms, 8)
if mask < 0:
logger.error(
f"Invalid Unix socket permission value: {perms}, "
f"reverting to default permission of 775."
)
return default_umask
elif mask >= 0o100:
logger.error(
f"Unix socket permission must allow user rwx, "
f"reverting to default permission of 775."
)
return default_umask
else:
return mask

class ShouldRetrySocketCall(Exception):

Expand Down Expand Up @@ -99,6 +119,7 @@ def format_hostname(hostname):
return hostname



class Server:

"""Setup listener and register it with GLib's event loop."""
Expand All @@ -111,21 +132,29 @@ def __init__(
protocol_kwargs=None,
max_connections=5,
timeout=30,
socket_permissions=None,
):
self.protocol = protocol
self.protocol_kwargs = protocol_kwargs or {}
self.max_connections = max_connections
self.timeout = timeout
self.server_socket = self.create_server_socket(host, port)
self.server_socket = self.create_server_socket(host, port, socket_permissions)
self.address = get_socket_address(host, port)
self.umask = get_socket_umask(socket_permissions)

self.watcher = self.register_server_socket(self.server_socket.fileno())

def create_server_socket(self, host, port):
def create_server_socket(self, host, port, socket_permissions=None):
socket_path = get_unix_socket_path(host)
if socket_path is not None: # host is a path so use unix socket
sock = create_unix_socket()
sock.bind(socket_path)
# apply socket perms from config
socket_umask = get_socket_umask(socket_permissions)
oldmask = os.umask(socket_umask)
try:
sock.bind(socket_path)
finally:
os.umask(oldmask)
else:
# ensure the port is supplied
if not isinstance(port, int):
Expand Down
2 changes: 1 addition & 1 deletion tests/network/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_init_calls_create_server_socket(self):
self.mock, sentinel.host, sentinel.port, sentinel.protocol
)
self.mock.create_server_socket.assert_called_once_with(
sentinel.host, sentinel.port
sentinel.host, sentinel.port, None
)
self.mock.stop()

Expand Down
1 change: 1 addition & 0 deletions tests/test_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_idle_hooked_up_correctly(event, expected):
"mpd": {
"hostname": "foobar",
"port": 1234,
"socket_permissions": "775",
"zeroconf": None,
"max_connections": None,
"connection_timeout": None,
Expand Down