mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-11 19:45:06 +00:00
fix(dl): reject local proxies when --remote targets another machine
Gluetun and loopback proxy URIs only listen on this machine. Stop with an error instead of forwarding them, unless the server is on this machine too.
This commit is contained in:
@@ -173,6 +173,13 @@ This means you get access to the **50+ VPN providers** that Gluetun supports, us
|
||||
VPN subscription, through a single uniform interface, with no per-provider integration
|
||||
in unshackle itself.
|
||||
|
||||
!!! warning "Not available with `--remote`"
|
||||
Gluetun listens on your machine, so a server on another machine cannot reach it. With
|
||||
`--remote`, unshackle stops with an error for `--proxy gluetun:...`, and for any proxy that
|
||||
resolves to `localhost` or a loopback address, such as a region query that Gluetun answers.
|
||||
Give `--remote` a proxy the server can reach, or run the service locally. A server on this
|
||||
machine (a `remote_services` URL on `127.0.0.1`) can reach a local proxy, so it is exempt.
|
||||
|
||||
!!! warning "Docker is required"
|
||||
Gluetun needs a working [Docker](https://docs.docker.com/get-docker/) installation on
|
||||
your `PATH`. If Docker is not found, the proxy provider fails to load with an error linking
|
||||
|
||||
@@ -53,6 +53,7 @@ from unshackle.core.events import events
|
||||
from unshackle.core.providers.anilist import parse_anilist_ref
|
||||
from unshackle.core.providers.tvdb import SEASON_TYPES, parse_int
|
||||
from unshackle.core.proxies import Basic, ExpressVPN, Gluetun, Hola, NordVPN, ProtonVPN, SurfsharkVPN, WindscribeVPN
|
||||
from unshackle.core.proxies.resolve import is_loopback
|
||||
from unshackle.core.service import Service, grow_session_pool
|
||||
from unshackle.core.services import Services
|
||||
from unshackle.core.temp import with_task_temp
|
||||
@@ -203,6 +204,16 @@ def title_wanted(candidate: Any, wanted: Collection[str]) -> bool:
|
||||
return bool(candidate.matches_wanted(wanted))
|
||||
|
||||
|
||||
def server_url(server_name: Optional[str]) -> str:
|
||||
"""The configured URL of a remote server, or an empty string when the config cannot be read."""
|
||||
from unshackle.core.remote_service import resolve_server
|
||||
|
||||
try:
|
||||
return resolve_server(server_name)[0]
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def post_script_group(candidate: Any) -> Any:
|
||||
"""Group a title by the folder its outputs share, for post-scripts in season mode."""
|
||||
if isinstance(candidate, Episode):
|
||||
@@ -1312,10 +1323,15 @@ class dl:
|
||||
if cdm_info:
|
||||
log_event("load_cdm", level="INFO", service=self.service, context={"cdm": cdm_info})
|
||||
|
||||
# A server on this machine can reach a local proxy, so only a server elsewhere is guarded
|
||||
self.remote_needs_public_proxy = self.is_remote and not is_loopback(server_url(ctx.params.get("server")))
|
||||
|
||||
self.proxy_providers = []
|
||||
if no_proxy:
|
||||
ctx.params["proxy"] = None
|
||||
else:
|
||||
if self.remote_needs_public_proxy and proxy and proxy.lower().startswith("gluetun:"):
|
||||
raise click.UsageError("Gluetun runs on your machine, so --remote cannot use it.")
|
||||
if proxy_providers is not None:
|
||||
self.proxy_providers = list(proxy_providers)
|
||||
else:
|
||||
@@ -1434,6 +1450,9 @@ class dl:
|
||||
ctx.params["proxy_query"] = None
|
||||
ctx.params["proxy_provider"] = None
|
||||
|
||||
if self.remote_needs_public_proxy and ctx.params.get("proxy") and is_loopback(ctx.params["proxy"]):
|
||||
raise click.UsageError("That proxy is on your machine, so --remote cannot use it.")
|
||||
|
||||
ctx.obj = ContextData(
|
||||
config=self.service_config, cdm=self.cdm, proxy_providers=self.proxy_providers, profile=self.profile
|
||||
)
|
||||
|
||||
@@ -5,9 +5,11 @@ Used by both the REST API handlers and the remote service client.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from unshackle.core.utils.redact import mask_proxy
|
||||
|
||||
@@ -111,3 +113,15 @@ def resolve_proxy(proxy: str, proxy_providers: List[Any]) -> Optional[str]:
|
||||
return proxy_uri
|
||||
|
||||
raise ValueError(f"No proxy provider had a proxy for {proxy}")
|
||||
|
||||
|
||||
def is_loopback(uri: str) -> bool:
|
||||
"""Whether a proxy or server URI points at this machine."""
|
||||
parsed = urlparse(uri if "//" in uri else f"//{uri}")
|
||||
host = parsed.hostname or ""
|
||||
if host == "localhost" or host.endswith(".localhost"):
|
||||
return True
|
||||
try:
|
||||
return ipaddress.ip_address(host).is_loopback
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@@ -31,6 +31,7 @@ from unshackle.core.config import config
|
||||
from unshackle.core.console import console, prompt_user
|
||||
from unshackle.core.constants import AnyTrack
|
||||
from unshackle.core.credential import Credential
|
||||
from unshackle.core.proxies.resolve import is_loopback
|
||||
from unshackle.core.titles import Title_T, Titles_T, remap_titles
|
||||
from unshackle.core.titles.episode import Episode, Series
|
||||
from unshackle.core.titles.movie import Movie, Movies
|
||||
@@ -866,6 +867,10 @@ class RemoteService:
|
||||
country = re.match(r"^(?:[a-z]+:)?([a-z]{2})\d*$", query, re.IGNORECASE)
|
||||
if country:
|
||||
create_data["proxy_region"] = country.group(1).lower()
|
||||
if create_data.get("proxy") and is_loopback(create_data["proxy"]) and not is_loopback(self.client.server_url):
|
||||
# the local CLI resolves the proxy, so a local-only URI is useless to a server elsewhere
|
||||
raise click.ClickException("That proxy is on your machine, so --remote cannot use it.")
|
||||
|
||||
if not no_proxy and not proxy and client_region:
|
||||
create_data["client_region"] = client_region
|
||||
elif not no_proxy and not proxy and self._server_accounts and not self._server_accounts.get("global"):
|
||||
|
||||
Reference in New Issue
Block a user