├── .rustfmt.toml ├── po ├── meson.build ├── LINGUAS └── POTFILES.in ├── data ├── resources │ ├── style.css │ ├── screenshots │ │ └── main.png │ └── ui │ │ ├── start_page.ui │ │ ├── vaults_page.ui │ │ ├── vaults_page_row.ui │ │ ├── vaults_page_row_password_prompt_window.ui │ │ ├── shortcuts.ui │ │ ├── vaults_page_row_settings_window.ui │ │ ├── window.ui │ │ ├── preferences.ui │ │ └── import_vault_window.ui ├── icons │ ├── meson.build │ ├── io.github.mpobaschnig.Vaults.svg │ └── io.github.mpobaschnig.Vaults-symbolic.svg ├── io.github.mpobaschnig.Vaults.desktop.in.in ├── resources.gresource.xml ├── meson.build ├── io.github.mpobaschnig.Vaults.gschema.xml.in └── io.github.mpobaschnig.Vaults.metainfo.xml.in.in ├── meson_options.txt ├── .transifex.yml ├── .gitignore ├── .editorconfig ├── src ├── ui │ ├── mod.rs │ ├── pages │ │ ├── mod.rs │ │ ├── vaults_page_row_password_prompt_window.rs │ │ └── vaults_page_row_settings_window.rs │ └── preferences.rs ├── config.rs.in ├── user_config.rs ├── legacy │ ├── mod.rs │ ├── user_config.rs │ └── global_config.rs ├── mod.rs ├── util.rs ├── meson.build ├── main.rs ├── backend │ ├── mod.rs │ ├── gocryptfs.rs │ └── cryfs.rs ├── user_config_manager.rs ├── vault.rs └── global_config_manager.rs ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── flatpak.yml │ └── quality.yml ├── Cargo.toml ├── README.md ├── RELEASING.md ├── meson.build └── io.github.mpobaschnig.Vaults.Devel.json /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext(gettext_package, preset: 'glib') 2 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | de 2 | es 3 | fr 4 | it 5 | nl 6 | pt_BR 7 | ru 8 | tr 9 | zh_CN 10 | -------------------------------------------------------------------------------- /data/resources/style.css: -------------------------------------------------------------------------------- 1 | .title-header{ 2 | font-size: 36px; 3 | font-weight: bold; 4 | } 5 | -------------------------------------------------------------------------------- /data/resources/screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpobaschnig/vaults/HEAD/data/resources/screenshots/main.png -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'profile', 3 | type: 'combo', 4 | choices: [ 5 | 'default', 6 | 'development' 7 | ], 8 | value: 'default' 9 | ) 10 | -------------------------------------------------------------------------------- /.transifex.yml: -------------------------------------------------------------------------------- 1 | git: 2 | filters: 3 | - filter_type: file 4 | file_format: PO 5 | source_file: po/vaults.pot 6 | source_language: en 7 | translation_files_expression: "po/.po" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | .flatpak-builder/ 3 | .flatpak/ 4 | .vscode/settings.json 5 | *.ui.in~ 6 | *.ui~ 7 | build-aux/.flatpak-builder/ 8 | build-aux/app 9 | build/ 10 | builddir/ 11 | src/config.rs 12 | target/ 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | charset = utf-8 8 | 9 | [*.{build,yml,ui,yaml}] 10 | indent_size = 2 11 | 12 | [*.{json,py}] 13 | indent_size = 4 14 | 15 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pages; 2 | 3 | mod add_new_vault_window; 4 | mod import_vault_window; 5 | mod preferences; 6 | mod window; 7 | 8 | pub use add_new_vault_window::AddNewVaultWindow; 9 | pub use import_vault_window::ImportVaultDialog; 10 | pub use preferences::VaultsSettingsWindow; 11 | pub use window::ApplicationWindow; 12 | -------------------------------------------------------------------------------- /src/ui/pages/mod.rs: -------------------------------------------------------------------------------- 1 | mod vaults_page_row; 2 | mod vaults_page_row_password_prompt_window; 3 | mod vaults_page_row_settings_window; 4 | 5 | pub use vaults_page_row::VaultsPageRow; 6 | pub use vaults_page_row_password_prompt_window::VaultsPageRowPasswordPromptWindow; 7 | pub use vaults_page_row_settings_window::VaultsPageRowSettingsWindow; 8 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | '@0@.svg'.format(application_id), 3 | install_dir: iconsdir / 'hicolor' / 'scalable' / 'apps' 4 | ) 5 | 6 | install_data( 7 | '@0@-symbolic.svg'.format(base_id), 8 | install_dir: iconsdir / 'hicolor' / 'symbolic' / 'apps', 9 | rename: '@0@-symbolic.svg'.format(application_id) 10 | ) 11 | -------------------------------------------------------------------------------- /src/config.rs.in: -------------------------------------------------------------------------------- 1 | pub const APP_ID: &str = @APP_ID@; 2 | pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@; 3 | pub const LOCALEDIR: &str = @LOCALEDIR@; 4 | pub const PKGDATADIR: &str = @PKGDATADIR@; 5 | pub const PROFILE: &str = @PROFILE@; 6 | pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource"); 7 | pub const VERSION: &str = @VERSION@; 8 | -------------------------------------------------------------------------------- /data/io.github.mpobaschnig.Vaults.desktop.in.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Vaults 3 | Comment=Keep important files safe 4 | Type=Application 5 | Exec=vaults 6 | Terminal=false 7 | Categories=GNOME;GTK;Utility; 8 | Keywords=Gnome;GTK; 9 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)! 10 | Icon=@icon@ 11 | StartupNotify=true 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is, e.g. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/flatpak.yml: -------------------------------------------------------------------------------- 1 | name: flatpak 2 | 3 | on: [pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: bilelmoussaoui/flatpak-github-actions:gnome-45 10 | options: --privileged 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v6 14 | with: 15 | bundle: vaults.flatpak 16 | manifest-path: io.github.mpobaschnig.Vaults.Devel.json 17 | run-tests: true 18 | cache-key: flatpak-builder-${{ github.sha }} 19 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: [pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | quality: 7 | runs-on: ubuntu-24.04 8 | container: fedora:42 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Install dependencies 12 | run: sudo dnf install -y gtk4-devel libadwaita-devel meson gettext cargo rust clippy rustfmt 13 | - name: Run meson setup 14 | run: meson setup build 15 | - name: Run clippy 16 | run: cargo clippy --all-targets --all-features -- -D warnings 17 | - name: Run fmt 18 | run: cargo fmt --all -- --check 19 | - name: Run tests 20 | run: cargo test 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vaults" 3 | version = "0.12.0" 4 | edition = "2024" 5 | rust-version = "1.85" 6 | 7 | [dependencies] 8 | log = "0.4" 9 | pretty_env_logger = "0.5" 10 | gettext-rs = { version = "0.7", features = ["gettext-system"] } 11 | gtk-macros = "0.3" 12 | once_cell = "1.21" 13 | quick-error = "2.0.0" 14 | strum = "0.27" 15 | strum_macros = "0.27" 16 | toml = "0.8" 17 | serde = { version = "1.0", features = ["derive"] } 18 | adw = { version = "0.8", package = "libadwaita", features = ["v1_6"] } 19 | gtk = { version = "0.10", package = "gtk4", features = ["v4_16"] } 20 | proc-mounts = "0.3" 21 | async-channel = "2" 22 | rust-ini = "0.21" 23 | uuid = { version = "1.17", features = ["serde", "v4"] } 24 | -------------------------------------------------------------------------------- /src/user_config.rs: -------------------------------------------------------------------------------- 1 | // user_config.rs 2 | // 3 | // Copyright 2025 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Steps to reproduce** 11 | 12 | Steps to reproduce the behavior: 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | **Current behavior** 19 | 20 | A clear and concise description of the current behavior. 21 | 22 | **Expected behavior** 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Version information** 27 | 28 | - Application: 29 | - CryFS (if related): 30 | - Gocryptfs (if related): 31 | - Distribution: 32 | 33 | **Additional information** 34 | 35 | Please provide the terminal output when the bug happens and if applicable, add screenshots or screencasts to help explain your problem. 36 | -------------------------------------------------------------------------------- /src/legacy/mod.rs: -------------------------------------------------------------------------------- 1 | // mod.rs 2 | // 3 | // Copyright 2025 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | pub mod global_config; 21 | pub mod user_config; 22 | -------------------------------------------------------------------------------- /src/mod.rs: -------------------------------------------------------------------------------- 1 | // mod.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | pub mod backend; 21 | pub mod legacy; 22 | pub mod pages; 23 | -------------------------------------------------------------------------------- /data/resources/ui/start_page.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | # data/ 2 | data/io.github.mpobaschnig.Vaults.desktop.in.in 3 | data/io.github.mpobaschnig.Vaults.gschema.xml.in 4 | data/io.github.mpobaschnig.Vaults.metainfo.xml.in.in 5 | 6 | # data/resources/ui/ 7 | data/resources/ui/add_new_vault_window.ui 8 | data/resources/ui/import_vault_window.ui 9 | data/resources/ui/preferences.ui 10 | data/resources/ui/shortcuts.ui 11 | data/resources/ui/start_page.ui 12 | data/resources/ui/vaults_page_row_password_prompt_window.ui 13 | data/resources/ui/vaults_page_row_settings_window.ui 14 | data/resources/ui/vaults_page_row.ui 15 | data/resources/ui/vaults_page.ui 16 | data/resources/ui/window.ui 17 | 18 | # src/backend/ 19 | src/backend/cryfs.rs 20 | src/backend/gocryptfs.rs 21 | src/backend/mod.rs 22 | 23 | # src/ui/pages/ 24 | src/ui/pages/mod.rs 25 | src/ui/pages/vaults_page_row.rs 26 | src/ui/pages/vaults_page_row_password_prompt_window.rs 27 | src/ui/pages/vaults_page_row_settings_window.rs 28 | 29 | # src/ui/ 30 | src/ui/add_new_vault_window.rs 31 | src/ui/import_vault_window.rs 32 | src/ui/mod.rs 33 | src/ui/window.rs 34 | src/ui/preferences.rs 35 | 36 | # src/ 37 | src/application.rs 38 | src/global_config_manager.rs 39 | src/main.rs 40 | src/mod.rs 41 | src/user_config_manager.rs 42 | src/vault.rs 43 | 44 | -------------------------------------------------------------------------------- /data/resources/ui/vaults_page.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 36 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // uuid.rs 2 | // 3 | // Copyright 2025 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::user_config_manager::UserConfigManager; 21 | use uuid::Uuid; 22 | 23 | pub fn generate_uuid() -> Uuid { 24 | let map = UserConfigManager::instance().get_map(); 25 | for _ in 0..1000 { 26 | let uuid = Uuid::new_v4(); 27 | if map.contains_key(&uuid) { 28 | log::debug!( 29 | "Generated UUID {} already exists, generating a new one", 30 | uuid 31 | ); 32 | continue; 33 | } 34 | log::debug!("Generated unique UUID: {}", uuid); 35 | return uuid; 36 | } 37 | log::error!("Failed to generate a unique UUID after 10 attempts"); 38 | Uuid::nil() 39 | } 40 | -------------------------------------------------------------------------------- /data/resources.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | resources/ui/shortcuts.ui 5 | resources/ui/preferences.ui 6 | resources/ui/window.ui 7 | resources/ui/start_page.ui 8 | resources/ui/vaults_page.ui 9 | resources/ui/vaults_page_row.ui 10 | resources/ui/vaults_page_row_settings_window.ui 11 | resources/ui/add_new_vault_window.ui 12 | resources/ui/import_vault_window.ui 13 | resources/ui/vaults_page_row_password_prompt_window.ui 14 | 15 | resources/style.css 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | global_conf = configuration_data() 2 | global_conf.set_quoted('APP_ID', application_id) 3 | global_conf.set_quoted('PKGDATADIR', pkgdatadir) 4 | global_conf.set_quoted('PROFILE', profile) 5 | global_conf.set_quoted('VERSION', version + version_suffix) 6 | global_conf.set_quoted('GETTEXT_PACKAGE', gettext_package) 7 | global_conf.set_quoted('LOCALEDIR', localedir) 8 | config = configure_file( 9 | input: 'config.rs.in', 10 | output: 'config.rs', 11 | configuration: global_conf 12 | ) 13 | # Copy the config.rs output to the source directory. 14 | run_command( 15 | 'cp', 16 | meson.build_root() / 'src' / 'config.rs', 17 | meson.source_root() / 'src' / 'config.rs', 18 | check: true 19 | ) 20 | 21 | sources = files( 22 | 'backend/cryfs.rs', 23 | 'backend/gocryptfs.rs', 24 | 'backend/mod.rs', 25 | 26 | 'legacy/global_config.rs', 27 | 'legacy/mod.rs', 28 | 'legacy/user_config.rs', 29 | 30 | 'ui/pages/mod.rs', 31 | 'ui/pages/vaults_page_row.rs', 32 | 'ui/pages/vaults_page_row_settings_window.rs', 33 | 'ui/pages/vaults_page_row_password_prompt_window.rs', 34 | 35 | 'ui/add_new_vault_window.rs', 36 | 'ui/import_vault_window.rs', 37 | 'ui/mod.rs', 38 | 'ui/preferences.rs', 39 | 'ui/window.rs', 40 | 41 | 'application.rs', 42 | 'config.rs', 43 | 'global_config_manager.rs', 44 | 'main.rs', 45 | 'mod.rs', 46 | 'user_config_manager.rs', 47 | 'util.rs', 48 | 'vault.rs', 49 | ) 50 | 51 | custom_target( 52 | 'cargo-build', 53 | build_by_default: true, 54 | input: sources, 55 | output: meson.project_name(), 56 | console: true, 57 | install: true, 58 | install_dir: bindir, 59 | depends: resources, 60 | command: [ 61 | cargo_script, 62 | meson.build_root(), 63 | meson.source_root(), 64 | '@OUTPUT@', 65 | profile, 66 | meson.project_name(), 67 | ] 68 | ) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Vaults
3 | Vaults 4 |

5 | 6 |

Keep important files safe

7 | 8 |

9 | Download on Flathub 10 |

11 | 12 |

13 | Main Window 14 |

15 | 16 | Vaults lets you create encrypted vaults in which you can safely store files. 17 | It currently uses [gocryptfs](https://github.com/rfjakob/gocryptfs) and [CryFS](https://github.com/cryfs/cryfs/) for encryption. Please always keep a backup of your encrypted files. 18 | 19 | # How to build 20 | 21 | The supported way of building Vaults using GNOME Builder (or Visual Studio Code with the Flatpak extension) by cloning the repository and simply building and then running it. 22 | 23 | # Contributing 24 | 25 | Pull requests for bugs or translation updates are welcomed! 26 | However, before working on new features, please open a new topic in [discussions](https://github.com/mpobaschnig/vaults/discussions) or comment on a dedicated [issue](https://github.com/mpobaschnig/vaults/issues), so duplicate efforts can be prevented. 27 | To coordinate accordingly, opening a pull request early as a draft is encouraged to make changes transparent to others. 28 | 29 | # Bug reporting 30 | 31 | For bug reports, please try to reproduce the issue with additional debug information enabled by running Vaults in the terminal using: 32 | 33 | ```bash 34 | flatpak run --env=RUST_LOG=trace io.github.mpobaschnig.Vaults 35 | ``` 36 | 37 | When sharing, please be careful of redacting personally identifiable information (PII). 38 | 39 | # Translations 40 | 41 | Imagery is translated mainly using [Transifex](https://www.transifex.com/mpobaschnig/vaults/). Translations over Github pull requests are also accepted, if you prefer to do so. 42 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Vaults release steps 2 | 3 | This list is a reminder for things we need to take care of when releasing a new version of Vaults. 4 | 5 | ## Smoke test / QA 6 | 7 | Some tests that we should do to see if something obvious stopped working: 8 | 9 | CryFS: 10 | 11 | * Add new vault 12 | * Open vault 13 | * Close vault 14 | 15 | gocryptfs: 16 | 17 | * Add new vault 18 | * Open vault 19 | * Close vault 20 | 21 | General: 22 | 23 | * Close Vaults app and start again to see if created vaults are loaded correctly 24 | * Search for a vault 25 | * Deleted one (gocryptfs or CryFS) vault 26 | * Close Vaults app and start again to see if deleted vault was removed correctly 27 | * Open and close the remaining vault again 28 | * Delete last vault 29 | * Close app and restart Vaults app again to see if main screen appears 30 | 31 | ## Update translations 32 | 33 | We use po files, so update it accordingly by either: 34 | 35 | 1. Call xgettext directly: `xgettext --files-from=po/POTFILES.in --from-code=UTF-8 --output po/vaults.pot`, or 36 | 2. Use `meson compile vaults-pot` within the meson build directory 37 | 38 | ## Update screenshots 39 | 40 | In case of some UI changes, we should update the screenshots displaying the current state-of-the-art. 41 | 42 | ## Update metainfo description 43 | 44 | The metainfo file contains information displayed on Flathub and sources that use it (e.g. GNOME Software). 45 | 46 | ## Prepare GitHub Release 47 | 48 | After all these things, we can prepare the relase: 49 | * Create release branch in the following format: `vaults-0.Y` 50 | * Create tag on main in the following format: `0.Y.Z` 51 | * Create tar by: `meson _build && ninja -C _build dist` 52 | * Push branch + tag to Github 53 | * Add tar to release 54 | 55 | ## Prepare Flathub Release 56 | 57 | When we published our new release on GitHub, we can update our Flatpak. We need to update the Flatpak repository by adding new link, eventually updating dependencies, and SHA256 value. 58 | 59 | ## Post-release chore 60 | 61 | When the new version was published on Flathub, we can finally update our branch to the next development version, i.e. bumping the minor version up by one within `meson.build` in the root directory and within the `cargo.toml` (0.Y+1.0). 62 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | subdir('icons') 2 | # Desktop file 3 | desktop_conf = configuration_data() 4 | desktop_conf.set('icon', application_id) 5 | desktop_file = i18n.merge_file( 6 | type: 'desktop', 7 | input: configure_file( 8 | input: '@0@.desktop.in.in'.format(base_id), 9 | output: '@BASENAME@', 10 | configuration: desktop_conf 11 | ), 12 | output: '@0@.desktop'.format(application_id), 13 | po_dir: podir, 14 | install: true, 15 | install_dir: datadir / 'applications' 16 | ) 17 | # Validate Desktop file 18 | if desktop_file_validate.found() 19 | test( 20 | 'validate-desktop', 21 | desktop_file_validate, 22 | args: [ 23 | desktop_file.full_path() 24 | ] 25 | ) 26 | endif 27 | 28 | # Appdata 29 | appdata_conf = configuration_data() 30 | appdata_conf.set('app-id', application_id) 31 | appdata_conf.set('gettext-package', gettext_package) 32 | appdata_file = i18n.merge_file( 33 | input: configure_file( 34 | input: '@0@.metainfo.xml.in.in'.format(base_id), 35 | output: '@BASENAME@', 36 | configuration: appdata_conf 37 | ), 38 | output: '@0@.metainfo.xml'.format(application_id), 39 | po_dir: podir, 40 | install: true, 41 | install_dir: datadir / 'metainfo' 42 | ) 43 | # Validate Appdata 44 | if appstreamcli.found() 45 | test( 46 | 'validate-appdata', appstreamcli, 47 | args: [ 48 | 'validate', '--no-net', '--explain', appdata_file.full_path() 49 | ] 50 | ) 51 | endif 52 | 53 | # GSchema 54 | gschema_conf = configuration_data() 55 | gschema_conf.set('app-id', application_id) 56 | gschema_conf.set('gettext-package', gettext_package) 57 | configure_file( 58 | input: '@0@.gschema.xml.in'.format(base_id), 59 | output: '@0@.gschema.xml'.format(application_id), 60 | configuration: gschema_conf, 61 | install: true, 62 | install_dir: datadir / 'glib-2.0' / 'schemas' 63 | ) 64 | 65 | # Validate GSchema 66 | if glib_compile_schemas.found() 67 | test( 68 | 'validate-gschema', glib_compile_schemas, 69 | args: [ 70 | '--strict', '--dry-run', meson.current_build_dir() 71 | ] 72 | ) 73 | endif 74 | 75 | # Resources 76 | resources = gnome.compile_resources( 77 | 'resources', 78 | 'resources.gresource.xml', 79 | gresource_bundle: true, 80 | source_dir: meson.current_build_dir(), 81 | install: true, 82 | install_dir: pkgdatadir, 83 | ) 84 | -------------------------------------------------------------------------------- /data/resources/ui/vaults_page_row.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 55 | 56 | -------------------------------------------------------------------------------- /data/resources/ui/vaults_page_row_password_prompt_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 51 | 52 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // main.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | mod application; 21 | #[rustfmt::skip] 22 | mod config; 23 | mod global_config_manager; 24 | mod legacy; 25 | mod user_config_manager; 26 | mod util; 27 | mod vault; 28 | 29 | mod backend; 30 | mod ui; 31 | 32 | #[macro_use] 33 | extern crate quick_error; 34 | extern crate ini; 35 | extern crate proc_mounts; 36 | extern crate serde; 37 | extern crate toml; 38 | extern crate uuid; 39 | 40 | use application::VApplication; 41 | use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE}; 42 | use gettextrs::*; 43 | use global_config_manager::GlobalConfigManager; 44 | use gtk::gio; 45 | use user_config_manager::UserConfigManager; 46 | 47 | fn main() { 48 | pretty_env_logger::init(); 49 | 50 | setlocale(LocaleCategory::LcAll, ""); 51 | 52 | if let Err(e) = bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR) { 53 | log::error!("Could not bind text domain: {}", e); 54 | } 55 | 56 | if let Err(e) = textdomain(GETTEXT_PACKAGE) { 57 | log::error!("Could not set text domain: {}", e); 58 | } 59 | 60 | GlobalConfigManager::instance().read_config(); 61 | 62 | if legacy::global_config::needs_conversion() { 63 | legacy::global_config::convert(); 64 | } 65 | 66 | UserConfigManager::instance().read_config(); 67 | 68 | gtk::glib::set_application_name("Vaults"); 69 | gtk::glib::set_prgname(Some("vaults")); 70 | 71 | gtk::init().expect("Unable to start GTK4"); 72 | 73 | let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file"); 74 | gio::resources_register(&res); 75 | 76 | let app = VApplication::new(); 77 | app.run(); 78 | } 79 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('vaults', 2 | 'rust', 3 | version: '0.12.0', 4 | license: 'GPL-3.0-or-later', 5 | meson_version: '>= 0.50') 6 | 7 | i18n = import('i18n') 8 | gnome = import('gnome') 9 | 10 | base_id = 'io.github.mpobaschnig.Vaults' 11 | 12 | dependency('glib-2.0', version: '>= 2.66') 13 | dependency('gio-2.0', version: '>= 2.66') 14 | dependency('gtk4', version: '>= 4.12.0') 15 | dependency('libadwaita-1', version: '>= 1.4.0') 16 | 17 | glib_compile_resources = find_program('glib-compile-resources', required: true) 18 | glib_compile_schemas = find_program('glib-compile-schemas', required: true) 19 | desktop_file_validate = find_program('desktop-file-validate', required: false) 20 | appstreamcli = find_program('appstreamcli', required: false) 21 | cargo = find_program('cargo', required: true) 22 | cargo_script = find_program('build-aux/cargo.sh') 23 | 24 | version = meson.project_version() 25 | version_array = version.split('.') 26 | major_version = version_array[0].to_int() 27 | minor_version = version_array[1].to_int() 28 | version_micro = version_array[2].to_int() 29 | 30 | prefix = get_option('prefix') 31 | bindir = prefix / get_option('bindir') 32 | localedir = prefix / get_option('localedir') 33 | 34 | datadir = prefix / get_option('datadir') 35 | pkgdatadir = datadir / meson.project_name() 36 | iconsdir = datadir / 'icons' 37 | podir = meson.source_root() / 'po' 38 | gettext_package = meson.project_name() 39 | 40 | if get_option('profile') == 'development' 41 | profile = 'Devel' 42 | vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip() 43 | if vcs_tag == '' 44 | version_suffix = '-devel' 45 | else 46 | version_suffix = '-@0@'.format(vcs_tag) 47 | endif 48 | application_id = '@0@.@1@'.format(base_id, profile) 49 | else 50 | profile = '' 51 | version_suffix = '' 52 | application_id = base_id 53 | endif 54 | 55 | meson.add_dist_script( 56 | 'build-aux/dist-vendor.sh', 57 | meson.build_root() / 'meson-dist' / meson.project_name() + '-' + version, 58 | meson.source_root() 59 | ) 60 | 61 | if get_option('profile') == 'development' 62 | # Setup pre-commit hook for ensuring coding style is always consistent 63 | message('Setting up git pre-commit hook..') 64 | run_command('cp', '-f', 'build-aux/pre-commit.hook', '.git/hooks/pre-commit') 65 | endif 66 | 67 | subdir('data') 68 | subdir('po') 69 | subdir('src') 70 | 71 | meson.add_install_script('build-aux/meson_post_install.py') 72 | -------------------------------------------------------------------------------- /data/io.github.mpobaschnig.Vaults.gschema.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -1 6 | Default window width 7 | Default window width 8 | 9 | 10 | -1 11 | Default window height 12 | Default window height 13 | 14 | 15 | false 16 | Default window maximized behaviour 17 | 18 | 19 | 20 | false 21 | Status of legacy conversion 22 | 23 | 24 | "" 25 | The encrypted data directory path 26 | The path to the encrypted data directory 27 | 28 | 29 | "" 30 | The path to the custom cryfs binary 31 | The path to the custom cryfs binary 32 | 33 | 34 | 35 | false 36 | Use custom binary 37 | Wether to use the provided custom binary to execute CryFS 38 | 39 | 40 | "" 41 | The path to the custom cryfs binary 42 | The path to the custom cryfs binary 43 | 44 | 45 | false 46 | Use custom binary 47 | Wether to use the provided custom binary to execute gocryptfs 48 | 49 | 50 | "" 51 | The path to the custom gocryptfs binary 52 | The path to the custom gocryptfs binary 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /data/resources/ui/shortcuts.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | 6 | 7 | shortcuts 8 | 10 9 | 10 | 11 | General 12 | 13 | 14 | Show Preferences 15 | app.preferences 16 | 17 | 18 | 19 | 20 | Show Shortcuts 21 | win.show-help-overlay 22 | 23 | 24 | 25 | 26 | Quit 27 | app.quit 28 | 29 | 30 | 31 | 32 | 33 | 34 | Vaults 35 | 36 | 37 | Add New Vault 38 | win.add_new_vault 39 | 40 | 41 | 42 | 43 | Import Vault 44 | win.import_vault 45 | 46 | 47 | 48 | 49 | Search 50 | win.search 51 | 52 | 53 | 54 | 55 | Refresh 56 | win.refresh 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/legacy/user_config.rs: -------------------------------------------------------------------------------- 1 | // user_config.rs 2 | // 3 | // Copyright 2025 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::{backend::Backend, util, vault::VaultConfig}; 21 | use gtk::glib::user_config_dir; 22 | use serde::{Deserialize, Serialize}; 23 | use std::collections::HashMap; 24 | use toml::de::Error; 25 | use uuid::Uuid; 26 | 27 | #[derive(Debug, Deserialize, Clone, Serialize)] 28 | pub struct LegacyVaultConfig { 29 | pub backend: Backend, 30 | pub encrypted_data_directory: String, 31 | pub mount_directory: String, 32 | pub session_lock: Option, 33 | pub use_custom_binary: Option, 34 | pub custom_binary_path: Option, 35 | } 36 | 37 | pub fn get_user_config_from_legacy_config() -> HashMap { 38 | log::trace!("convert_legacy_user_config_to_new()"); 39 | 40 | let mut new_user_config: HashMap = HashMap::new(); 41 | let legacy_user_config = read_legacy_user_config(); 42 | if let Some(legacy_user_config) = legacy_user_config { 43 | for (name, vault_config) in legacy_user_config { 44 | let new_vault_config = VaultConfig { 45 | name, 46 | backend: vault_config.backend, 47 | encrypted_data_directory: vault_config.encrypted_data_directory, 48 | mount_directory: vault_config.mount_directory, 49 | session_lock: vault_config.session_lock.unwrap_or(false), 50 | }; 51 | new_user_config.insert(util::generate_uuid(), new_vault_config); 52 | } 53 | } 54 | 55 | new_user_config 56 | } 57 | 58 | pub fn read_legacy_user_config() -> Option> { 59 | log::trace!("read_legacy_user_config()"); 60 | 61 | let legacy_user_config_path = get_legacy_user_config_path(); 62 | 63 | if let Some(legacy_user_config_path) = legacy_user_config_path { 64 | let contents = std::fs::read_to_string(&legacy_user_config_path); 65 | match contents { 66 | Ok(content) => { 67 | let res: Result, Error> = 68 | toml::from_str(&content.clone()); 69 | match res { 70 | Ok(v) => { 71 | return Some(v); 72 | } 73 | Err(e) => { 74 | log::error!("Failed to parse user data config: {}", e); 75 | } 76 | } 77 | } 78 | Err(e) => { 79 | log::warn!("Failed to read user data config: {}", e); 80 | } 81 | } 82 | } 83 | 84 | None 85 | } 86 | 87 | fn get_legacy_user_config_path() -> Option { 88 | log::trace!("get_legacy_user_config_path()"); 89 | 90 | match user_config_dir().as_os_str().to_str() { 91 | Some(user_config_directory) => { 92 | log::info!("Got user config dir: {}", user_config_directory); 93 | Some(user_config_directory.to_owned() + "/user_config.toml") 94 | } 95 | None => { 96 | log::error!("Could not get user data directory"); 97 | None 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/legacy/global_config.rs: -------------------------------------------------------------------------------- 1 | // global_config.rs 2 | // 3 | // Copyright 2025 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::{config::APP_ID, global_config_manager}; 21 | 22 | use gtk::gio::{Settings, prelude::SettingsExt}; 23 | 24 | pub fn needs_conversion() -> bool { 25 | if let Some((major, minor, patch)) = get_sem_version(APP_ID) { 26 | if major > 0 || minor >= 11 { 27 | log::debug!( 28 | "Version is {}.{}.{}, no conversion needed", 29 | major, 30 | minor, 31 | patch 32 | ); 33 | 34 | return false; 35 | } 36 | } 37 | 38 | true 39 | } 40 | 41 | pub fn convert() { 42 | let settings = Settings::new(APP_ID); 43 | 44 | if settings.boolean("legacy-converted") { 45 | log::debug!("Already converted, skipping"); 46 | return; 47 | } 48 | 49 | let global_config = global_config_manager::GlobalConfigManager::instance().get_global_config(); 50 | 51 | if let Some(encrypted_data_directory) = global_config.encrypted_data_directory.borrow().clone() 52 | { 53 | let encrypted_data_directory_settings = settings.string("encrypted-data-directory"); 54 | 55 | if encrypted_data_directory_settings.is_empty() { 56 | let ret = settings.set_string("encrypted-data-directory", &encrypted_data_directory); 57 | if ret.is_ok() { 58 | log::debug!( 59 | "Encrypted data directory set to {}", 60 | encrypted_data_directory 61 | ); 62 | } else { 63 | log::error!("Failed to set encrypted data directory in settings"); 64 | } 65 | } else { 66 | log::debug!( 67 | "Encrypted data directory already set to {}, not converting", 68 | encrypted_data_directory_settings 69 | ); 70 | } 71 | }; 72 | 73 | if let Some(mount_directory) = global_config.mount_directory.borrow().clone() { 74 | let mount_directory_settings = settings.string("mount-directory"); 75 | 76 | if mount_directory_settings.is_empty() { 77 | let ret = settings.set_string("mount-directory", &mount_directory); 78 | if ret.is_err() { 79 | log::error!("Failed to set mount directory in settings"); 80 | } else { 81 | log::debug!("Mount directory set to {}", mount_directory); 82 | } 83 | } else { 84 | log::debug!( 85 | "Mount directory already set to {}, not converting", 86 | mount_directory_settings 87 | ); 88 | } 89 | }; 90 | 91 | let ret = settings.set_boolean("legacy-converted", true); 92 | log::debug!( 93 | "Converted to new settings format successfully: {}", 94 | ret.is_ok() 95 | ); 96 | } 97 | 98 | fn get_sem_version(current_version: &str) -> Option<(u32, u32, u32)> { 99 | let mut version = Vec::new(); 100 | 101 | if current_version.contains("-") { 102 | let parts: Vec<&str> = current_version.split("-").collect(); 103 | if let Some(first) = parts.first() { 104 | version.push(first.to_string()); 105 | } else { 106 | return None; 107 | } 108 | } else { 109 | version.push(current_version.to_owned()); 110 | } 111 | if version.len() != 1 { 112 | return None; 113 | } 114 | 115 | let versions: Vec<&str> = version[0].split(".").collect(); 116 | if versions.len() != 3 { 117 | return None; 118 | } 119 | 120 | let major = versions[0].parse::().unwrap(); 121 | let minor = versions[1].parse::().unwrap(); 122 | let patch = versions[2].parse::().unwrap(); 123 | 124 | Some((major, minor, patch)) 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | 131 | #[test] 132 | fn test_correct_full() { 133 | assert_eq!(get_sem_version("0.0.0"), Some((0, 0, 0))); 134 | assert_eq!(get_sem_version("0.9.0"), Some((0, 9, 0))); 135 | assert_eq!(get_sem_version("0.10.0"), Some((0, 10, 0))); 136 | assert_eq!(get_sem_version("0.11.0"), Some((0, 11, 0))); 137 | } 138 | 139 | #[test] 140 | fn test_correct_full_dev() { 141 | assert_eq!(get_sem_version("0.7.0-920ce7a"), Some((0, 7, 0))); 142 | assert_eq!(get_sem_version("0.0.0-abcdef"), Some((0, 0, 0))); 143 | } 144 | 145 | #[test] 146 | fn test_incorrect() { 147 | assert_eq!(get_sem_version(""), None); 148 | assert_eq!(get_sem_version("0"), None); 149 | assert_eq!(get_sem_version("0.10"), None); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/ui/pages/vaults_page_row_password_prompt_window.rs: -------------------------------------------------------------------------------- 1 | // vaults_page_row_settings_window.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use adw::{ 21 | prelude::{AdwDialogExt, EntryRowExt}, 22 | subclass::prelude::*, 23 | }; 24 | use gtk::{self, CompositeTemplate, gio, glib, glib::clone, prelude::*}; 25 | 26 | mod imp { 27 | use gtk::glib::subclass::Signal; 28 | use once_cell::sync::Lazy; 29 | 30 | use super::*; 31 | 32 | #[derive(Debug, CompositeTemplate)] 33 | #[template( 34 | resource = "/io/github/mpobaschnig/Vaults/vaults_page_row_password_prompt_window.ui" 35 | )] 36 | pub struct VaultsPageRowPasswordPromptWindow { 37 | #[template_child] 38 | pub unlock_button: TemplateChild, 39 | #[template_child] 40 | pub password_entry_row: TemplateChild, 41 | #[template_child] 42 | pub status_page: TemplateChild, 43 | } 44 | 45 | #[glib::object_subclass] 46 | impl ObjectSubclass for VaultsPageRowPasswordPromptWindow { 47 | const NAME: &'static str = "VaultsPageRowPasswordPromptWindow"; 48 | type ParentType = adw::Dialog; 49 | type Type = super::VaultsPageRowPasswordPromptWindow; 50 | 51 | fn new() -> Self { 52 | Self { 53 | unlock_button: TemplateChild::default(), 54 | password_entry_row: TemplateChild::default(), 55 | status_page: TemplateChild::default(), 56 | } 57 | } 58 | 59 | fn class_init(klass: &mut Self::Class) { 60 | Self::bind_template(klass); 61 | } 62 | 63 | fn instance_init(obj: &glib::subclass::InitializingObject) { 64 | obj.init_template(); 65 | } 66 | } 67 | 68 | impl ObjectImpl for VaultsPageRowPasswordPromptWindow { 69 | fn constructed(&self) { 70 | let obj = self.obj(); 71 | self.parent_constructed(); 72 | 73 | obj.setup_signals(); 74 | } 75 | 76 | fn signals() -> &'static [Signal] { 77 | static SIGNALS: Lazy> = 78 | Lazy::new(|| vec![Signal::builder("unlock").build()]); 79 | SIGNALS.as_ref() 80 | } 81 | } 82 | 83 | impl WidgetImpl for VaultsPageRowPasswordPromptWindow {} 84 | impl WindowImpl for VaultsPageRowPasswordPromptWindow {} 85 | impl AdwDialogImpl for VaultsPageRowPasswordPromptWindow {} 86 | } 87 | 88 | glib::wrapper! { 89 | pub struct VaultsPageRowPasswordPromptWindow(ObjectSubclass) 90 | @extends gtk::Widget, adw::Dialog, adw::Window, gtk::Window, 91 | @implements gio::ActionMap, gio::ActionGroup, gtk::Accessible, gtk::Native, gtk::Root, gtk::ShortcutManager, gtk::Buildable, gtk::ConstraintTarget; 92 | } 93 | 94 | impl Default for VaultsPageRowPasswordPromptWindow { 95 | fn default() -> Self { 96 | Self::new() 97 | } 98 | } 99 | 100 | impl VaultsPageRowPasswordPromptWindow { 101 | pub fn new() -> Self { 102 | let dialog: Self = glib::Object::builder().build(); 103 | 104 | dialog.add_css_class("flat"); 105 | 106 | dialog 107 | } 108 | 109 | fn setup_signals(&self) { 110 | self.imp().unlock_button.connect_clicked(clone!( 111 | #[weak(rename_to = obj)] 112 | self, 113 | move |_| { 114 | obj.unlock_button_clicked(); 115 | } 116 | )); 117 | 118 | self.imp().password_entry_row.connect_text_notify(clone!( 119 | #[weak(rename_to = obj)] 120 | self, 121 | move |_| { 122 | obj.check_unlock_button_enable_conditions(); 123 | } 124 | )); 125 | 126 | self.imp() 127 | .password_entry_row 128 | .connect_entry_activated(clone!( 129 | #[weak(rename_to = obj)] 130 | self, 131 | move |_| { 132 | obj.connect_activate(); 133 | } 134 | )); 135 | } 136 | 137 | pub fn set_name(&self, name: &str) { 138 | self.imp().status_page.set_title(name); 139 | } 140 | 141 | fn unlock_button_clicked(&self) { 142 | self.emit_by_name::<()>("unlock", &[]); 143 | AdwDialogExt::close(self); 144 | } 145 | 146 | fn check_unlock_button_enable_conditions(&self) { 147 | let vault_name = self.imp().password_entry_row.text(); 148 | 149 | if !vault_name.is_empty() { 150 | self.imp().unlock_button.set_sensitive(true); 151 | } else { 152 | self.imp().unlock_button.set_sensitive(false); 153 | } 154 | } 155 | 156 | fn connect_activate(&self) { 157 | if !self.imp().password_entry_row.text().is_empty() { 158 | self.unlock_button_clicked(); 159 | } 160 | } 161 | 162 | pub fn get_password(&self) -> String { 163 | self.imp().password_entry_row.text().to_string() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /data/io.github.mpobaschnig.Vaults.metainfo.xml.in.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | @app-id@ 4 | CC0 5 | GPL-3.0+ 6 | Vaults 7 | Keep important files safe 8 | 9 |

Vaults lets you create encrypted vaults in which you can safely store files. It uses gocryptfs and CryFS for encryption.

10 |
11 | 12 | #ffbe6f 13 | #62462d 14 | 15 | 16 | 17 | https://raw.githubusercontent.com/mpobaschnig/Vaults/master/data/resources/screenshots/main.png 18 | Main window showing different vaults 19 | 20 | 21 | https://github.com/mpobaschnig/vaults 22 | https://github.com/mpobaschnig/Vaults/issues 23 | https://github.com/mpobaschnig/vaults 24 | https://github.com/mpobaschnig/vaults/tree/main/po 25 | https://github.com/sponsors/mpobaschnig/ 26 | 27 | 28 | ModernToolkit 29 | HiDpiIcon 30 | 31 | Martin Pobaschnig 32 | 33 | Martin Pobaschnig 34 | 35 | mpobaschnig@protonmail.com 36 | @gettext-package@ 37 | @app-id@.desktop 38 | 39 | 40 | 41 |

Welcome to the 0.11.0 release of Vaults! This update includes:

42 |
    43 |
  • Update dependencies, including GNOME runtime to 49 and gocryptfs to 2.6.1
  • 44 |
  • Various UI updates including selection for removing multiple vaults
  • 45 |
  • Fix for not crashing when we're not in a Flatpak due to missing .flatpak-info file
  • 46 |
47 |
48 |
49 | 50 | 51 |

Welcome to the 0.10.0 release of Vaults! This update includes:

52 |
    53 |
  • Update GNOME runtime to 48
  • 54 |
  • Update gocryptfs to 2.5.2
  • 55 |
  • Improved logging in case of errors
  • 56 |
  • Fixed a bug where a CryFS vault name couldn't be reused
  • 57 |
  • Fixed a regression where pressing enter or escape in the password dialog wouldn't work
  • 58 |
59 |
60 |
61 | 62 | 63 |

Welcome to the 0.9.1 release of Vaults! This update includes a small bug fix to make gocryptfs work again.

64 |
65 |
66 | 67 | 68 |

Welcome to the 0.9.0 release of Vaults! This update includes:

69 |
    70 |
  • Update dependencies and runtime to GNOME 47
  • 71 |
  • Bundle gocryptfs and CryFS dependency within the Flatpak
  • 72 |
  • Update to new libadwaita widgets
  • 73 |
  • Bug fixes and general improvements
  • 74 |
75 |
76 |
77 | 78 | 79 |

Small update:

80 |
    81 |
  • Update dependencies and GNOME 46 runtime
  • 82 |
  • Add option to delete encrypted data when removing Vault
  • 83 |
  • Add following translations: tr, zh_CN
  • 84 |
85 |
86 |
87 | 88 | 89 |

Small update:

90 |
    91 |
  • Redesigned using new libadwaita widgets
  • 92 |
  • Add option to lock vault on screen saver activation
  • 93 |
  • Add open/close CLI parameters
  • 94 |
95 |
96 |
97 | 98 | 99 |

Bug fix update:

100 |
    101 |
  • Updated release notes and version
  • 102 |
  • Updated correct screenshots
  • 103 |
104 |
105 |
106 | 107 | 108 |

Small release:

109 |
    110 |
  • Added French translation
  • 111 |
  • Updated to GNOME 44 runtime
  • 112 |
113 |
114 |
115 | 116 | 117 |

Translation release:

118 |
    119 |
  • Added Dutch translation
  • 120 |
121 |
122 |
123 | 124 | 125 |

Translation release:

126 |
    127 |
  • Added German translation
  • 128 |
129 |
130 |
131 | 132 | 133 |

This is a smaller release and includes:

134 |
    135 |
  • Support for manually mounting/unmounting Vaults in hidden paths
  • 136 |
  • Updated libadwaita about window
  • 137 |
  • Updated dependencies, GNOME 43 runtime
  • 138 |
139 |
140 |
141 | 142 | 143 |

This release adds search functionality and toasts, and updates the libadwaita theme.

144 |
145 |
146 | 147 | 148 |

Bug fix release:

149 |
    150 |
  • Fix importing of vaults where gocryptfs and CryFS is not installed
  • 151 |
152 |
153 |
154 | 155 | 156 |

This release introduces various changes, in particular:

157 |
    158 |
  • New application icon
  • 159 |
  • New responsive design and style based on libadwaita
  • 160 |
  • Preferences to change default directories
  • 161 |
  • Automatically adapt the state of the vault row if it gets opened or closed outside the app
  • 162 |
  • Add shortcuts for more keyboard friendly interaction
  • 163 |
  • Various bug fixes
  • 164 |
165 |
166 |
167 | 168 | 169 |

Initial release

170 |
171 |
172 |
173 | 174 | 360 175 | 176 |
177 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | // mod.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | pub mod cryfs; 21 | pub mod gocryptfs; 22 | 23 | use crate::{config::APP_ID, vault::VaultConfig}; 24 | use gettextrs::gettext; 25 | use gtk::{gio::Settings, glib}; 26 | use serde::{Deserialize, Serialize}; 27 | use std::string::String; 28 | use strum_macros::EnumIter; 29 | 30 | quick_error! { 31 | #[derive(Debug)] 32 | pub enum BackendError { 33 | ToUser(e: String) { 34 | display("{}", e) 35 | } 36 | Generic { 37 | from(std::io::Error) 38 | } 39 | } 40 | } 41 | 42 | #[derive( 43 | Debug, 44 | EnumIter, 45 | strum_macros::Display, 46 | Serialize, 47 | Deserialize, 48 | strum_macros::EnumString, 49 | Copy, 50 | Clone, 51 | glib::Enum, 52 | Default, 53 | )] 54 | #[enum_type(name = "Backend")] 55 | pub enum Backend { 56 | #[default] 57 | #[enum_value(name = "cryfs")] 58 | Cryfs, 59 | #[enum_value(name = "gocryptfs")] 60 | Gocryptfs, 61 | } 62 | 63 | impl Backend { 64 | pub fn is_available(&self, vault_config: &VaultConfig) -> Result { 65 | log::trace!("is_available({:?}, {:?})", self, vault_config); 66 | 67 | let settings = Settings::new(APP_ID); 68 | 69 | match &self { 70 | Backend::Cryfs => cryfs::is_available(&settings, vault_config), 71 | Backend::Gocryptfs => gocryptfs::is_available(&settings, vault_config), 72 | } 73 | } 74 | 75 | pub fn init(vault_config: &VaultConfig, password: String) -> Result<(), BackendError> { 76 | log::trace!("init({:?}, password: )", vault_config); 77 | 78 | let settings = Settings::new(APP_ID); 79 | let encrypted_data_directory = &vault_config.encrypted_data_directory; 80 | let mount_directory = &vault_config.mount_directory; 81 | 82 | match create_edd_if_not_exists(encrypted_data_directory) { 83 | Ok(_) => {} 84 | Err(e) => { 85 | return Err(e); 86 | } 87 | } 88 | 89 | match create_md_if_not_exists(mount_directory) { 90 | Ok(_) => {} 91 | Err(e) => { 92 | return Err(e); 93 | } 94 | } 95 | 96 | match vault_config.backend { 97 | Backend::Cryfs => cryfs::init(&settings, vault_config, password), 98 | Backend::Gocryptfs => gocryptfs::init(&settings, vault_config, password), 99 | } 100 | } 101 | 102 | pub fn open(vault_config: &VaultConfig, password: String) -> Result<(), BackendError> { 103 | log::trace!("open({:?}, password: )", vault_config); 104 | 105 | let settings = Settings::new(APP_ID); 106 | 107 | match vault_config.backend { 108 | Backend::Cryfs => cryfs::open(&settings, vault_config, password), 109 | Backend::Gocryptfs => gocryptfs::open(&settings, vault_config, password), 110 | } 111 | } 112 | 113 | pub fn close(vault_config: &VaultConfig) -> Result<(), BackendError> { 114 | log::trace!("close({:?})", vault_config); 115 | 116 | let settings = Settings::new(APP_ID); 117 | 118 | match vault_config.backend { 119 | Backend::Cryfs => cryfs::close(&settings, vault_config), 120 | Backend::Gocryptfs => gocryptfs::close(&settings, vault_config), 121 | } 122 | } 123 | } 124 | 125 | pub fn get_ui_string_from_backend(backend: &Backend) -> String { 126 | log::trace!("get_ui_string_from_backend({:?})", backend); 127 | 128 | match backend { 129 | Backend::Cryfs => String::from("CryFS"), 130 | Backend::Gocryptfs => String::from("gocryptfs"), 131 | } 132 | } 133 | 134 | pub fn get_backend_from_ui_string(backend: &String) -> Option { 135 | log::trace!("get_backend_from_ui_string({:?})", backend); 136 | 137 | if backend == "CryFS" { 138 | Some(Backend::Cryfs) 139 | } else if backend == "gocryptfs" { 140 | Some(Backend::Gocryptfs) 141 | } else { 142 | None 143 | } 144 | } 145 | 146 | fn create_edd_if_not_exists(encrypted_data_directory: &String) -> Result<(), BackendError> { 147 | log::trace!("create_edd_if_not_exists({:?}", encrypted_data_directory); 148 | 149 | match std::fs::create_dir_all(encrypted_data_directory) { 150 | Ok(_) => Ok(()), 151 | Err(e) => { 152 | log::error!("Failed to create encrypted data directory: {}", e); 153 | 154 | match e.kind() { 155 | std::io::ErrorKind::PermissionDenied => Err(BackendError::ToUser(gettext( 156 | "Failed to create encrypted data directory: Permission denied.", 157 | ))), 158 | std::io::ErrorKind::ConnectionRefused => Err(BackendError::ToUser(gettext( 159 | "Failed to create encrypted data directory: Connection refused.", 160 | ))), 161 | std::io::ErrorKind::ConnectionReset => Err(BackendError::ToUser(gettext( 162 | "Failed to create encrypted data directory: Connection reset.", 163 | ))), 164 | std::io::ErrorKind::ConnectionAborted => Err(BackendError::ToUser(gettext( 165 | "Failed to create encrypted data directory: Connection aborted.", 166 | ))), 167 | std::io::ErrorKind::NotConnected => Err(BackendError::ToUser(gettext( 168 | "Failed to create encrypted data directory: Not connected.", 169 | ))), 170 | _ => Err(BackendError::ToUser(gettext( 171 | "Failed to create encrypted data directory.", 172 | ))), 173 | } 174 | } 175 | } 176 | } 177 | 178 | fn create_md_if_not_exists(mount_directory: &String) -> Result<(), BackendError> { 179 | log::trace!("create_md_if_not_exists({:?}", mount_directory); 180 | 181 | match std::fs::create_dir_all(mount_directory) { 182 | Ok(_) => Ok(()), 183 | Err(e) => { 184 | log::error!("Failed to create encrypted data directory: {}", e); 185 | 186 | match e.kind() { 187 | std::io::ErrorKind::PermissionDenied => Err(BackendError::ToUser(gettext( 188 | "Failed to create mount directory: Permission denied.", 189 | ))), 190 | std::io::ErrorKind::ConnectionRefused => Err(BackendError::ToUser(gettext( 191 | "Failed to create mount directory: Connection refused.", 192 | ))), 193 | std::io::ErrorKind::ConnectionReset => Err(BackendError::ToUser(gettext( 194 | "Failed to create mount directory: Connection reset.", 195 | ))), 196 | std::io::ErrorKind::ConnectionAborted => Err(BackendError::ToUser(gettext( 197 | "Failed to create mount directory: Connection aborted.", 198 | ))), 199 | std::io::ErrorKind::NotConnected => Err(BackendError::ToUser(gettext( 200 | "Failed to create mount directory: Not connected.", 201 | ))), 202 | _ => Err(BackendError::ToUser(gettext( 203 | "Failed to create mount directory.", 204 | ))), 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /data/resources/ui/vaults_page_row_settings_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 153 | 154 | -------------------------------------------------------------------------------- /io.github.mpobaschnig.Vaults.Devel.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "io.github.mpobaschnig.Vaults.Devel", 3 | "runtime": "org.gnome.Platform", 4 | "runtime-version": "49", 5 | "sdk": "org.gnome.Sdk", 6 | "sdk-extensions": [ 7 | "org.freedesktop.Sdk.Extension.rust-stable", 8 | "org.freedesktop.Sdk.Extension.llvm21" 9 | ], 10 | "command": "vaults", 11 | "finish-args": [ 12 | "--socket=fallback-x11", 13 | "--socket=wayland", 14 | "--share=ipc", 15 | "--device=dri", 16 | "--talk-name=org.freedesktop.Flatpak", 17 | "--filesystem=host", 18 | "--env=RUST_LOG=vaults=trace", 19 | "--env=G_MESSAGES_DEBUG=none", 20 | "--filesystem=xdg-run/gvfsd", 21 | "--talk-name=org.gtk.vfs.*" 22 | ], 23 | "cleanup": [ 24 | "/include", 25 | "/lib/pkgconfig" 26 | ], 27 | "build-options": { 28 | "append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm21/bin", 29 | "prepend-ld-library-path": "/usr/lib/sdk/llvm21/lib", 30 | "env": { 31 | "RUSTFLAGS": "-C force-frame-pointers=yes -C symbol-mangling-version=v0 -C linker=clang -C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold" 32 | }, 33 | "build-args": [ 34 | "--share=network" 35 | ] 36 | }, 37 | "modules": [ 38 | { 39 | "name": "libfuse", 40 | "buildsystem": "autotools", 41 | "cleanup": [ 42 | "/include", 43 | "/lib/pkgconfig", 44 | "*.a", 45 | "*.la", 46 | "/lib/libulockmgr*" 47 | ], 48 | "config-opts": [ 49 | "--disable-util" 50 | ], 51 | "sources": [ 52 | { 53 | "type": "archive", 54 | "url": "https://github.com/libfuse/libfuse/releases/download/fuse-2.9.9/fuse-2.9.9.tar.gz", 55 | "sha256": "d0e69d5d608cc22ff4843791ad097f554dd32540ddc9bed7638cc6fea7c1b4b5" 56 | }, 57 | { 58 | "type": "patch", 59 | "paths": [ 60 | "build-aux/fuse-disable-sys-mount-under-flatpak.patch", 61 | "build-aux/fuse-2.9.2-namespace-conflict-fix.patch", 62 | "build-aux/fuse-closefrom.patch" 63 | ] 64 | } 65 | ] 66 | }, 67 | { 68 | "name": "gocryptfs", 69 | "buildsystem": "simple", 70 | "build-commands": [ 71 | "mkdir -p /app/bin/", 72 | "ln -s `pwd`/bin/go /app/bin/go", 73 | "`pwd`/build-without-openssl.bash", 74 | "make install", 75 | "rm -rf /app/bin/go" 76 | ], 77 | "cleanup": [], 78 | "config-opts": [], 79 | "sources": [ 80 | { 81 | "type": "archive", 82 | "url": "https://github.com/rfjakob/gocryptfs/releases/download/v2.6.1/gocryptfs_v2.6.1_src-deps.tar.gz", 83 | "sha256": "9a966c1340a1a1d92073091643687b1205c46b57017c5da2bf7e97e3f5729a5a" 84 | }, 85 | { 86 | "type": "patch", 87 | "paths": [ 88 | "build-aux/gocryptfs.patch", 89 | "build-aux/gocryptfs_version.patch" 90 | ] 91 | }, 92 | { 93 | "type": "archive", 94 | "only-arches": [ 95 | "x86_64" 96 | ], 97 | "url": "https://go.dev/dl/go1.23.2.linux-amd64.tar.gz", 98 | "sha256": "542d3c1705f1c6a1c5a80d5dc62e2e45171af291e755d591c5e6531ef63b454e" 99 | }, 100 | { 101 | "type": "archive", 102 | "only-arches": [ 103 | "aarch64" 104 | ], 105 | "url": "https://go.dev/dl/go1.23.2.linux-arm64.tar.gz", 106 | "sha256": "f626cdd92fc21a88b31c1251f419c17782933a42903db87a174ce74eeecc66a9" 107 | } 108 | ] 109 | }, 110 | { 111 | "name": "range-v3", 112 | "buildsystem": "cmake-ninja", 113 | "config-opts": [ 114 | "-DCMAKE_C_COMPILER=clang", 115 | "-DCMAKE_CXX_COMPILER=clang++", 116 | "-DCMAKE_BUILD_TYPE=Release", 117 | "-DRANGES_HAS_WERROR=0" 118 | ], 119 | "cleanup": [ 120 | "*" 121 | ], 122 | "sources": [ 123 | { 124 | "type": "git", 125 | "url": "https://github.com/ericniebler/range-v3", 126 | "commit": "7e6f34b1e820fb8321346888ef0558a0ec842b8e" 127 | } 128 | ] 129 | }, 130 | { 131 | "name": "spdlog", 132 | "buildsystem": "cmake-ninja", 133 | "config-opts": [ 134 | "-DCMAKE_BUILD_TYPE=Release" 135 | ], 136 | "cleanup": [ 137 | "*" 138 | ], 139 | "sources": [ 140 | { 141 | "type": "archive", 142 | "url": "https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz", 143 | "sha256": "1586508029a7d0670dfcb2d97575dcdc242d3868a259742b69f100801ab4e16b" 144 | } 145 | ] 146 | }, 147 | { 148 | "name": "boost", 149 | "buildsystem": "simple", 150 | "build-commands": [ 151 | "`pwd`/bootstrap.sh --prefix=/app", 152 | "`pwd`/b2 install --prefix=/app" 153 | ], 154 | "cleanup": [ 155 | "*" 156 | ], 157 | "sources": [ 158 | { 159 | "type": "archive", 160 | "url": "https://archives.boost.io/release/1.86.0/source/boost_1_86_0.tar.gz", 161 | "sha256": "2575e74ffc3ef1cd0babac2c1ee8bdb5782a0ee672b1912da40e5b4b591ca01f" 162 | } 163 | ] 164 | }, 165 | { 166 | "name": "cryfs", 167 | "buildsystem": "cmake-ninja", 168 | "config-opts": [ 169 | "-DCMAKE_BUILD_TYPE=RELEASE", 170 | "-DCRYFS_UPDATE_CHECKS=FALSE", 171 | "-DDISABLE_OPENMP=ON", 172 | "-DBoost_USE_STATIC_LIBS=ON" 173 | ], 174 | "cleanup": [ 175 | "/share/*" 176 | ], 177 | "sources": [ 178 | { 179 | "type": "archive", 180 | "url": "https://github.com/cryfs/cryfs/archive/refs/tags/1.0.1.tar.gz", 181 | "sha256": "4e13ade27751b977e60321d06e78cccabb7168d72a1d4cde5597656399d61245" 182 | }, 183 | { 184 | "type": "patch", 185 | "paths": [ 186 | "build-aux/cryfs.patch" 187 | ] 188 | } 189 | ] 190 | }, 191 | { 192 | "name": "wrapper", 193 | "buildsystem": "simple", 194 | "build-commands": [ 195 | "install fusermount-wrapper.sh /app/bin/fusermount" 196 | ], 197 | "sources": [ 198 | { 199 | "type": "file", 200 | "path": "build-aux/fusermount-wrapper.sh" 201 | } 202 | ] 203 | }, 204 | { 205 | "name": "umount", 206 | "buildsystem": "simple", 207 | "build-commands": [ 208 | "install umount-wrapper.sh /app/bin/umount" 209 | ], 210 | "sources": [ 211 | { 212 | "type": "file", 213 | "path": "build-aux/umount-wrapper.sh" 214 | } 215 | ] 216 | }, 217 | { 218 | "name": "vaults", 219 | "buildsystem": "meson", 220 | "run-tests": true, 221 | "config-opts": [ 222 | "-Dprofile=development" 223 | ], 224 | "sources": [ 225 | { 226 | "type": "dir", 227 | "path": "." 228 | } 229 | ] 230 | } 231 | ] 232 | } 233 | -------------------------------------------------------------------------------- /src/user_config_manager.rs: -------------------------------------------------------------------------------- 1 | // user_config_manager.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::{legacy, vault::*}; 21 | use gtk::glib::Properties; 22 | use gtk::{ 23 | gio::subclass::prelude::*, 24 | glib::{self, prelude::*, subclass::Signal, user_config_dir}, 25 | }; 26 | use once_cell::sync::Lazy; 27 | use std::{cell::RefCell, collections::HashMap}; 28 | use toml::de::Error; 29 | use uuid::Uuid; 30 | 31 | static mut USER_CONFIG_MANAGER: Option = None; 32 | 33 | mod imp { 34 | use super::*; 35 | 36 | #[derive(Debug, Properties)] 37 | #[properties(wrapper_type = super::UserConfigManager)] 38 | pub struct UserConfigManager { 39 | pub vaults: RefCell>, 40 | pub user_config_directory: RefCell>, 41 | #[property(name = "has-vaults", default = false, get, set)] 42 | pub has_vaults: RefCell, 43 | } 44 | 45 | #[glib::object_subclass] 46 | impl ObjectSubclass for UserConfigManager { 47 | const NAME: &'static str = "UserConfigManager"; 48 | type ParentType = glib::Object; 49 | type Type = super::UserConfigManager; 50 | 51 | fn new() -> Self { 52 | Self { 53 | vaults: RefCell::new(HashMap::new()), 54 | user_config_directory: RefCell::new(None), 55 | has_vaults: RefCell::new(false), 56 | } 57 | } 58 | } 59 | 60 | #[glib::derived_properties] 61 | impl ObjectImpl for UserConfigManager { 62 | fn signals() -> &'static [Signal] { 63 | static SIGNALS: Lazy> = Lazy::new(|| { 64 | vec![ 65 | Signal::builder("refresh") 66 | .param_types([bool::static_type()]) 67 | .build(), 68 | Signal::builder("add-vault").build(), 69 | Signal::builder("remove-vault").build(), 70 | Signal::builder("change-vault").build(), 71 | ] 72 | }); 73 | 74 | SIGNALS.as_ref() 75 | } 76 | } 77 | } 78 | 79 | glib::wrapper! { 80 | pub struct UserConfigManager(ObjectSubclass); 81 | } 82 | 83 | impl UserConfigManager { 84 | pub fn connect_refresh(&self, callback: F) -> glib::SignalHandlerId { 85 | self.connect_local("refresh", false, move |args| { 86 | let map_is_empty = args.get(1).unwrap().get::().unwrap(); 87 | callback(map_is_empty); 88 | None 89 | }) 90 | } 91 | 92 | pub fn connect_add_vault(&self, callback: F) -> glib::SignalHandlerId { 93 | self.connect_local("add-vault", false, move |_| { 94 | callback(); 95 | None 96 | }) 97 | } 98 | 99 | pub fn connect_remove_vault(&self, callback: F) -> glib::SignalHandlerId { 100 | self.connect_local("remove-vault", false, move |_| { 101 | callback(); 102 | None 103 | }) 104 | } 105 | 106 | pub fn connect_change_vault(&self, callback: F) -> glib::SignalHandlerId { 107 | self.connect_local("change-vault", false, move |_| { 108 | callback(); 109 | None 110 | }) 111 | } 112 | 113 | pub fn instance() -> Self { 114 | unsafe { 115 | #[allow(static_mut_refs)] 116 | match USER_CONFIG_MANAGER.as_ref() { 117 | Some(user_config) => user_config.clone(), 118 | None => { 119 | let user_config = UserConfigManager::new(); 120 | USER_CONFIG_MANAGER = Some(user_config.clone()); 121 | user_config 122 | } 123 | } 124 | } 125 | } 126 | 127 | fn new() -> Self { 128 | log::trace!("new()"); 129 | 130 | let object: Self = glib::Object::new(); 131 | 132 | match user_config_dir().as_os_str().to_str() { 133 | Some(user_config_directory) => { 134 | log::info!("Got user config dir: {}", user_config_directory); 135 | 136 | *object.imp().user_config_directory.borrow_mut() = 137 | Some(user_config_directory.to_owned() + "/user_config_v2.toml"); 138 | } 139 | None => { 140 | log::error!("Could not get user data directory"); 141 | } 142 | } 143 | 144 | object 145 | } 146 | 147 | pub fn get_map(&self) -> HashMap { 148 | log::trace!("get_map()"); 149 | 150 | self.imp().vaults.borrow().clone() 151 | } 152 | 153 | pub fn read_config(&self) { 154 | log::trace!("read_config()"); 155 | 156 | if let Some(path) = self.imp().user_config_directory.borrow().as_ref() { 157 | if !std::path::Path::new(path).exists() { 158 | log::info!("User config file does not exist: {}", path); 159 | log::info!("Trying to read legacy user config..."); 160 | let user_config = legacy::user_config::get_user_config_from_legacy_config(); 161 | let map = &mut *self.imp().vaults.borrow_mut(); 162 | map.clear(); 163 | *map = user_config; 164 | return; 165 | } 166 | 167 | let map = &mut *self.imp().vaults.borrow_mut(); 168 | map.clear(); 169 | 170 | let contents = std::fs::read_to_string(path); 171 | match contents { 172 | Ok(content) => { 173 | let res: Result, Error> = 174 | toml::from_str(&content.clone()); 175 | match res { 176 | Ok(v) => { 177 | *map = v; 178 | } 179 | Err(e) => { 180 | log::error!("Failed to parse user data config: {}", e); 181 | } 182 | } 183 | } 184 | Err(e) => { 185 | log::warn!("Failed to read user data config: {}", e); 186 | } 187 | } 188 | 189 | self.set_has_vaults(!map.is_empty()); 190 | } 191 | } 192 | 193 | pub fn write_config(&self, map: &mut HashMap) { 194 | log::trace!("write_config({:?})", &map); 195 | if let Some(path) = self.imp().user_config_directory.borrow().as_ref() { 196 | match toml::to_string_pretty(&map) { 197 | Ok(contents) => match std::fs::write(path, &contents) { 198 | Ok(_) => { 199 | log::debug!("Successfully wrote user config: {}", &contents); 200 | } 201 | Err(e) => { 202 | log::error!("Failed to write user config: {}", e); 203 | } 204 | }, 205 | Err(e) => { 206 | log::error!("Failed to parse config: {}", e); 207 | } 208 | } 209 | } 210 | } 211 | 212 | pub fn add_vault(&self, vault: Vault) { 213 | log::debug!("Add vault: {:?}, {:?}", &vault.name(), &vault.config()); 214 | 215 | #[allow(unused_assignments)] 216 | let mut is_map_empty = false; 217 | { 218 | let map = &mut self.imp().vaults.borrow_mut(); 219 | map.insert(vault.get_uuid(), vault.config()); 220 | self.write_config(map); 221 | is_map_empty = map.is_empty(); 222 | }; 223 | self.set_has_vaults(!is_map_empty); 224 | 225 | self.emit_by_name::<()>("add-vault", &[]); 226 | } 227 | 228 | pub fn remove_vault(self, uuid: Uuid) { 229 | log::trace!("remove_vault({:?})", &uuid); 230 | 231 | #[allow(unused_assignments)] 232 | let mut is_map_empty = false; 233 | { 234 | let map = &mut self.imp().vaults.borrow_mut(); 235 | map.remove(&uuid); 236 | self.write_config(map); 237 | is_map_empty = map.is_empty(); 238 | } 239 | self.set_has_vaults(!is_map_empty); 240 | 241 | self.emit_by_name::<()>("remove-vault", &[]); 242 | self.emit_by_name::<()>("refresh", &[&is_map_empty]); 243 | } 244 | 245 | pub fn change_vault(&self, uuid: Uuid, new_vault_config: VaultConfig) { 246 | log::trace!("change_vault({:?}, {:?})", &uuid, &new_vault_config); 247 | 248 | let map = &mut self.imp().vaults.borrow_mut(); 249 | map.insert(uuid, new_vault_config); 250 | self.write_config(map); 251 | 252 | self.emit_by_name::<()>("change-vault", &[]); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /data/icons/io.github.mpobaschnig.Vaults.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 19 | 21 | 43 | 46 | 47 | 53 | 58 | 59 | 61 | 64 | 69 | 70 | 71 | 73 | 77 | 78 | 81 | 85 | 89 | 93 | 97 | 98 | 106 | 114 | 122 | 130 | 138 | 146 | 150 | 154 | 159 | 162 | 163 | 167 | 171 | 175 | 180 | 185 | 189 | 193 | 198 | 202 | 206 | 211 | 218 | 225 | 232 | 239 | 240 | 245 | 246 | -------------------------------------------------------------------------------- /src/backend/gocryptfs.rs: -------------------------------------------------------------------------------- 1 | // gocryptfs.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use super::BackendError; 21 | use crate::global_config_manager::GlobalConfigManager; 22 | use crate::vault::VaultConfig; 23 | use gettextrs::gettext; 24 | use gtk::gio::Settings; 25 | use gtk::gio::prelude::SettingsExt; 26 | use std::process::Command; 27 | use std::{io::Write, process::Stdio}; 28 | 29 | fn get_binary_path(settings: &Settings, vault_config: &VaultConfig) -> Option { 30 | log::trace!("get_binary_path({:?})", vault_config); 31 | 32 | if settings.boolean("use-custom-gocryptfs-binary") { 33 | return Some(settings.string("custom-gocryptfs-binary-path").to_string()); 34 | } 35 | 36 | GlobalConfigManager::instance().get_gocryptfs_binary_path() 37 | } 38 | 39 | pub fn is_available(settings: &Settings, vault_config: &VaultConfig) -> Result { 40 | log::trace!("is_available({:?})", vault_config); 41 | 42 | let binary_path = get_binary_path(settings, vault_config); 43 | if binary_path.is_none() { 44 | log::error!("gocryptfs binary path is not set"); 45 | return Err(BackendError::ToUser(gettext( 46 | "No gocryptfs binary path set", 47 | ))); 48 | } 49 | 50 | let output = Command::new("flatpak-spawn") 51 | .arg("--host") 52 | .arg(binary_path.unwrap()) 53 | .arg("--version") 54 | .output()?; 55 | log::debug!("gocryptfs output: {:?}", output); 56 | 57 | let success = output.status.success(); 58 | log::info!("gocryptfs is available: {}", success); 59 | Ok(success) 60 | } 61 | 62 | pub fn init( 63 | settings: &Settings, 64 | vault_config: &VaultConfig, 65 | password: String, 66 | ) -> Result<(), BackendError> { 67 | log::trace!("init({:?}, password: )", vault_config); 68 | 69 | let binary_path = get_binary_path(settings, vault_config); 70 | if binary_path.is_none() { 71 | log::error!("gocryptfs binary path is not set"); 72 | return Err(BackendError::ToUser(gettext( 73 | "No gocryptfs binary path set", 74 | ))); 75 | } 76 | 77 | let mut child = Command::new("flatpak-spawn") 78 | .arg("--host") 79 | .arg(binary_path.unwrap()) 80 | .stdin(Stdio::piped()) 81 | .stdout(Stdio::piped()) 82 | .arg("--init") 83 | .arg("-q") 84 | .arg("--") 85 | .arg(&vault_config.encrypted_data_directory) 86 | .spawn()?; 87 | 88 | let mut pw = String::from(&password); 89 | pw.push('\n'); 90 | pw.push_str(&password); 91 | pw.push('\n'); 92 | 93 | child 94 | .stdin 95 | .as_mut() 96 | .ok_or(BackendError::Generic)? 97 | .write_all(pw.as_bytes())?; 98 | 99 | let output = child.wait_with_output()?; 100 | log::debug!("gocryptfs output: {:?}", output); 101 | if output.status.success() { 102 | log::info!("gocryptfs init successful"); 103 | Ok(()) 104 | } else { 105 | std::io::stdout().write_all(&output.stdout).unwrap(); 106 | std::io::stderr().write_all(&output.stderr).unwrap(); 107 | 108 | let err_code = output.status.code(); 109 | log::error!("gocryptfs init failed: {:?}", err_code); 110 | Err(gocryptfs_ret_status_to_err(err_code)) 111 | } 112 | } 113 | 114 | pub fn open( 115 | settings: &Settings, 116 | vault_config: &VaultConfig, 117 | password: String, 118 | ) -> Result<(), BackendError> { 119 | log::trace!("open({:?}, password: )", vault_config); 120 | 121 | let binary_path = get_binary_path(settings, vault_config); 122 | if binary_path.is_none() { 123 | log::error!("gocryptfs binary path is not set"); 124 | return Err(BackendError::ToUser(gettext( 125 | "No gocryptfs binary path set", 126 | ))); 127 | } 128 | 129 | let mut child = Command::new("flatpak-spawn") 130 | .arg("--host") 131 | .arg(binary_path.unwrap()) 132 | .stdin(Stdio::piped()) 133 | .stdout(Stdio::piped()) 134 | .arg("-q") 135 | .arg("--") 136 | .arg(&vault_config.encrypted_data_directory) 137 | .arg(&vault_config.mount_directory) 138 | .spawn()?; 139 | 140 | let mut pw = String::from(&password); 141 | pw.push('\n'); 142 | 143 | child 144 | .stdin 145 | .as_mut() 146 | .ok_or(BackendError::Generic)? 147 | .write_all(pw.as_bytes())?; 148 | 149 | let output = child.wait_with_output()?; 150 | log::debug!("gocryptfs output: {:?}", output); 151 | if output.status.success() { 152 | log::info!("gocryptfs open successful"); 153 | Ok(()) 154 | } else { 155 | std::io::stdout().write_all(&output.stdout).unwrap(); 156 | std::io::stderr().write_all(&output.stderr).unwrap(); 157 | 158 | let err_code = output.status.code(); 159 | log::error!("gocryptfs open failed: {:?}", err_code); 160 | Err(gocryptfs_ret_status_to_err(err_code)) 161 | } 162 | } 163 | 164 | pub fn close(_settings: &Settings, vault_config: &VaultConfig) -> Result<(), BackendError> { 165 | log::trace!("close({:?}, password: )", vault_config); 166 | 167 | let child = Command::new("flatpak-spawn") 168 | .arg("--host") 169 | .arg("umount") 170 | .stdout(Stdio::piped()) 171 | .arg(&vault_config.mount_directory) 172 | .spawn()?; 173 | 174 | let output = child.wait_with_output()?; 175 | log::debug!("umount output: {:?}", output); 176 | if output.status.success() { 177 | log::info!("umount close successful"); 178 | Ok(()) 179 | } else { 180 | std::io::stdout().write_all(&output.stdout).unwrap(); 181 | std::io::stderr().write_all(&output.stderr).unwrap(); 182 | 183 | let err_code = output.status.code(); 184 | log::error!("gocryptfs close failed: {:?}", err_code); 185 | Err(umount_ret_status_to_err(err_code)) 186 | } 187 | } 188 | 189 | fn gocryptfs_ret_status_to_err(status: Option) -> BackendError { 190 | log::trace!("status_to_err({:?})", status); 191 | 192 | struct GocryptfsExitStatus {} 193 | 194 | #[allow(dead_code)] 195 | impl GocryptfsExitStatus { 196 | pub const SUCCESS: i32 = 0; 197 | pub const NON_EMPTY_CIPHER_DIR: i32 = 6; 198 | pub const NON_EMPTY_MOUNT_POINT: i32 = 10; 199 | pub const WRONG_PASSWORD: i32 = 12; 200 | pub const EMPTY_PASSWORD: i32 = 22; 201 | pub const CANNOT_READ_CONFIG: i32 = 23; 202 | pub const CANNOT_WRITE_CONFIG: i32 = 24; 203 | pub const FSCK_ERROR: i32 = 26; 204 | } 205 | 206 | match status { 207 | Some(status) => match status { 208 | GocryptfsExitStatus::NON_EMPTY_CIPHER_DIR => { 209 | BackendError::ToUser(gettext("The encrypted data directory is not empty.")) 210 | } 211 | GocryptfsExitStatus::NON_EMPTY_MOUNT_POINT => { 212 | BackendError::ToUser(gettext("The mount directory is not empty.")) 213 | } 214 | GocryptfsExitStatus::WRONG_PASSWORD => { 215 | BackendError::ToUser(gettext("The password is wrong.")) 216 | } 217 | GocryptfsExitStatus::EMPTY_PASSWORD => { 218 | BackendError::ToUser(gettext("The password is empty.")) 219 | } 220 | GocryptfsExitStatus::CANNOT_READ_CONFIG => { 221 | BackendError::ToUser(gettext("Vaults cannot read configuration file.")) 222 | } 223 | GocryptfsExitStatus::CANNOT_WRITE_CONFIG => { 224 | BackendError::ToUser(gettext("Vaults cannot write configuration file.")) 225 | } 226 | GocryptfsExitStatus::FSCK_ERROR => { 227 | BackendError::ToUser(gettext("The file system check reported an error.")) 228 | } 229 | _ => BackendError::ToUser(gettext("An unknown error occurred.")), 230 | }, 231 | None => BackendError::Generic, 232 | } 233 | } 234 | 235 | fn umount_ret_status_to_err(status: Option) -> BackendError { 236 | log::trace!("umount_ret_status_to_err({:?})", status); 237 | 238 | // We are guaranteed to have a non-zero errno here 239 | if let Some(status) = status { 240 | match status { 241 | 1 => BackendError::ToUser(gettext( 242 | "You don't have the necessary privileges to unmount the directory.", 243 | )), 244 | 2 => BackendError::ToUser(gettext("The data directory (mount point) does not exist.")), 245 | 4 => BackendError::ToUser(gettext("Internal error.")), 246 | 32 => BackendError::ToUser(gettext( 247 | "The data directory (mount point) is busy. There are open files or processes using the filesystem.", 248 | )), 249 | _ => BackendError::Generic, 250 | } 251 | } else { 252 | BackendError::Generic 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /data/icons/io.github.mpobaschnig.Vaults-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 52 | 56 | 60 | 64 | 71 | 78 | 85 | 92 | 93 | 95 | 102 | 107 | 108 | 110 | 113 | 120 | 121 | 122 | 124 | 130 | 131 | 134 | 139 | 140 | 142 | 145 | 152 | 153 | 154 | 156 | 162 | 163 | 166 | 171 | 172 | 174 | 177 | 184 | 185 | 186 | 188 | 194 | 195 | 198 | 203 | 204 | 206 | 209 | 216 | 217 | 218 | 220 | 226 | 227 | 230 | 235 | 236 | 237 | 242 | 243 | -------------------------------------------------------------------------------- /src/vault.rs: -------------------------------------------------------------------------------- 1 | // vault.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::backend::{Backend, BackendError}; 21 | use gio::VolumeMonitor; 22 | use gio::prelude::*; 23 | use gio::subclass::prelude::*; 24 | use gtk::{gio, glib, glib::Properties}; 25 | use serde::{Deserialize, Serialize}; 26 | use std::cell::RefCell; 27 | use uuid::Uuid; 28 | 29 | #[derive(Debug, Default, Deserialize, Clone, Serialize, glib::Boxed)] 30 | #[boxed_type(name = "VaultConfig")] 31 | pub struct VaultConfig { 32 | pub name: String, 33 | pub backend: Backend, 34 | pub encrypted_data_directory: String, 35 | pub mount_directory: String, 36 | pub session_lock: bool, 37 | } 38 | 39 | mod imp { 40 | use super::*; 41 | 42 | #[derive(Debug, Default, Deserialize, Properties, Serialize)] 43 | #[properties(wrapper_type = super::Vault)] 44 | pub struct Vault { 45 | pub uuid: RefCell, 46 | #[property(name = "config", get, set, type = VaultConfig)] 47 | #[property(name = "name", get, set, type = String, member = name)] 48 | #[property(name = "backend", get, set, type = Backend, member = backend, builder(Backend::Gocryptfs))] 49 | #[property(name = "encrypted-data-directory", get, set, type = String, member = encrypted_data_directory)] 50 | #[property(name = "mount-directory", get, set, type = String, member = mount_directory)] 51 | #[property(name = "session-lock", get, set, type = bool, member = session_lock)] 52 | pub config: RefCell, 53 | } 54 | 55 | #[glib::object_subclass] 56 | impl ObjectSubclass for Vault { 57 | const NAME: &'static str = "Vault"; 58 | type ParentType = glib::Object; 59 | type Type = super::Vault; 60 | } 61 | 62 | #[glib::derived_properties] 63 | impl ObjectImpl for Vault {} 64 | } 65 | 66 | glib::wrapper! { 67 | pub struct Vault(ObjectSubclass); 68 | } 69 | 70 | impl Vault { 71 | pub fn new( 72 | uuid: Uuid, 73 | name: String, 74 | backend: Backend, 75 | encrypted_data_directory: String, 76 | mount_directory: String, 77 | session_lock: bool, 78 | ) -> Vault { 79 | let object: Self = glib::Object::new(); 80 | 81 | object.imp().uuid.replace(uuid); 82 | object.imp().config.replace(VaultConfig { 83 | name, 84 | backend, 85 | encrypted_data_directory, 86 | mount_directory, 87 | session_lock, 88 | }); 89 | 90 | object 91 | } 92 | 93 | pub fn new_none() -> Vault { 94 | let object: Self = glib::Object::new(); 95 | 96 | object 97 | } 98 | 99 | pub fn get_uuid(&self) -> Uuid { 100 | log::trace!("get_uuid"); 101 | let uuid = self.imp().uuid.borrow(); 102 | *uuid 103 | } 104 | 105 | pub fn set_uuid(&self, uuid: Uuid) { 106 | log::trace!("set_uuid({})", uuid); 107 | *self.imp().uuid.borrow_mut() = uuid; 108 | } 109 | 110 | pub fn init(&self, password: String) -> Result<(), BackendError> { 111 | log::trace!("init(password: )"); 112 | Backend::init(&self.config(), password) 113 | } 114 | 115 | pub fn unlock(&self, password: String) -> Result<(), BackendError> { 116 | log::trace!("unlock(password: )"); 117 | Backend::open(&self.config(), password) 118 | } 119 | 120 | pub fn lock(&self) -> Result<(), BackendError> { 121 | log::trace!("lock"); 122 | Backend::close(&self.config()) 123 | } 124 | 125 | pub fn is_mounted(&self) -> bool { 126 | log::trace!("is_mounted"); 127 | 128 | let config_mount_directory = self.config().mount_directory; 129 | 130 | if self.is_mount_hidden() { 131 | log::debug!("Vault is hidden"); 132 | let is_vault_mounted_all = self.is_mounted_all(); 133 | log::debug!("Vault is mounted (all): {}", is_vault_mounted_all); 134 | return is_vault_mounted_all; 135 | } 136 | 137 | let canon_config_path = std::path::Path::new(&config_mount_directory) 138 | .canonicalize() 139 | .ok(); 140 | 141 | if let Some(canon_config_path) = canon_config_path { 142 | log::info!( 143 | "Opening canonical path: {}", 144 | &canon_config_path.as_os_str().to_str().unwrap() 145 | ); 146 | for mount in VolumeMonitor::get().mounts() { 147 | let is_configured_mount = mount 148 | .default_location() 149 | .path() 150 | .map(|mount_path| std::path::Path::canonicalize(&mount_path)) 151 | .and_then(Result::ok) 152 | .map(|canon_mount_path| canon_mount_path == canon_config_path) 153 | .unwrap_or(false); 154 | if is_configured_mount { 155 | return true; 156 | } 157 | } 158 | } else { 159 | log::error!("Could not get canonical mount directory path"); 160 | } 161 | 162 | log::debug!("Vault is not mounted"); 163 | false 164 | } 165 | 166 | pub fn is_mount_hidden(&self) -> bool { 167 | log::trace!("is_mount_hidden"); 168 | 169 | let vault_config = self.config(); 170 | 171 | let components: Vec<_> = std::path::Path::new(&vault_config.mount_directory) 172 | .components() 173 | .map(|c| c.as_os_str().to_str()) 174 | .collect(); 175 | 176 | components 177 | .iter() 178 | .flatten() 179 | .any(|c| c.starts_with(".") && !c.eq(&"..".to_string()) && c.len() > 1) 180 | } 181 | 182 | pub fn is_mounted_all(&self) -> bool { 183 | log::trace!("is_mounted_all"); 184 | 185 | use proc_mounts::*; 186 | 187 | let mount_list = MountList::new(); 188 | match mount_list { 189 | Ok(mount_list) => { 190 | MountList::get_mount_by_dest(&mount_list, &self.config().mount_directory).is_some() 191 | } 192 | Err(e) => { 193 | log::error!("Could not check if there exists any mounted vaults: {}", e); 194 | false 195 | } 196 | } 197 | } 198 | 199 | pub fn is_backend_available(&self) -> bool { 200 | log::trace!("is_backend_available"); 201 | if let Ok(success) = self.backend().is_available(&self.config()) { 202 | return success; 203 | } 204 | false 205 | } 206 | 207 | pub fn delete_encrypted_data(&self) -> std::io::Result<()> { 208 | log::trace!("delete_encrypted_data"); 209 | let encrypted_data_directory = self.encrypted_data_directory(); 210 | let path = std::path::Path::new(&encrypted_data_directory); 211 | log::debug!("Deleting encrypted data directory: {:?}", path); 212 | std::fs::remove_dir_all(path) 213 | } 214 | } 215 | 216 | #[cfg(test)] 217 | mod tests { 218 | use super::*; 219 | 220 | #[test] 221 | fn check_hidden_paths() { 222 | let vault = Vault::new( 223 | Uuid::nil(), 224 | "".to_string(), 225 | Backend::Gocryptfs, 226 | "".to_string(), 227 | "".to_string(), 228 | false, 229 | ); 230 | assert!(!vault.is_mount_hidden()); 231 | 232 | vault.set_config(VaultConfig { 233 | name: "".to_string(), 234 | backend: Backend::Gocryptfs, 235 | encrypted_data_directory: "".to_string(), 236 | mount_directory: ".".to_string(), 237 | session_lock: false, 238 | }); 239 | assert!(!vault.is_mount_hidden()); 240 | 241 | vault.set_config(VaultConfig { 242 | name: "".to_string(), 243 | backend: Backend::Gocryptfs, 244 | encrypted_data_directory: "".to_string(), 245 | mount_directory: "..".to_string(), 246 | session_lock: false, 247 | }); 248 | assert!(!vault.is_mount_hidden()); 249 | 250 | vault.set_config(VaultConfig { 251 | name: "".to_string(), 252 | backend: Backend::Gocryptfs, 253 | encrypted_data_directory: "".to_string(), 254 | mount_directory: "./".to_string(), 255 | session_lock: false, 256 | }); 257 | assert!(!vault.is_mount_hidden()); 258 | 259 | vault.set_config(VaultConfig { 260 | name: "".to_string(), 261 | backend: Backend::Gocryptfs, 262 | encrypted_data_directory: "".to_string(), 263 | mount_directory: "./Hidden".to_string(), 264 | session_lock: false, 265 | }); 266 | assert!(!vault.is_mount_hidden()); 267 | 268 | vault.set_config(VaultConfig { 269 | name: "".to_string(), 270 | backend: Backend::Gocryptfs, 271 | encrypted_data_directory: "".to_string(), 272 | mount_directory: "Test/.Test".to_string(), 273 | session_lock: false, 274 | }); 275 | assert!(vault.is_mount_hidden()); 276 | 277 | vault.set_config(VaultConfig { 278 | name: "".to_string(), 279 | backend: Backend::Gocryptfs, 280 | encrypted_data_directory: "".to_string(), 281 | mount_directory: "./Test/.Test".to_string(), 282 | session_lock: false, 283 | }); 284 | assert!(vault.is_mount_hidden()); 285 | 286 | vault.set_config(VaultConfig { 287 | name: "".to_string(), 288 | backend: Backend::Gocryptfs, 289 | encrypted_data_directory: "".to_string(), 290 | mount_directory: "../.Test".to_string(), 291 | session_lock: false, 292 | }); 293 | assert!(vault.is_mount_hidden()); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/ui/preferences.rs: -------------------------------------------------------------------------------- 1 | // preferences.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use adw::prelude::*; 21 | use adw::subclass::dialog::AdwDialogImpl; 22 | use gettextrs::gettext; 23 | use glib::clone; 24 | use gtk::gio; 25 | use gtk::gio::{Settings, SettingsBindFlags}; 26 | use gtk::glib::subclass::Signal; 27 | use gtk::subclass::prelude::*; 28 | use gtk::{CompositeTemplate, glib}; 29 | use once_cell::sync::Lazy; 30 | 31 | use crate::application::VApplication; 32 | use crate::config::APP_ID; 33 | 34 | mod imp { 35 | use super::*; 36 | 37 | #[derive(Debug, CompositeTemplate)] 38 | #[template(resource = "/io/github/mpobaschnig/Vaults/preferences.ui")] 39 | pub struct VaultsSettingsWindow { 40 | #[template_child] 41 | pub encrypted_data_directory_entry_row: TemplateChild, 42 | #[template_child] 43 | pub encrypted_data_directory_button: TemplateChild, 44 | #[template_child] 45 | pub mount_directory_entry_row: TemplateChild, 46 | #[template_child] 47 | pub mount_directory_button: TemplateChild, 48 | #[template_child] 49 | pub toast_overlay: TemplateChild, 50 | // cryfs 51 | #[template_child] 52 | pub cryfs_custom_binary_expander_row: TemplateChild, 53 | #[template_child] 54 | pub cryfs_custom_binary_entry_row: TemplateChild, 55 | #[template_child] 56 | pub cryfs_custom_binary_button: TemplateChild, 57 | // gocryptfs 58 | #[template_child] 59 | pub gocryptfs_custom_binary_expander_row: TemplateChild, 60 | #[template_child] 61 | pub gocryptfs_custom_binary_entry_row: TemplateChild, 62 | #[template_child] 63 | pub gocryptfs_custom_binary_button: TemplateChild, 64 | 65 | pub settings: Settings, 66 | } 67 | 68 | #[glib::object_subclass] 69 | impl ObjectSubclass for VaultsSettingsWindow { 70 | const NAME: &'static str = "VaultSettingsWindow"; 71 | type ParentType = adw::Dialog; 72 | type Type = super::VaultsSettingsWindow; 73 | 74 | fn new() -> Self { 75 | Self { 76 | encrypted_data_directory_entry_row: TemplateChild::default(), 77 | encrypted_data_directory_button: TemplateChild::default(), 78 | mount_directory_entry_row: TemplateChild::default(), 79 | mount_directory_button: TemplateChild::default(), 80 | toast_overlay: TemplateChild::default(), 81 | cryfs_custom_binary_expander_row: TemplateChild::default(), 82 | cryfs_custom_binary_entry_row: TemplateChild::default(), 83 | cryfs_custom_binary_button: TemplateChild::default(), 84 | gocryptfs_custom_binary_expander_row: TemplateChild::default(), 85 | gocryptfs_custom_binary_entry_row: TemplateChild::default(), 86 | gocryptfs_custom_binary_button: TemplateChild::default(), 87 | 88 | settings: Settings::new(APP_ID), 89 | } 90 | } 91 | 92 | fn class_init(klass: &mut Self::Class) { 93 | Self::bind_template(klass); 94 | } 95 | 96 | fn instance_init(obj: &glib::subclass::InitializingObject) { 97 | obj.init_template(); 98 | } 99 | } 100 | 101 | impl ObjectImpl for VaultsSettingsWindow { 102 | fn constructed(&self) { 103 | self.parent_constructed(); 104 | } 105 | 106 | fn signals() -> &'static [Signal] { 107 | static SIGNALS: Lazy> = 108 | Lazy::new(|| vec![Signal::builder("refresh").build()]); 109 | SIGNALS.as_ref() 110 | } 111 | } 112 | 113 | impl WidgetImpl for VaultsSettingsWindow {} 114 | impl WindowImpl for VaultsSettingsWindow {} 115 | impl AdwDialogImpl for VaultsSettingsWindow {} 116 | } 117 | 118 | glib::wrapper! { 119 | pub struct VaultsSettingsWindow(ObjectSubclass) 120 | @extends gtk::Widget, adw::Dialog, adw::Window, gtk::Window, 121 | @implements gio::ActionMap, gio::ActionGroup, gtk::Accessible, gtk::Native, gtk::Root, gtk::ShortcutManager, gtk::Buildable, gtk::ConstraintTarget; 122 | } 123 | 124 | impl Default for VaultsSettingsWindow { 125 | fn default() -> Self { 126 | Self::new() 127 | } 128 | } 129 | 130 | impl VaultsSettingsWindow { 131 | pub fn new() -> Self { 132 | let o: Self = glib::Object::builder().build(); 133 | o.setup_signals(); 134 | o 135 | } 136 | 137 | fn setup_signals(&self) { 138 | self.imp() 139 | .encrypted_data_directory_button 140 | .connect_clicked(clone!( 141 | #[weak(rename_to = obj)] 142 | self, 143 | move |_| { 144 | obj.encrypted_data_directory_button_clicked(); 145 | } 146 | )); 147 | 148 | self.imp().mount_directory_button.connect_clicked(clone!( 149 | #[weak(rename_to = obj)] 150 | self, 151 | move |_| { 152 | obj.mount_directory_button_clicked(); 153 | } 154 | )); 155 | 156 | self.imp() 157 | .settings 158 | .bind( 159 | "encrypted-data-directory", 160 | &self.imp().encrypted_data_directory_entry_row.get(), 161 | "text", 162 | ) 163 | .build(); 164 | 165 | self.imp() 166 | .settings 167 | .bind( 168 | "mount-directory", 169 | &self.imp().mount_directory_entry_row.get(), 170 | "text", 171 | ) 172 | .build(); 173 | 174 | self.imp() 175 | .settings 176 | .bind( 177 | "use-custom-cryfs-binary", 178 | &self.imp().cryfs_custom_binary_expander_row.get(), 179 | "expanded", 180 | ) 181 | .build(); 182 | 183 | self.imp() 184 | .settings 185 | .bind( 186 | "use-custom-cryfs-binary", 187 | &self.imp().cryfs_custom_binary_expander_row.get(), 188 | "enable-expansion", 189 | ) 190 | .flags(SettingsBindFlags::GET) 191 | .build(); 192 | 193 | self.imp() 194 | .settings 195 | .bind( 196 | "custom-cryfs-binary-path", 197 | &self.imp().cryfs_custom_binary_entry_row.get(), 198 | "text", 199 | ) 200 | .build(); 201 | 202 | self.imp() 203 | .settings 204 | .bind( 205 | "use-custom-gocryptfs-binary", 206 | &self.imp().gocryptfs_custom_binary_expander_row.get(), 207 | "expanded", 208 | ) 209 | .build(); 210 | 211 | self.imp() 212 | .settings 213 | .bind( 214 | "use-custom-gocryptfs-binary", 215 | &self.imp().gocryptfs_custom_binary_expander_row.get(), 216 | "enable-expansion", 217 | ) 218 | .build(); 219 | 220 | self.imp() 221 | .settings 222 | .bind( 223 | "custom-gocryptfs-binary-path", 224 | &self.imp().gocryptfs_custom_binary_entry_row.get(), 225 | "text", 226 | ) 227 | .build(); 228 | } 229 | 230 | fn encrypted_data_directory_button_clicked(&self) { 231 | let window = gtk::gio::Application::default() 232 | .unwrap() 233 | .downcast_ref::() 234 | .unwrap() 235 | .active_window() 236 | .unwrap() 237 | .clone(); 238 | 239 | glib::spawn_future_local(clone!( 240 | #[strong] 241 | window, 242 | #[strong(rename_to = obj)] 243 | self, 244 | async move { 245 | let dialog = gtk::FileDialog::builder() 246 | .title(gettext("Choose Encrypted Data Directory")) 247 | .modal(true) 248 | .accept_label(gettext("Select")) 249 | .build(); 250 | 251 | dialog.select_folder( 252 | Some(&window), 253 | gio::Cancellable::NONE, 254 | clone!( 255 | #[strong] 256 | obj, 257 | move |directory| { 258 | if let Ok(directory) = directory { 259 | let path = String::from( 260 | directory.path().unwrap().as_os_str().to_str().unwrap(), 261 | ); 262 | obj.imp().encrypted_data_directory_entry_row.set_text(&path); 263 | } 264 | } 265 | ), 266 | ); 267 | } 268 | )); 269 | } 270 | 271 | fn mount_directory_button_clicked(&self) { 272 | let window = gtk::gio::Application::default() 273 | .unwrap() 274 | .downcast_ref::() 275 | .unwrap() 276 | .active_window() 277 | .unwrap() 278 | .clone(); 279 | 280 | glib::spawn_future_local(clone!( 281 | #[strong] 282 | window, 283 | #[strong(rename_to = obj)] 284 | self, 285 | async move { 286 | let dialog = gtk::FileDialog::builder() 287 | .title(gettext("Choose Mount Directory")) 288 | .modal(true) 289 | .accept_label(gettext("Select")) 290 | .build(); 291 | 292 | dialog.select_folder( 293 | Some(&window), 294 | gio::Cancellable::NONE, 295 | clone!( 296 | #[strong] 297 | obj, 298 | move |directory| { 299 | if let Ok(directory) = directory { 300 | let path = String::from( 301 | directory.path().unwrap().as_os_str().to_str().unwrap(), 302 | ); 303 | obj.imp().mount_directory_entry_row.set_text(&path); 304 | } 305 | } 306 | ), 307 | ); 308 | } 309 | )); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /data/resources/ui/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 194 | 195 |
196 | 197 | _New Vault 198 | win.add_new_vault 199 | 200 | 201 | _Import Vault 202 | win.import_vault 203 | 204 |
205 |
206 | 207 |
208 | 209 | _Refresh 210 | win.refresh 211 | 212 |
213 |
214 | 215 | _Preferences 216 | app.preferences 217 | 218 | 219 | _Keyboard Shortcuts 220 | win.show-help-overlay 221 | 222 | 223 | _About Vaults 224 | app.about 225 | 226 |
227 |
228 |
229 | -------------------------------------------------------------------------------- /data/resources/ui/preferences.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 207 | 208 | -------------------------------------------------------------------------------- /data/resources/ui/import_vault_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 223 | 224 | -------------------------------------------------------------------------------- /src/ui/pages/vaults_page_row_settings_window.rs: -------------------------------------------------------------------------------- 1 | // vaults_page_row_settings_window.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use crate::application::VApplication; 21 | use crate::ui::pages::vaults_page_row_settings_window; 22 | use crate::vault::Vault; 23 | use crate::{backend, backend::Backend, user_config_manager::UserConfigManager}; 24 | use adw::{ 25 | prelude::{ComboRowExt, EntryRowExt}, 26 | subclass::{dialog::AdwDialogImpl, prelude::*}, 27 | }; 28 | use gettextrs::gettext; 29 | use gtk::{ 30 | self, CompositeTemplate, gio, 31 | glib::{self, Properties, clone, subclass::Signal}, 32 | prelude::*, 33 | }; 34 | use once_cell::sync::Lazy; 35 | use std::cell::RefCell; 36 | use strum::IntoEnumIterator; 37 | 38 | mod imp { 39 | use super::*; 40 | 41 | #[derive(Debug, CompositeTemplate, Properties, Default)] 42 | #[properties(wrapper_type = super::VaultsPageRowSettingsWindow)] 43 | #[template(resource = "/io/github/mpobaschnig/Vaults/vaults_page_row_settings_window.ui")] 44 | pub struct VaultsPageRowSettingsWindow { 45 | #[template_child] 46 | pub name_entry_row: TemplateChild, 47 | #[template_child] 48 | pub combo_row_backend: TemplateChild, 49 | #[template_child] 50 | pub encrypted_data_directory_entry_row: TemplateChild, 51 | #[template_child] 52 | pub encrypted_data_directory_button: TemplateChild, 53 | #[template_child] 54 | pub mount_directory_entry_row: TemplateChild, 55 | #[template_child] 56 | pub mount_directory_button: TemplateChild, 57 | #[template_child] 58 | pub lock_screen_switch_row: TemplateChild, 59 | #[property(get, set, name = "vault", construct)] 60 | pub vault: RefCell>, 61 | } 62 | 63 | #[glib::object_subclass] 64 | impl ObjectSubclass for VaultsPageRowSettingsWindow { 65 | const NAME: &'static str = "VaultsPageRowSettingsWindow"; 66 | type ParentType = adw::Dialog; 67 | type Type = vaults_page_row_settings_window::VaultsPageRowSettingsWindow; 68 | 69 | fn class_init(klass: &mut Self::Class) { 70 | Self::bind_template(klass); 71 | } 72 | 73 | fn instance_init(obj: &glib::subclass::InitializingObject) { 74 | obj.init_template(); 75 | } 76 | } 77 | 78 | #[glib::derived_properties] 79 | impl ObjectImpl for VaultsPageRowSettingsWindow { 80 | fn constructed(&self) { 81 | self.parent_constructed(); 82 | 83 | self.name_entry_row 84 | .set_text(&self.obj().vault().unwrap().name()); 85 | 86 | let mut model_position = 0; 87 | let vault_backend = self.obj().vault().unwrap().backend(); 88 | let list = gtk::StringList::new(&[]); 89 | for (position, backend) in Backend::iter().enumerate() { 90 | let backend = &backend::get_ui_string_from_backend(&backend); 91 | list.append(backend); 92 | if &backend::get_ui_string_from_backend(&vault_backend) == backend { 93 | model_position = position; 94 | } 95 | } 96 | self.combo_row_backend.set_model(Some(&list)); 97 | self.combo_row_backend.set_selected(model_position as u32); 98 | 99 | self.encrypted_data_directory_entry_row 100 | .set_text(&self.obj().vault().unwrap().encrypted_data_directory()); 101 | 102 | self.mount_directory_entry_row 103 | .set_text(&self.obj().vault().unwrap().mount_directory()); 104 | 105 | self.lock_screen_switch_row 106 | .set_active(self.obj().vault().unwrap().session_lock()); 107 | 108 | self.obj().connect_vault_notify(clone!(move |obj| { 109 | obj.emit_by_name::<()>("save", &[]); 110 | })); 111 | 112 | self.name_entry_row.connect_apply(clone!( 113 | #[weak(rename_to = s)] 114 | self, 115 | move |_| { 116 | s.obj().apply_changes(); 117 | } 118 | )); 119 | 120 | self.encrypted_data_directory_entry_row 121 | .connect_apply(clone!( 122 | #[weak(rename_to = s)] 123 | self, 124 | move |_| { 125 | s.obj().apply_changes(); 126 | } 127 | )); 128 | 129 | self.encrypted_data_directory_button.connect_clicked(clone!( 130 | #[weak(rename_to = s)] 131 | self, 132 | move |_| { 133 | s.obj().encrypted_data_directory_button_clicked(); 134 | } 135 | )); 136 | 137 | self.mount_directory_entry_row.connect_apply(clone!( 138 | #[weak(rename_to = s)] 139 | self, 140 | move |_| { 141 | s.obj().apply_changes(); 142 | } 143 | )); 144 | 145 | self.mount_directory_button.connect_clicked(clone!( 146 | #[weak(rename_to = s)] 147 | self, 148 | move |_| { 149 | s.obj().mount_directory_button_clicked(); 150 | } 151 | )); 152 | 153 | self.lock_screen_switch_row.connect_active_notify(clone!( 154 | #[weak(rename_to = s)] 155 | self, 156 | move |_| { 157 | s.obj().apply_changes(); 158 | } 159 | )); 160 | } 161 | 162 | fn signals() -> &'static [Signal] { 163 | static SIGNALS: Lazy> = Lazy::new(|| { 164 | vec![ 165 | Signal::builder("save").build(), 166 | Signal::builder("remove").build(), 167 | ] 168 | }); 169 | SIGNALS.as_ref() 170 | } 171 | } 172 | 173 | impl WidgetImpl for VaultsPageRowSettingsWindow {} 174 | impl WindowImpl for VaultsPageRowSettingsWindow {} 175 | impl AdwDialogImpl for VaultsPageRowSettingsWindow {} 176 | } 177 | 178 | glib::wrapper! { 179 | pub struct VaultsPageRowSettingsWindow(ObjectSubclass) 180 | @extends gtk::Widget, adw::Dialog, adw::Window, gtk::Window, 181 | @implements gio::ActionMap, gio::ActionGroup, gtk::Accessible, gtk::Native, gtk::Root, gtk::ShortcutManager, gtk::Buildable, gtk::ConstraintTarget; 182 | } 183 | 184 | impl VaultsPageRowSettingsWindow { 185 | pub fn new(vault: Vault) -> Self { 186 | glib::Object::builder().property("vault", vault).build() 187 | } 188 | 189 | fn apply_changes(&self) { 190 | let new_vault = Vault::new( 191 | self.vault().unwrap().get_uuid(), 192 | String::from(self.imp().name_entry_row.text().as_str()), 193 | backend::get_backend_from_ui_string( 194 | &self 195 | .imp() 196 | .combo_row_backend 197 | .selected_item() 198 | .unwrap() 199 | .downcast::() 200 | .unwrap() 201 | .string() 202 | .to_string(), 203 | ) 204 | .unwrap(), 205 | String::from( 206 | self.imp() 207 | .encrypted_data_directory_entry_row 208 | .text() 209 | .as_str(), 210 | ), 211 | String::from(self.imp().mount_directory_entry_row.text().as_str()), 212 | self.imp().lock_screen_switch_row.is_active(), 213 | ); 214 | 215 | UserConfigManager::instance() 216 | .change_vault(self.vault().unwrap().get_uuid(), new_vault.config().clone()); 217 | self.set_vault(new_vault); 218 | self.notify_vault(); 219 | } 220 | 221 | fn encrypted_data_directory_button_clicked(&self) { 222 | let window = gtk::gio::Application::default() 223 | .unwrap() 224 | .downcast_ref::() 225 | .unwrap() 226 | .active_window() 227 | .unwrap() 228 | .clone(); 229 | 230 | glib::spawn_future_local(clone!( 231 | #[strong] 232 | window, 233 | #[strong(rename_to = obj)] 234 | self, 235 | async move { 236 | let dialog = gtk::FileDialog::builder() 237 | .title(gettext("Choose Encrypted Data Directory")) 238 | .modal(true) 239 | .accept_label(gettext("Select")) 240 | .build(); 241 | 242 | dialog.select_folder( 243 | Some(&window), 244 | gio::Cancellable::NONE, 245 | clone!( 246 | #[strong] 247 | obj, 248 | move |directory| { 249 | if let Ok(directory) = directory { 250 | let path = String::from( 251 | directory.path().unwrap().as_os_str().to_str().unwrap(), 252 | ); 253 | obj.imp().encrypted_data_directory_entry_row.set_text(&path); 254 | obj.apply_changes(); 255 | } 256 | } 257 | ), 258 | ); 259 | } 260 | )); 261 | } 262 | 263 | fn mount_directory_button_clicked(&self) { 264 | let window = gtk::gio::Application::default() 265 | .unwrap() 266 | .downcast_ref::() 267 | .unwrap() 268 | .active_window() 269 | .unwrap() 270 | .clone(); 271 | 272 | glib::spawn_future_local(clone!( 273 | #[strong] 274 | window, 275 | #[strong(rename_to = obj)] 276 | self, 277 | async move { 278 | let dialog = gtk::FileDialog::builder() 279 | .title(gettext("Choose Mount Directory")) 280 | .modal(true) 281 | .accept_label(gettext("Select")) 282 | .build(); 283 | 284 | dialog.select_folder( 285 | Some(&window), 286 | gio::Cancellable::NONE, 287 | clone!( 288 | #[strong] 289 | obj, 290 | move |directory| { 291 | if let Ok(directory) = directory { 292 | let path = String::from( 293 | directory.path().unwrap().as_os_str().to_str().unwrap(), 294 | ); 295 | obj.imp().mount_directory_entry_row.set_text(&path); 296 | obj.apply_changes(); 297 | } 298 | } 299 | ), 300 | ); 301 | } 302 | )); 303 | } 304 | 305 | pub fn create_vault_from_settings(&self) -> Vault { 306 | Vault::new( 307 | self.vault().unwrap().get_uuid(), 308 | String::from(self.imp().name_entry_row.text().as_str()), 309 | backend::get_backend_from_ui_string( 310 | &self 311 | .imp() 312 | .combo_row_backend 313 | .selected_item() 314 | .unwrap() 315 | .downcast::() 316 | .unwrap() 317 | .string() 318 | .to_string(), 319 | ) 320 | .unwrap(), 321 | String::from( 322 | self.imp() 323 | .encrypted_data_directory_entry_row 324 | .text() 325 | .as_str(), 326 | ), 327 | String::from(self.imp().mount_directory_entry_row.text().as_str()), 328 | self.imp().lock_screen_switch_row.is_active(), 329 | ) 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/backend/cryfs.rs: -------------------------------------------------------------------------------- 1 | // cryfs.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use super::BackendError; 21 | use crate::global_config_manager::GlobalConfigManager; 22 | use crate::vault::VaultConfig; 23 | use gettextrs::gettext; 24 | use gtk::gio::Settings; 25 | use gtk::gio::prelude::SettingsExt; 26 | use std::process::Command; 27 | use std::{self, io::Write, process::Stdio}; 28 | 29 | fn get_binary_path(settings: &Settings, vault_config: &VaultConfig) -> Option { 30 | log::trace!("get_binary_path({:?})", vault_config); 31 | 32 | if settings.boolean("use-custom-cryfs-binary") { 33 | return Some(settings.string("custom-cryfs-binary-path").to_string()); 34 | } 35 | 36 | GlobalConfigManager::instance().get_cryfs_binary_path() 37 | } 38 | 39 | pub fn is_available(settings: &Settings, vault_config: &VaultConfig) -> Result { 40 | log::trace!("is_available({:?})", vault_config); 41 | 42 | let binary_path = get_binary_path(settings, vault_config); 43 | if binary_path.is_none() { 44 | log::error!("cryfs binary path is not set"); 45 | return Err(BackendError::ToUser(gettext("No CryFs binary path set"))); 46 | } 47 | 48 | let output = Command::new("flatpak-spawn") 49 | .arg("--host") 50 | .arg(binary_path.unwrap()) 51 | .arg("--version") 52 | .output()?; 53 | log::debug!("CryFS output: {:?}", output); 54 | 55 | let success = output.status.success(); 56 | log::info!("CryFS is available: {}", success); 57 | Ok(success) 58 | } 59 | 60 | pub fn init( 61 | settings: &Settings, 62 | vault_config: &VaultConfig, 63 | password: String, 64 | ) -> Result<(), BackendError> { 65 | log::trace!("init({:?}, password: )", vault_config); 66 | 67 | let binary_path = get_binary_path(settings, vault_config); 68 | if binary_path.is_none() { 69 | log::error!("cryfs binary path is not set"); 70 | return Err(BackendError::ToUser(gettext("No CryFs binary path set"))); 71 | } 72 | 73 | let mut child = Command::new("flatpak-spawn") 74 | .arg("--host") 75 | .arg(binary_path.unwrap()) 76 | .env("CRYFS_FRONTEND", "noninteractive") 77 | .stdin(Stdio::piped()) 78 | .stdout(Stdio::piped()) 79 | .arg("--allow-replaced-filesystem") 80 | .arg(&vault_config.encrypted_data_directory) 81 | .arg(&vault_config.mount_directory) 82 | .spawn()?; 83 | 84 | let mut pw = String::from("y"); 85 | pw.push('\n'); 86 | pw.push_str(&password); 87 | pw.push('\n'); 88 | pw.push_str(&password); 89 | pw.push('\n'); 90 | 91 | child 92 | .stdin 93 | .as_mut() 94 | .ok_or(BackendError::Generic)? 95 | .write_all(pw.as_bytes())?; 96 | 97 | let output = child.wait_with_output()?; 98 | log::debug!("CryFS output: {:?}", output); 99 | if output.status.success() { 100 | log::info!("CryFS init successful. Closing now"); 101 | close(settings, vault_config) 102 | } else { 103 | std::io::stdout().write_all(&output.stdout).unwrap(); 104 | std::io::stderr().write_all(&output.stderr).unwrap(); 105 | 106 | let err_code = output.status.code(); 107 | log::error!("CryFS init failed: {:?}", err_code); 108 | Err(status_to_err(err_code)) 109 | } 110 | } 111 | 112 | pub fn open( 113 | settings: &Settings, 114 | vault_config: &VaultConfig, 115 | password: String, 116 | ) -> Result<(), BackendError> { 117 | log::trace!("open({:?}, password: )", vault_config); 118 | 119 | let binary_path = get_binary_path(settings, vault_config); 120 | if binary_path.is_none() { 121 | log::error!("cryfs binary path is not set"); 122 | return Err(BackendError::ToUser(gettext("No CryFs binary path set"))); 123 | } 124 | 125 | let mut child = Command::new("flatpak-spawn") 126 | .arg("--host") 127 | .arg(binary_path.unwrap()) 128 | .env("CRYFS_FRONTEND", "noninteractive") 129 | .stdin(Stdio::piped()) 130 | .stdout(Stdio::piped()) 131 | .arg(&vault_config.encrypted_data_directory) 132 | .arg(&vault_config.mount_directory) 133 | .spawn()?; 134 | 135 | let mut pw = String::from(&password); 136 | pw.push('\n'); 137 | 138 | child 139 | .stdin 140 | .as_mut() 141 | .ok_or(BackendError::Generic)? 142 | .write_all(pw.as_bytes())?; 143 | 144 | let output = child.wait_with_output()?; 145 | log::debug!("CryFS output: {:?}", output); 146 | if output.status.success() { 147 | log::info!("CryFS open successful"); 148 | Ok(()) 149 | } else { 150 | std::io::stdout().write_all(&output.stdout).unwrap(); 151 | std::io::stderr().write_all(&output.stderr).unwrap(); 152 | 153 | let err_code = output.status.code(); 154 | log::error!("CryFS open failed: {:?}", err_code); 155 | Err(status_to_err(err_code)) 156 | } 157 | } 158 | 159 | pub fn close(_settings: &Settings, vault_config: &VaultConfig) -> Result<(), BackendError> { 160 | log::trace!("close({:?})", vault_config); 161 | 162 | let child = Command::new("flatpak-spawn") 163 | .arg("--host") 164 | .arg("fusermount") 165 | .arg("-u") 166 | .stdout(Stdio::piped()) 167 | .arg(&vault_config.mount_directory) 168 | .spawn()?; 169 | 170 | let output = child.wait_with_output()?; 171 | log::debug!("CryFS output: {:?}", output); 172 | if output.status.success() { 173 | log::info!("CryFS close successful"); 174 | Ok(()) 175 | } else { 176 | std::io::stdout().write_all(&output.stdout).unwrap(); 177 | std::io::stderr().write_all(&output.stderr).unwrap(); 178 | 179 | let err_code = output.status.code(); 180 | log::error!("CryFS close failed: {:?}", err_code); 181 | Err(status_to_err(err_code)) 182 | } 183 | } 184 | 185 | fn status_to_err(status: Option) -> BackendError { 186 | log::trace!("status_to_err({:?})", status); 187 | 188 | struct CryfsExitStatus {} 189 | 190 | // Error codes and text from: 191 | // https://github.com/cryfs/cryfs/blob/develop/src/cryfs/impl/ErrorCodes.h 192 | impl CryfsExitStatus { 193 | pub const _SUCCESS: i32 = 0; 194 | // An error happened that doesn't have an error code associated with it 195 | pub const UNSPECIFIED_ERROR: i32 = 1; 196 | // The command line arguments are invalid. 197 | pub const INVALID_ARGUMENTS: i32 = 10; 198 | // Couldn't load config file. Probably the password is wrong 199 | pub const WRONG_PASSWORD: i32 = 11; 200 | // Password cannot be empty 201 | pub const EMPTY_PASSWORD: i32 = 12; 202 | // The file system format is too new for this CryFS version. Please update your CryFS version. 203 | pub const TOO_NEW_FILESYSTEM_FORMAT: i32 = 13; 204 | // The file system format is too old for this CryFS version. Run with --allow-filesystem-upgrade to upgrade it. 205 | pub const TOO_OLD_FILESYSTEM_FORMAT: i32 = 14; 206 | // The file system uses a different cipher than the one specified on the command line using the --cipher argument. 207 | pub const WRONG_CIPHER: i32 = 15; 208 | // Base directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory) 209 | pub const INACCESSIBLE_BASE_DIR: i32 = 16; 210 | // Mount directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory) 211 | pub const INACCESSIBLE_MOUNT_DIR: i32 = 17; 212 | // Base directory can't be a subdirectory of the mount directory 213 | pub const BASE_DIR_INSIDE_MOUNT_DIR: i32 = 18; 214 | // Something's wrong with the file system. 215 | pub const INVALID_FILESYSTEM: i32 = 19; 216 | // The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this. 217 | pub const FILESYSTEM_ID_CHANGED: i32 = 20; 218 | // The filesystem encryption key differs from the last time we loaded this filesystem. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this. 219 | pub const ENCRYPTION_KEY_CHANGED: i32 = 21; 220 | // The command line options and the file system disagree on whether missing blocks should be treated as integrity violations. 221 | pub const FILESYSTEM_HAS_DIFFERENT_INTEGRITY_SETUP: i32 = 22; 222 | // File system is in single-client mode and can only be used from the client that created it. 223 | pub const SINGLE_CLIENT_FILE_SYSTEM: i32 = 23; 224 | // A previous run of the file system detected an integrity violation. Preventing access to make sure the user notices. The file system will be accessible again after the user deletes the integrity state file. 225 | pub const INTEGRITY_VIOLATION_ON_PREVIOUS_RUN: i32 = 24; 226 | // An integrity violation was detected and the file system unmounted to make sure the user notices. 227 | pub const INTEGRITY_VIOLATION: i32 = 25; 228 | } 229 | 230 | match status { 231 | Some(status) => match status { 232 | CryfsExitStatus::UNSPECIFIED_ERROR => { 233 | BackendError::ToUser(gettext("An unknown error occurred.")) 234 | } 235 | CryfsExitStatus::INVALID_ARGUMENTS => { 236 | BackendError::ToUser(gettext("Invalid arguments were given.")) 237 | } 238 | CryfsExitStatus::WRONG_PASSWORD => { 239 | BackendError::ToUser(gettext("The password is wrong.")) 240 | } 241 | CryfsExitStatus::EMPTY_PASSWORD => { 242 | BackendError::ToUser(gettext("The password is empty.")) 243 | } 244 | CryfsExitStatus::TOO_NEW_FILESYSTEM_FORMAT => BackendError::ToUser(gettext( 245 | "The format of the encrypted data directory is too new for this CryFS version. Please update CryFS.", 246 | )), 247 | CryfsExitStatus::TOO_OLD_FILESYSTEM_FORMAT => BackendError::ToUser(gettext( 248 | "The format of the encrypted data directory is too old for this CryFS version.", 249 | )), 250 | CryfsExitStatus::WRONG_CIPHER => BackendError::ToUser(gettext( 251 | "The vault uses a different cipher than the default of CryFS.", 252 | )), 253 | CryfsExitStatus::INACCESSIBLE_BASE_DIR => BackendError::ToUser(gettext( 254 | "The encrypted data directory does not exist or is inaccessible.", 255 | )), 256 | CryfsExitStatus::INACCESSIBLE_MOUNT_DIR => BackendError::ToUser(gettext( 257 | "The mount directory does not exist or is inaccessible.", 258 | )), 259 | CryfsExitStatus::BASE_DIR_INSIDE_MOUNT_DIR => BackendError::ToUser(gettext( 260 | "The mount directory is inside the encrypted data directory.", 261 | )), 262 | CryfsExitStatus::INVALID_FILESYSTEM => { 263 | BackendError::ToUser(gettext("The encrypted data directory is invalid.")) 264 | } 265 | CryfsExitStatus::FILESYSTEM_ID_CHANGED => BackendError::ToUser(gettext( 266 | "The encrypted data id in the configuration file is different to the last time this vault was opened. This could mean someone replaced files in the encrypted data directory with different ones.", 267 | )), 268 | CryfsExitStatus::ENCRYPTION_KEY_CHANGED => BackendError::ToUser(gettext( 269 | "The encryption key for your encrypted files is different to the last time this vault was opened. This could mean someone replaced files in the encrypted data directory with different ones.", 270 | )), 271 | CryfsExitStatus::FILESYSTEM_HAS_DIFFERENT_INTEGRITY_SETUP => BackendError::ToUser( 272 | gettext("Vaults' configuration and the encrypted data configuration mismatches."), 273 | ), 274 | CryfsExitStatus::SINGLE_CLIENT_FILE_SYSTEM => BackendError::ToUser(gettext( 275 | "The encrypted data directory is in single-user mode and can only be used from the user that created it.", 276 | )), 277 | CryfsExitStatus::INTEGRITY_VIOLATION_ON_PREVIOUS_RUN => BackendError::ToUser(gettext( 278 | "CryFS detected an integrity violation. The encrypted data directory will be accessible again after the integrity state file has been deleted.", 279 | )), 280 | CryfsExitStatus::INTEGRITY_VIOLATION => BackendError::ToUser(gettext( 281 | "An integrity violation was detected. Vault will be unmounted.", 282 | )), 283 | _ => BackendError::ToUser(gettext("An unknown error occurred.")), 284 | }, 285 | None => BackendError::Generic, 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/global_config_manager.rs: -------------------------------------------------------------------------------- 1 | // global_config_manager.rs 2 | // 3 | // Copyright 2021 Martin Pobaschnig 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use gtk::{ 21 | gio::subclass::prelude::*, 22 | glib::{self, home_dir, user_config_dir, user_data_dir}, 23 | }; 24 | use ini::Ini; 25 | use serde::{Deserialize, Serialize}; 26 | use std::{cell::RefCell, fs}; 27 | use toml::de::Error; 28 | 29 | use self::imp::GlobalConfig; 30 | 31 | static mut GLOBAL_CONFIG_MANAGER: Option = None; 32 | 33 | mod imp { 34 | use super::*; 35 | 36 | #[derive(Debug, Serialize, Deserialize)] 37 | pub struct GlobalConfig { 38 | pub encrypted_data_directory: RefCell>, 39 | pub mount_directory: RefCell>, 40 | pub gocryptfs_custom_binary: RefCell>, 41 | pub gocryptfs_custom_binary_path: RefCell>, 42 | pub cryfs_custom_binary: RefCell>, 43 | pub cryfs_custom_binary_path: RefCell>, 44 | } 45 | 46 | impl Clone for GlobalConfig { 47 | fn clone(&self) -> GlobalConfig { 48 | GlobalConfig { 49 | encrypted_data_directory: self.encrypted_data_directory.clone(), 50 | mount_directory: self.mount_directory.clone(), 51 | gocryptfs_custom_binary: self.gocryptfs_custom_binary.clone(), 52 | gocryptfs_custom_binary_path: self.gocryptfs_custom_binary_path.clone(), 53 | cryfs_custom_binary: self.cryfs_custom_binary.clone(), 54 | cryfs_custom_binary_path: self.cryfs_custom_binary_path.clone(), 55 | } 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | pub struct GlobalConfigManager { 61 | pub user_config_directory: RefCell>, 62 | 63 | pub global_config: RefCell, 64 | pub flatpak_info: RefCell>, 65 | } 66 | 67 | #[glib::object_subclass] 68 | impl ObjectSubclass for GlobalConfigManager { 69 | const NAME: &'static str = "GlobalConfigManager"; 70 | type ParentType = glib::Object; 71 | type Type = super::GlobalConfigManager; 72 | 73 | fn new() -> Self { 74 | Self { 75 | user_config_directory: RefCell::new(None), 76 | global_config: RefCell::new(GlobalConfig { 77 | encrypted_data_directory: RefCell::new(Some("".to_string())), 78 | mount_directory: RefCell::new(Some("".to_string())), 79 | cryfs_custom_binary: RefCell::new(Some(false)), 80 | cryfs_custom_binary_path: RefCell::new(Some("".to_string())), 81 | gocryptfs_custom_binary: RefCell::new(Some(false)), 82 | gocryptfs_custom_binary_path: RefCell::new(Some("".to_string())), 83 | }), 84 | flatpak_info: RefCell::new(None), 85 | } 86 | } 87 | } 88 | 89 | impl ObjectImpl for GlobalConfigManager {} 90 | } 91 | 92 | glib::wrapper! { 93 | pub struct GlobalConfigManager(ObjectSubclass); 94 | } 95 | 96 | impl GlobalConfigManager { 97 | pub fn instance() -> Self { 98 | unsafe { 99 | #[allow(static_mut_refs)] 100 | match GLOBAL_CONFIG_MANAGER.as_ref() { 101 | Some(user_config) => user_config.clone(), 102 | None => { 103 | let user_config = GlobalConfigManager::new(); 104 | GLOBAL_CONFIG_MANAGER = Some(user_config.clone()); 105 | user_config 106 | } 107 | } 108 | } 109 | } 110 | 111 | fn new() -> Self { 112 | log::trace!("new()"); 113 | 114 | let object: Self = glib::Object::new(); 115 | 116 | if let Ok(flatpak_info) = Ini::load_from_file("/.flatpak-info") { 117 | log::debug!("Loaded .flatpak-info successfully"); 118 | *object.imp().flatpak_info.borrow_mut() = Some(flatpak_info); 119 | } else { 120 | log::error!("Could not load .flatpak-info"); 121 | } 122 | 123 | match user_config_dir().as_os_str().to_str() { 124 | Some(user_config_directory) => { 125 | log::info!("Got user data dir: {}", user_config_directory); 126 | 127 | *object.imp().user_config_directory.borrow_mut() = 128 | Some(user_config_directory.to_owned() + "/global_config.toml"); 129 | } 130 | None => { 131 | log::error!("Could not get user data directory"); 132 | } 133 | } 134 | 135 | object 136 | } 137 | 138 | pub fn read_config(&self) { 139 | log::trace!("read_config()"); 140 | 141 | if let Some(path) = self.imp().user_config_directory.borrow().as_ref() { 142 | let global_config = &mut *self.imp().global_config.borrow_mut(); 143 | 144 | let contents = std::fs::read_to_string(path); 145 | match contents { 146 | Ok(content) => { 147 | let res: Result = toml::from_str(&content.clone()); 148 | match res { 149 | Ok(v) => { 150 | *global_config = v; 151 | } 152 | Err(e) => { 153 | log::error!("Failed to parse user data config: {}", e); 154 | } 155 | } 156 | } 157 | Err(e) => { 158 | log::error!( 159 | "Failed to read user data config: {}. Falling back to default directories.", 160 | e 161 | ); 162 | 163 | match user_data_dir().as_os_str().to_str() { 164 | Some(user_data_directory) => { 165 | log::info!("Got user data directory: {}", user_data_directory); 166 | 167 | *global_config.encrypted_data_directory.borrow_mut() = 168 | Some(user_data_directory.to_owned() + "/"); 169 | } 170 | None => { 171 | log::error!("Could not get user data directory"); 172 | } 173 | } 174 | 175 | match home_dir().to_str() { 176 | Some(home_directory) => { 177 | log::debug!("Got home directory: {}", &home_directory); 178 | 179 | *global_config.mount_directory.borrow_mut() = 180 | Some(home_directory.to_owned() + "/Vaults/"); 181 | } 182 | None => { 183 | log::error!("Could not get home directory"); 184 | } 185 | } 186 | 187 | match toml::to_string_pretty(&global_config) { 188 | Ok(contents) => match std::fs::write(path, &contents) { 189 | Ok(_) => { 190 | log::debug!("Successfully wrote user config: {}", &contents); 191 | } 192 | Err(e) => { 193 | log::error!("Failed to write user config: {}", e); 194 | } 195 | }, 196 | Err(e) => { 197 | log::error!("Failed to parse config: {}", e); 198 | } 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | pub fn write_config(&self) { 206 | log::trace!("write_config()"); 207 | 208 | if let Some(path) = self.imp().user_config_directory.borrow().as_ref() { 209 | match toml::to_string_pretty(&self.imp().global_config.borrow().clone()) { 210 | Ok(contents) => match std::fs::write(path, &contents) { 211 | Ok(_) => { 212 | log::debug!("Successfully wrote user config: {}", &contents); 213 | } 214 | Err(e) => { 215 | log::error!("Failed to write user config: {}", e); 216 | } 217 | }, 218 | Err(e) => { 219 | log::error!("Failed to parse config: {}", e); 220 | } 221 | } 222 | } 223 | } 224 | 225 | pub fn get_global_config(&self) -> GlobalConfig { 226 | log::trace!("get_global_config()"); 227 | 228 | self.imp().global_config.borrow().clone() 229 | } 230 | 231 | pub fn set_encrypted_data_directory(&self, path: String) { 232 | log::trace!("set_encrypted_data_directory({})", path); 233 | 234 | *self 235 | .imp() 236 | .global_config 237 | .borrow_mut() 238 | .encrypted_data_directory 239 | .borrow_mut() = Some(path); 240 | } 241 | 242 | pub fn set_mount_directory(&self, path: String) { 243 | log::trace!("set_mount_directory({})", path); 244 | 245 | *self 246 | .imp() 247 | .global_config 248 | .borrow_mut() 249 | .mount_directory 250 | .borrow_mut() = Some(path); 251 | } 252 | 253 | pub fn get_flatpak_info(&self) -> Option { 254 | log::trace!("get_flatpak_info()"); 255 | 256 | self.imp().flatpak_info.borrow().as_ref().cloned() 257 | } 258 | 259 | pub fn cryfs_custom_binary(&self) -> bool { 260 | log::trace!("cryfs_custom_binary()"); 261 | 262 | (*self 263 | .imp() 264 | .global_config 265 | .borrow() 266 | .cryfs_custom_binary 267 | .borrow()) 268 | .unwrap_or(false) 269 | } 270 | 271 | pub fn set_cryfs_custom_binary(&self, enabled: bool) { 272 | log::trace!("set_cryfs_custom_binary({})", enabled); 273 | 274 | *self 275 | .imp() 276 | .global_config 277 | .borrow_mut() 278 | .cryfs_custom_binary 279 | .borrow_mut() = Some(enabled); 280 | } 281 | 282 | pub fn cryfs_custom_binary_path(&self) -> String { 283 | log::trace!("cryfs_custom_binary_path()"); 284 | 285 | self.imp() 286 | .global_config 287 | .borrow() 288 | .cryfs_custom_binary_path 289 | .borrow() 290 | .clone() 291 | .unwrap() 292 | } 293 | 294 | pub fn set_cryfs_custom_binary_path(&self, path: String) { 295 | log::trace!("set_cryfs_custom_binary_path({})", path); 296 | 297 | *self 298 | .imp() 299 | .global_config 300 | .borrow_mut() 301 | .cryfs_custom_binary_path 302 | .borrow_mut() = Some(path); 303 | } 304 | 305 | pub fn gocryptfs_custom_binary(&self) -> bool { 306 | log::trace!("gocryptfs_custom_binary()"); 307 | 308 | (*self 309 | .imp() 310 | .global_config 311 | .borrow() 312 | .gocryptfs_custom_binary 313 | .borrow()) 314 | .unwrap_or(false) 315 | } 316 | 317 | pub fn set_gocryptfs_custom_binary(&self, enabled: bool) { 318 | log::trace!("set_gocryptfs_custom_binary({})", enabled); 319 | 320 | *self 321 | .imp() 322 | .global_config 323 | .borrow_mut() 324 | .gocryptfs_custom_binary 325 | .borrow_mut() = Some(enabled); 326 | } 327 | 328 | pub fn gocryptfs_custom_binary_path(&self) -> String { 329 | log::trace!("gocryptfs_custom_binary_path()"); 330 | 331 | self.imp() 332 | .global_config 333 | .borrow() 334 | .gocryptfs_custom_binary_path 335 | .borrow() 336 | .clone() 337 | .unwrap() 338 | } 339 | 340 | pub fn set_gocryptfs_custom_binary_path(&self, path: String) { 341 | log::trace!("set_gocrypfs_custom_binary_path({})", path); 342 | 343 | *self 344 | .imp() 345 | .global_config 346 | .borrow_mut() 347 | .gocryptfs_custom_binary_path 348 | .borrow_mut() = Some(path); 349 | } 350 | 351 | pub fn get_cryfs_binary_path(&self) -> Option { 352 | let flatpak_info = self.get_flatpak_info()?; 353 | let instance_path = flatpak_info.section(Some("Instance"))?.get("app-path")?; 354 | let cryfs_instance_path = instance_path.to_owned() + "/bin/cryfs"; 355 | log::info!("CryFS binary path: {}", cryfs_instance_path); 356 | Some(cryfs_instance_path) 357 | } 358 | 359 | pub fn get_gocryptfs_binary_path(&self) -> Option { 360 | let flatpak_info = self.get_flatpak_info()?; 361 | let instance_path = flatpak_info.section(Some("Instance"))?.get("app-path")?; 362 | let gocryptfs_instance_path = instance_path.to_owned() + "/bin/gocryptfs"; 363 | log::info!("gocryptfs binary path: {}", gocryptfs_instance_path); 364 | Some(gocryptfs_instance_path) 365 | } 366 | } 367 | --------------------------------------------------------------------------------