├── .clippy.toml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .rustfmt.toml ├── AUTHORS ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── RELEASE.md ├── dist.sh ├── src ├── bin │ ├── fireurl │ │ └── main.rs │ └── fireurld │ │ └── main.rs ├── lib.rs └── utils.rs └── systemd └── fireurld.service /.clippy.toml: -------------------------------------------------------------------------------- 1 | avoid-breaking-exported-api = false 2 | upper-case-acronyms-aggressive = true 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | 12 | [*.rs] 13 | indent_style = space 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | Cargo.lock linguist-generated 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.cargo 3 | /vendor 4 | /outdir 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | use_field_init_shorthand = true 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | fireurl authors 2 | =============== 3 | 4 | Maintainer: 5 | - rusty-snake (https://github.com/rusty-snake) 6 | 7 | Committers: 8 | 9 | Contributors: 10 | - Aleksei Ilin (https://github.com/a-ilin) 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | ### Added 10 | 11 | ### Changed 12 | 13 | ### Deprecated 14 | 15 | ### Removed 16 | 17 | ### Fixed 18 | 19 | ### Security 20 | 21 | ## 0.3.1 - 2024-07-26 22 | ### Changed 23 | - Increase schema size to 15, allow point in URL schema (by @a-ilin in #13) 24 | 25 | ## 0.3.0 - 2024-06-27 26 | ### Added 27 | - FIREURLD_BROWSER environment variable to set the browser for fireurld (by @a-ilin in #10) 28 | 29 | ### Changed 30 | - Updated dependencies 31 | 32 | ### Fixed 33 | - dist.sh for 0.2.0 34 | 35 | ## 0.2.0 - 2023-03-04 36 | ### Added 37 | - `FIREURL_BROWSER` env variable to set the browser 38 | > **Note**: This is a stopgap solution and will eventually be removed in the future. 39 | - restriction for the uri 40 | 41 | ### Changed 42 | - Refactored and Improved dist.sh 43 | - Create fireurld socket with 0600 mode 44 | 45 | ## 0.1.0 - 2023-01-05 46 | ### Added 47 | - basic fireurl client and server. 48 | - dist.sh to build the release binaries. 49 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "fireurl" 7 | version = "0.4.0" 8 | dependencies = [ 9 | "libc", 10 | ] 11 | 12 | [[package]] 13 | name = "libc" 14 | version = "0.2.155" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fireurl" 3 | version = "0.4.0" 4 | edition = "2021" 5 | rust-version = "1.61" 6 | description = "Fixing the firejail URL open issue" 7 | repository = "https://github.com/rusty-snake/fireurl" 8 | license = "MIT" 9 | #default-run = "" 10 | 11 | [dependencies] 12 | libc = "0.2" 13 | 14 | [profile.release] 15 | lto = "thin" 16 | strip = "debuginfo" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022,2023 rusty-snake 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fireurl 2 | 3 | Fixing the firejail URL open issue. 4 | 5 | ## Installation 6 | 7 | ### Binary 8 | 9 | There is a statically linked 64-bit musl binary attached to very [release](https://github.com/rusty-snake/fireurl/releases). 10 | You can install it system-wide: 11 | 12 | ```bash 13 | FIREURL_VERSION=0.3.1 14 | mkdir -p /opt/fireurl 15 | curl --proto '=https' --tlsv1.3 -sSf -L "https://github.com/rusty-snake/fireurl/releases/download/v$FIREURL_VERSION/fireurl-v$FIREURL_VERSION-x86_64-unknown-linux-musl.tar.xz" | tar -xJf- -C /opt/fireurl 16 | ``` 17 | 18 | or per user: 19 | 20 | ```bash 21 | FIREURL_VERSION=0.3.1 22 | mkdir -p ~/.local/opt/fireurl 23 | curl --proto '=https' --tlsv1.3 -sSf -L "https://github.com/rusty-snake/fireurl/releases/download/v$FIREURL_VERSION/fireurl-v$FIREURL_VERSION-x86_64-unknown-linux-musl.tar.xz" | tar -xJf- -C ~/.local/opt/fireurl 24 | ``` 25 | 26 | 27 | ### From source 28 | 29 | #### Prerequisites 30 | 31 | - [Rust](https://www.rust-lang.org/) >= 1.61 32 | 33 | #### Build 34 | 35 | ```bash 36 | cargo build --release 37 | ``` 38 | 39 | #### Install 40 | 41 | ```bash 42 | install -Dm0755 target/release/fireurl /usr/local/bin/fireurl 43 | install -Dm0755 target/release/fireurld /usr/local/libexec/fireurld 44 | ``` 45 | 46 | ## Usage 47 | 48 | TBW 49 | 50 | ## Start `fireurld` with systemd 51 | 52 | In order to start fireurld with systemd, you need to create a service unit for it. 53 | Run `systemctl --user edit --full --force fireurld.service`, paste [`systemd/fireurld.service`](systemd/fireurld.service) 54 | and adjust it as necessary. You likely need to change the path to the `fireurld` 55 | binary. Afterwards you can close the editor and execute 56 | `systemctl --user enable --now fireurld.service`. 57 | 58 | ## Start `fireurld` with xdg-autostart 59 | 60 | TBW 61 | 62 | ## Architecture 63 | 64 | Fireurl has a client (fireurl) and a server (fireurld) component. 65 | A url is passed to the client on the command line. If it is running outside 66 | of a container, it opens the url directly (using the `fireurl::open` function). 67 | If it is running inside of a container, it send a request to fireurld, which 68 | must run in the background at this time, via an UNIX domain socket to open the 69 | url. 70 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Major/Minor Release from `main` 2 | 3 | - [ ] Run `cargo update` 4 | - [ ] Update CHANGELOG.md 5 | - [ ] Update `FIREURL_VERSION` in the 'Installation' section of README.md on `main` 6 | - [ ] Draft a new Release in GitHub 7 | - Create a new tag: `v{{version}}` 8 | - Target: `main` 9 | - Title: `v{{version}` 10 | ````markdown 11 | ## What's Changed 12 | {{CHANGELOG.md}} 13 | 14 | **All changes**: https://github.com/rusty-snake/fireurl/compare/v{{previous_version}}...v{{version}} 15 | **Changelog**: https://github.com/rusty-snake/fireurl/blob/main/CHANGELOG.md 16 | 17 | 18 | ## New Contributors 19 | * @user made their first contribution in https://github.com/rusty-snake/fireurl/pull/123 20 | 21 | --- 22 | 23 | ou can use [minisign] (or OpenBSD's signify) to verify the assets like: 24 | 25 | ```bash 26 | minisign -V -P RWS65FES3L8OgwhyZPHwbh1GyXsCZvJtQ5y4LXWHJKMpkhjNXNDt0Bzi -m fireurl-v{{version}}-x86_64-unknown-linux-musl.tar.xz 27 | ``` 28 | 29 | [minisign]: https://jedisct1.github.io/minisign/ 30 | ```` 31 | - Check "Create a discussion for this release" 32 | - [ ] Publish Release 33 | - [ ] Fetch tag 34 | - [ ] Update `version` in Cargo.toml (post-release bump) 35 | - [ ] Build: `MINISIGN= FIREURL_GIT_REF=v{{version}} ./dist.sh` 36 | - [ ] Attach `outdir/*` to Release 37 | 38 | ## Minor/Patch Release from `release/v1.2.3` 39 | 40 | - [ ] Cherry-Pick 41 | - [ ] Run `cargo update` 42 | - [ ] Update CHANGELOG.md on `main` and `release/v1.2.3` 43 | - [ ] Update `version` in Cargo.toml on `release/v1.2.3` 44 | - [ ] Update `FIREURL_VERSION` in the 'Installation' section of README.md on `main` 45 | - [ ] Draft a new Release in GitHub 46 | - Create a new tag: `v{{version}}` 47 | - Target: `release/v1.2.3` 48 | - Title: `v{{version}` 49 | ````markdown 50 | ## What's Changed 51 | {{CHANGELOG.md}} 52 | 53 | **All changes**: https://github.com/rusty-snake/fireurl/compare/v{{previous_version}}...v{{version}} 54 | **Changelog**: https://github.com/rusty-snake/fireurl/blob/main/CHANGELOG.md 55 | 56 | 57 | ## New Contributors 58 | * @user made their first contribution in https://github.com/rusty-snake/fireurl/pull/123 59 | 60 | --- 61 | 62 | ou can use [minisign] (or OpenBSD's signify) to verify the assets like: 63 | 64 | ```bash 65 | minisign -V -P RWS65FES3L8OgwhyZPHwbh1GyXsCZvJtQ5y4LXWHJKMpkhjNXNDt0Bzi -m fireurl-v{{version}}-x86_64-unknown-linux-musl.tar.xz 66 | ```` 67 | 68 | [minisign]: https://jedisct1.github.io/minisign/ 69 | ``` 70 | - Check "Create a discussion for this release" 71 | - [ ] Publish Release 72 | - [ ] Fetch tag 73 | - [ ] Build: `MINISIGN= FIREURL_GIT_REF=v{{version}} ./dist.sh` 74 | - [ ] Attach `outdir/*` to Release 75 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright © 2021,2023 rusty-snake 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | 22 | # USAGE: 23 | # [MINISIGN="/path/to/secret.key"] [FIREURL_GIT_REF=] ./dist.sh 24 | 25 | set -euo pipefail 26 | 27 | # cd into the project directory 28 | cd -P -- "$(readlink -e "$(dirname "$0")")" 29 | 30 | me="$(basename "$0")" 31 | 32 | #TODO: exec > >(tee "$me.log") 33 | 34 | # Do not run if an old outdir exists 35 | [ -d outdir ] && { echo "$me: Please delete 'outdir' first."; exit 1; } 36 | 37 | # Check presents of non-standard programs (everything except coreutils and built-ins) 38 | REQUIRED_PROGRAMS=(cargo git jq podman xz) 39 | for program in "${REQUIRED_PROGRAMS[@]}"; do 40 | if ! command -v "$program" >&-; then 41 | echo "$me: Missing requirement: $program is not installed or could not be found." 42 | echo "Please make sure $program is installed and in \$PATH." 43 | exit 1 44 | fi 45 | done 46 | 47 | # Check working tree 48 | if [[ -n "$(git status --porcelain)" ]]; then 49 | echo "$me: Working tree is not clean." 50 | echo "Please stash all changes: git stash --include-untracked" 51 | exit 1 52 | fi 53 | 54 | # Pull alpine image if necessary 55 | if [[ -z "$(podman image list --noheading alpine:latest)" ]]; then 56 | podman pull docker.io/library/alpine:latest 57 | fi 58 | 59 | # Check if we are allowed to run podman 60 | if [[ "$(podman run --rm alpine:latest echo "hello")" != "hello" ]]; then 61 | echo "$me: podman does not seem to work correctly." 62 | exit 1 63 | fi 64 | 65 | # Get RELEASE_ID: -v[+] 66 | TEMP_WORK_TREE=$(mktemp -d "${TMPDIR:-/tmp}/build-${PWD##*/}.XXXXXX") 67 | # shellcheck disable=SC2064 68 | trap "rm -r '$TEMP_WORK_TREE'" EXIT 69 | for file in Cargo.toml Cargo.lock src/lib.rs; do 70 | mkdir -p "$TEMP_WORK_TREE/$(dirname "$file")" 71 | git show "${FIREURL_GIT_REF:-HEAD}:$file" > "$TEMP_WORK_TREE/$file" 72 | done 73 | RELEASE_ID=$(cargo metadata --manifest-path="$TEMP_WORK_TREE/Cargo.toml" --no-deps --format-version=1 | jq -j '.packages[0].name, "-v", .packages[0].version') 74 | if [[ "${FIREURL_GIT_REF:-HEAD}" != v* ]]; then 75 | RELEASE_ID="$RELEASE_ID+$(git rev-parse --short "${FIREURL_GIT_REF:-HEAD}")" 76 | fi 77 | rm -r "$TEMP_WORK_TREE" 78 | trap - EXIT 79 | 80 | # Vendor all dependencies 81 | cargo --color=never --locked vendor vendor 82 | [ -d .cargo ] && mv -v .cargo .cargo.bak 83 | mkdir -v .cargo 84 | trap "rm -rv .cargo && [ -d .cargo.bak ] && mv -v .cargo.bak .cargo" EXIT 85 | echo "$me: Creating .cargo/config.toml" 86 | cat > .cargo/config.toml < outdir/SHA256SUMS 123 | (cd outdir; sha512sum -- *.tar.xz) > outdir/SHA512SUMS 124 | 125 | if [[ -n "${MINISIGN:-}" ]] && command -v minisign >&-; then 126 | minisign -S -s "$MINISIGN" -m outdir/* 127 | fi 128 | 129 | echo "Success!" 130 | -------------------------------------------------------------------------------- /src/bin/fireurl/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | 3 | use std::env::{args, var_os}; 4 | use std::os::unix::net::UnixDatagram; 5 | use std::process::ExitCode; 6 | 7 | fn main() -> ExitCode { 8 | /* url is expected at argv[1] */ 9 | let url = match args().nth(1) { 10 | Some(url) => url, 11 | None => { 12 | eprintln!("USAGE: fireurl "); 13 | return ExitCode::from(2); 14 | } 15 | }; 16 | 17 | if !fireurl::is_uri_trustworthy(&url) { 18 | eprintln!("INFO: Not opening uri that failed check."); 19 | return ExitCode::from(5); 20 | } 21 | 22 | if var_os("container").is_none() { 23 | /* Not running in a container (firejail, flatpak, podman, ...), open url directly */ 24 | fireurl::open(&url, "FIREURL_BROWSER"); 25 | } else { 26 | /* Running in a container, ask fireurld to open the url */ 27 | let socket = UnixDatagram::unbound().expect("Failed to create unbound UNIX socket"); 28 | match socket.send_to(url.as_bytes(), fireurl::socket_path()) { 29 | Ok(_) => (), 30 | Err(error) => { 31 | eprintln!("ERROR: Failed to send url: {error}"); 32 | return ExitCode::FAILURE; 33 | } 34 | } 35 | } 36 | 37 | ExitCode::SUCCESS 38 | } 39 | -------------------------------------------------------------------------------- /src/bin/fireurld/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | 3 | use std::io::ErrorKind as IoErrorKind; 4 | use std::os::unix::net::UnixDatagram; 5 | use std::process::ExitCode; 6 | use std::str; 7 | 8 | fn main() -> Result<(), ExitCode> { 9 | // TODO: lnix 10 | let orig_umask = unsafe { libc::umask(0o077) }; 11 | let socket = create_socket()?; 12 | unsafe { libc::umask(orig_umask) }; 13 | 14 | loop { 15 | let mut buf = [0; 4096]; 16 | let size = match socket.recv(&mut buf) { 17 | Ok(size) => size, 18 | Err(error) => { 19 | eprintln!("ERROR: Failed to recive message: {error}"); 20 | continue; 21 | } 22 | }; 23 | let url = match str::from_utf8(&buf[..size]) { 24 | Ok(url) => url, 25 | Err(error) => { 26 | eprintln!("ERROR: Received invalid UTF-8: {error}"); 27 | continue; 28 | } 29 | }; 30 | if fireurl::is_uri_trustworthy(url) { 31 | fireurl::open(&url, "FIREURLD_BROWSER"); 32 | } else { 33 | eprintln!("INFO: Not opening uri that failed check."); 34 | } 35 | } 36 | } 37 | 38 | fn create_socket() -> Result { 39 | match UnixDatagram::bind(fireurl::socket_path()) { 40 | Ok(socket) => Ok(socket), 41 | Err(error) => { 42 | eprintln!("ERROR: Failed to bind the socket: {error}"); 43 | 44 | if error.kind() == IoErrorKind::AddrInUse { 45 | eprintln!( 46 | "INFO: Make sure that fireurld is not running and delete '{}'.", 47 | fireurl::socket_path().display() 48 | ); 49 | 50 | Err(ExitCode::from(10)) 51 | } else { 52 | Err(ExitCode::FAILURE) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | 3 | use std::borrow::Cow; 4 | use std::env::var_os; 5 | use std::ffi::OsStr; 6 | use std::fs::create_dir; 7 | use std::path::PathBuf; 8 | use std::process::Command; 9 | 10 | pub mod utils; 11 | 12 | pub fn socket_path() -> PathBuf { 13 | let mut path = PathBuf::from(var_os("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set")); 14 | path.push("fireurl/fireurl0"); 15 | create_dir(path.parent().unwrap()) 16 | .or_else(utils::filter_already_exists) 17 | .expect("Failed to create $XDG_RUNTIME_DIR/fireurl."); 18 | path 19 | } 20 | 21 | /// Checks that `uri` complies to certain restrictions. 22 | /// 23 | /// - Only ASCII graphic character are allowed (! to ~) 24 | /// TODO: Could be stricter 25 | /// - Must be at least 3 characters long 26 | /// - Starts with an letter 27 | /// - Has a colon within the first 15 characters and all preceding characters 28 | /// must be letters, digits, `+`, `-` or `.`. 29 | pub fn is_uri_trustworthy(uri: &str) -> bool { 30 | uri.chars().all(|c| c.is_ascii_graphic()) 31 | && uri.len() >= 3 32 | && uri.bytes().next().unwrap().is_ascii_alphabetic() 33 | && uri 34 | .bytes() 35 | .take(15) 36 | .take_while(|b| b.is_ascii_alphanumeric() || [b'+', b'-', b':', b'.'].contains(b)) 37 | .any(|b| b == b':') 38 | } 39 | 40 | pub fn open>(url: &S, env_name: &str) { 41 | let browser = match var_os(env_name) { 42 | Some(browser) => Cow::Owned(browser), 43 | None => Cow::Borrowed(OsStr::new("firefox")), 44 | }; 45 | // TODO: 46 | // - Make program and commandline configurable. 47 | // - Collect zombies 48 | // - What happens if we start a new main instance outside of a session? 49 | // - Support non http(s) urls 50 | Command::new(browser) 51 | .arg(url.as_ref()) 52 | .spawn() 53 | .expect("Failed to spawn browser"); 54 | } 55 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult}; 2 | 3 | pub fn filter_already_exists(error: IoError) -> IoResult<()> { 4 | if error.kind() == IoErrorKind::AlreadyExists { 5 | Ok(()) 6 | } else { 7 | Err(error) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /systemd/fireurld.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=fireurl deamon 3 | 4 | [Service] 5 | ExecStartPre=-/usr/bin/rm -f ${XDG_RUNTIME_DIR}/fireurl/fireurl0 6 | ExecStart=/opt/fireurl/bin/fireurld 7 | Environment=FIREURLD_BROWSER=firefox 8 | 9 | [Install] 10 | WantedBy=default.target 11 | --------------------------------------------------------------------------------