diff --git a/.vscode/settings.json b/.vscode/settings.json index e5dc0e1..ef8ad26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,11 +5,13 @@ "clickaway", "clientgpversion", "clientos", + "consts", "devicename", "distro", "gpcommon", "gpgui", "gpservice", + "humantime", "Immer", "jnlp", "oneshot", @@ -17,7 +19,10 @@ "prelogin", "prelogon", "prelogonuserauthcookie", + "repr", + "rustc", "tauri", + "thiserror", "unlisten", "userauthcookie", "vpnc", diff --git a/Cargo.lock b/Cargo.lock index 63ec891..f402a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,53 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead", + "aes 0.8.3", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -51,8 +98,11 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" name = "app" version = "0.1.0" dependencies = [ - "env_logger", + "aes-gcm", + "anyhow", "gpcommon", + "hex", + "keyring", "log", "openssl", "regex", @@ -69,6 +119,117 @@ dependencies = [ "whoami", ] +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock", + "autocfg", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.66" @@ -104,6 +265,12 @@ dependencies = [ "system-deps 6.0.3", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "attohttpc" version = "0.22.0" @@ -170,6 +337,37 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher 0.3.0", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "log", +] + [[package]] name = "brotli" version = "3.3.4" @@ -329,6 +527,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -387,6 +604,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -478,6 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -518,6 +745,15 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cty" version = "0.2.2" @@ -571,6 +807,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -592,6 +839,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -671,16 +919,24 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.10.0" +name = "enumflags2" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", ] [[package]] @@ -704,6 +960,12 @@ dependencies = [ "libc", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fastrand" version = "1.8.0" @@ -729,7 +991,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset", + "memoffset 0.6.5", "rustc_version 0.3.3", ] @@ -827,6 +1089,21 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.26" @@ -857,8 +1134,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1004,6 +1284,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gio" version = "0.15.12" @@ -1139,8 +1429,9 @@ dependencies = [ name = "gpservice" version = "0.1.0" dependencies = [ - "env_logger", + "fern", "gpcommon", + "humantime", "log", "tokio", ] @@ -1251,6 +1542,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -1388,6 +1697,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1408,18 +1726,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "is_executable" version = "1.0.1" @@ -1505,6 +1811,20 @@ dependencies = [ "treediff", ] +[[package]] +name = "keyring" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ac4b8b0884cdf23c4619d139acf43839eac4f0739b92980c2a6d460d9c84f5" +dependencies = [ + "byteorder", + "lazy_static", + "linux-keyutils", + "secret-service", + "security-framework", + "winapi", +] + [[package]] name = "kuchiki" version = "0.8.1" @@ -1538,6 +1858,16 @@ dependencies = [ "safemem", ] +[[package]] +name = "linux-keyutils" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f27bb67f6dd1d0bb5ab582868e4f65052e58da6401188a08f0da09cf512b84b" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.3.7" @@ -1638,6 +1968,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1711,6 +2050,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset 0.7.1", + "static_assertions", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1727,6 +2079,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1737,6 +2123,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -1744,6 +2141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", + "num-bigint", "num-integer", "num-traits", ] @@ -1831,6 +2229,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "open" version = "3.2.0" @@ -1886,6 +2290,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "overload" version = "0.1.1" @@ -1917,6 +2331,12 @@ dependencies = [ "system-deps 6.0.3", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2104,6 +2524,34 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2423,6 +2871,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "secret-service" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da1a5ad4d28c03536f82f77d9f36603f5e37d8869ac98f0a750d5b5686d8d95" +dependencies = [ + "aes 0.7.5", + "block-modes", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.5", + "serde", + "sha2", + "zbus", +] + [[package]] name = "security-framework" version = "2.8.2" @@ -2616,6 +3083,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.6" @@ -2636,6 +3114,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2725,6 +3213,12 @@ dependencies = [ "loom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.4" @@ -2757,6 +3251,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.107" @@ -3112,15 +3612,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thin-slice" version = "0.1.1" @@ -3354,6 +3845,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicode-bidi" version = "0.3.10" @@ -3381,6 +3882,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -3501,6 +4012,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -4041,3 +4558,117 @@ checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] + +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "zbus" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.107", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.107", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] diff --git a/gpcommon/src/client.rs b/gpcommon/src/client.rs index 8705338..9c1d45c 100644 --- a/gpcommon/src/client.rs +++ b/gpcommon/src/client.rs @@ -201,8 +201,14 @@ impl Client { }) } - pub async fn connect(&self, server: String, cookie: String) -> Result<(), ServerApiError> { - self.send_command(Connect::new(server, cookie).into()).await + pub async fn connect( + &self, + server: String, + cookie: String, + user_agent: String, + ) -> Result<(), ServerApiError> { + self.send_command(Connect::new(server, cookie, user_agent).into()) + .await } pub async fn disconnect(&self) -> Result<(), ServerApiError> { diff --git a/gpcommon/src/cmd/connect.rs b/gpcommon/src/cmd/connect.rs index d1e9688..5e360fa 100644 --- a/gpcommon/src/cmd/connect.rs +++ b/gpcommon/src/cmd/connect.rs @@ -7,11 +7,16 @@ use serde::{Deserialize, Serialize}; pub struct Connect { server: String, cookie: String, + user_agent: String, } impl Connect { - pub fn new(server: String, cookie: String) -> Self { - Self { server, cookie } + pub fn new(server: String, cookie: String, user_agent: String) -> Self { + Self { + server, + cookie, + user_agent, + } } } @@ -25,7 +30,7 @@ impl Command for Connect { return Err(format!("VPN is already in state: {:?}", status).into()); } - if let Err(err) = vpn.connect(&self.server, &self.cookie).await { + if let Err(err) = vpn.connect(&self.server, &self.cookie, &self.user_agent).await { return Err(err.to_string().into()); } diff --git a/gpcommon/src/vpn/ffi.rs b/gpcommon/src/vpn/ffi.rs index af4539d..973522f 100644 --- a/gpcommon/src/vpn/ffi.rs +++ b/gpcommon/src/vpn/ffi.rs @@ -5,16 +5,17 @@ use tokio::sync::mpsc; #[repr(C)] #[derive(Debug, Copy, Clone)] pub(crate) struct Options { - pub server: *const ::std::os::raw::c_char, - pub cookie: *const ::std::os::raw::c_char, - pub script: *const ::std::os::raw::c_char, + pub server: *const std::os::raw::c_char, + pub cookie: *const std::os::raw::c_char, + pub script: *const std::os::raw::c_char, + pub user_agent: *const std::os::raw::c_char, pub user_data: *mut c_void, } #[link(name = "vpn")] extern "C" { #[link_name = "vpn_connect"] - pub(crate) fn connect(options: *const Options) -> ::std::os::raw::c_int; + pub(crate) fn connect(options: *const Options) -> std::os::raw::c_int; #[link_name = "vpn_disconnect"] pub(crate) fn disconnect(); @@ -32,7 +33,7 @@ extern "C" fn on_vpn_connected(value: i32, sender: *mut c_void) { // level: 0 = error, 1 = info, 2 = debug, 3 = trace // map the error level log in openconnect to the warning level #[no_mangle] -extern "C" fn vpn_log(level: i32, message: *const ::std::os::raw::c_char) { +extern "C" fn vpn_log(level: i32, message: *const std::os::raw::c_char) { let message = unsafe { std::ffi::CStr::from_ptr(message) }; let message = message.to_str().unwrap_or("Invalid log message"); // Strip the trailing newline diff --git a/gpcommon/src/vpn/mod.rs b/gpcommon/src/vpn/mod.rs index 243797f..3883520 100644 --- a/gpcommon/src/vpn/mod.rs +++ b/gpcommon/src/vpn/mod.rs @@ -56,6 +56,7 @@ pub(crate) struct VpnOptions { server: CString, cookie: CString, script: CString, + user_agent: CString, } impl VpnOptions { @@ -64,6 +65,7 @@ impl VpnOptions { server: self.server.as_ptr(), cookie: self.cookie.as_ptr(), script: self.script.as_ptr(), + user_agent: self.user_agent.as_ptr(), user_data, } } @@ -88,6 +90,7 @@ impl Vpn { &self, server: &str, cookie: &str, + user_agent: &str, ) -> Result<(), Box> { let script = match find_default_vpnc_script() { Some(script) => { @@ -104,6 +107,7 @@ impl Vpn { server: VpnOptions::to_cstr(server), cookie: VpnOptions::to_cstr(cookie), script: VpnOptions::to_cstr(script), + user_agent: VpnOptions::to_cstr(user_agent), }); let vpn_options = self.vpn_options.clone(); diff --git a/gpcommon/src/vpn/vpn.c b/gpcommon/src/vpn/vpn.c index 54620bf..904c571 100644 --- a/gpcommon/src/vpn/vpn.c +++ b/gpcommon/src/vpn/vpn.c @@ -53,7 +53,7 @@ int vpn_connect(const vpn_options *options) g_user_data = options->user_data; g_vpnc_script = options->script; - vpninfo = openconnect_vpninfo_new("PAN GlobalProtect", validate_peer_cert, NULL, NULL, print_progress, NULL); + vpninfo = openconnect_vpninfo_new(options->user_agent, validate_peer_cert, NULL, NULL, print_progress, NULL); if (!vpninfo) { diff --git a/gpcommon/src/vpn/vpn.h b/gpcommon/src/vpn/vpn.h index 573fd5c..355aa59 100644 --- a/gpcommon/src/vpn/vpn.h +++ b/gpcommon/src/vpn/vpn.h @@ -8,6 +8,7 @@ typedef struct vpn_options const char *server; const char *cookie; const char *script; + const char *user_agent; void *user_data; } vpn_options; diff --git a/gpgui/package.json b/gpgui/package.json index 097a467..4d4af5c 100644 --- a/gpgui/package.json +++ b/gpgui/package.json @@ -23,8 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-spinners": "^0.13.8", - "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log", - "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1" + "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1" }, "devDependencies": { "@tauri-apps/cli": "^1.3.1", diff --git a/gpgui/pnpm-lock.yaml b/gpgui/pnpm-lock.yaml index b3dcbbc..f7c4dbf 100644 --- a/gpgui/pnpm-lock.yaml +++ b/gpgui/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -48,11 +48,8 @@ dependencies: specifier: ^0.13.8 version: 0.13.8(react-dom@18.2.0)(react@18.2.0) tauri-plugin-log-api: - specifier: github:tauri-apps/tauri-plugin-log - version: github.com/tauri-apps/tauri-plugin-log/21921031d74f871180381317a338559f588ad8e9 - tauri-plugin-store-api: - specifier: github:tauri-apps/tauri-plugin-store#v1 - version: github.com/tauri-apps/tauri-plugin-store/1467ba770623ab1d41d825841c3d9435d9eaa0f1 + specifier: github:tauri-apps/tauri-plugin-log#v1 + version: github.com/tauri-apps/tauri-plugin-log/fbbb126e6d7fba7a7e6772d33f99c0fb689f32b6 devDependencies: '@tauri-apps/cli': @@ -878,6 +875,11 @@ packages: engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} dev: false + /@tauri-apps/api@1.4.0: + resolution: {integrity: sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==} + engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} + dev: false + /@tauri-apps/cli-darwin-arm64@1.3.1: resolution: {integrity: sha512-QlepYVPgOgspcwA/u4kGG4ZUijlXfdRtno00zEy+LxinN/IRXtk+6ErVtsmoLi1ZC9WbuMwzAcsRvqsD+RtNAg==} engines: {node: '>= 10'} @@ -1465,18 +1467,10 @@ packages: engines: {node: '>= 6'} dev: false - github.com/tauri-apps/tauri-plugin-log/21921031d74f871180381317a338559f588ad8e9: - resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/21921031d74f871180381317a338559f588ad8e9} + github.com/tauri-apps/tauri-plugin-log/fbbb126e6d7fba7a7e6772d33f99c0fb689f32b6: + resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/fbbb126e6d7fba7a7e6772d33f99c0fb689f32b6} name: tauri-plugin-log-api version: 0.0.0 dependencies: - '@tauri-apps/api': 1.3.0 - dev: false - - github.com/tauri-apps/tauri-plugin-store/1467ba770623ab1d41d825841c3d9435d9eaa0f1: - resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/1467ba770623ab1d41d825841c3d9435d9eaa0f1} - name: tauri-plugin-store-api - version: 0.0.0 - dependencies: - '@tauri-apps/api': 1.3.0 + '@tauri-apps/api': 1.4.0 dev: false diff --git a/gpgui/src-tauri/Cargo.toml b/gpgui/src-tauri/Cargo.toml index 4255a1b..c4102ab 100644 --- a/gpgui/src-tauri/Cargo.toml +++ b/gpgui/src-tauri/Cargo.toml @@ -23,7 +23,6 @@ tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", br serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -env_logger = "0.10" webkit2gtk = "0.18.2" regex = "1" url = "2.3" @@ -32,6 +31,10 @@ veil = "0.1.6" whoami = "1.4.1" tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } openssl = "0.10" +keyring = "2" +aes-gcm = { version = "0.10", features = ["std"] } +hex = "0.4" +anyhow = "1.0" [features] # by default Tauri runs in production mode diff --git a/gpgui/src-tauri/src/auth.rs b/gpgui/src-tauri/src/auth.rs index 07df56f..2bcaea3 100644 --- a/gpgui/src-tauri/src/auth.rs +++ b/gpgui/src-tauri/src/auth.rs @@ -133,7 +133,7 @@ fn build_window(app_handle: &AppHandle, ua: &str) -> tauri::Result { Window::builder(app_handle, AUTH_WINDOW_LABEL, url) .visible(false) .title("GlobalProtect Login") - .inner_size(400.0, 647.0) + .inner_size(600.0, 500.0) .min_inner_size(370.0, 600.0) .user_agent(ua) .always_on_top(true) diff --git a/gpgui/src-tauri/src/commands.rs b/gpgui/src-tauri/src/commands.rs index 765304d..e715020 100644 --- a/gpgui/src-tauri/src/commands.rs +++ b/gpgui/src-tauri/src/commands.rs @@ -1,9 +1,11 @@ use crate::{ auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams}, + storage::{AppStorage, KeyHint}, utils::get_openssl_conf, utils::get_openssl_conf_path, }; use gpcommon::{Client, ServerApiError, VpnStatus}; +use serde_json::Value; use std::sync::Arc; use tauri::{AppHandle, State}; use tokio::fs; @@ -24,9 +26,10 @@ pub(crate) async fn vpn_status<'a>( pub(crate) async fn vpn_connect<'a>( server: String, cookie: String, + user_agent: String, client: State<'a, Arc>, ) -> Result<(), ServerApiError> { - client.connect(server, cookie).await + client.connect(server, cookie, user_agent).await } #[tauri::command] @@ -40,10 +43,10 @@ pub(crate) async fn vpn_disconnect<'a>( pub(crate) async fn saml_login( binding: SamlBinding, request: String, + user_agent: String, clear_cookies: bool, app_handle: AppHandle, ) -> tauri::Result> { - let user_agent = String::from("PAN GlobalProtect"); let params = SamlLoginParams { auth_request: AuthRequest::new(binding, request), user_agent, @@ -71,3 +74,29 @@ pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Resul fs::write(openssl_conf_path, openssl_conf).await?; Ok(()) } + +#[tauri::command] +pub(crate) async fn store_get<'a>( + hint: KeyHint<'_>, + app_storage: State<'_, AppStorage<'_>>, +) -> Result, ()> { + Ok(app_storage.get(hint)) +} + +#[tauri::command] +pub(crate) fn store_set( + hint: KeyHint, + value: Value, + app_storage: State<'_, AppStorage>, +) -> Result<(), tauri_plugin_store::Error> { + app_storage.set(hint, &value)?; + Ok(()) +} + +#[tauri::command] +pub(crate) fn store_save( + app_storage: State<'_, AppStorage>, +) -> Result<(), tauri_plugin_store::Error> { + app_storage.save()?; + Ok(()) +} diff --git a/gpgui/src-tauri/src/crypto.rs b/gpgui/src-tauri/src/crypto.rs new file mode 100644 index 0000000..7089b67 --- /dev/null +++ b/gpgui/src-tauri/src/crypto.rs @@ -0,0 +1,46 @@ +use aes_gcm::{ + aead::{consts::U12, Aead, OsRng}, + AeadCore, Aes256Gcm, Key, KeyInit, Nonce, +}; +use keyring::Entry; + +const SERVICE_NAME: &str = "GlobalProtect-openconnect"; +const ENTRY_KEY: &str = "master-key"; + +fn get_master_key() -> Result, anyhow::Error> { + let key_entry = Entry::new(SERVICE_NAME, ENTRY_KEY)?; + + if let Ok(key) = key_entry.get_password() { + let key = hex::decode(key)?; + return Ok(Key::::clone_from_slice(&key)); + } + + let key = Aes256Gcm::generate_key(OsRng); + let encoded_key = hex::encode(key); + + key_entry.set_password(&encoded_key)?; + + Ok(key) +} + +pub(crate) fn encrypt(data: &str) -> Result { + let master_key = get_master_key()?; + let cipher = Aes256Gcm::new(&master_key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let cipher_text = cipher.encrypt(&nonce, data.as_bytes())?; + + let mut encrypted = nonce.to_vec(); + encrypted.extend_from_slice(&cipher_text); + Ok(hex::encode(encrypted)) +} + +pub(crate) fn decrypt(encrypted: &str) -> Result { + let master_key = get_master_key()?; + let encrypted = hex::decode(encrypted)?; + let nonce = Nonce::::from_slice(&encrypted[..12]); + let cipher_text = &encrypted[12..]; + let cipher = Aes256Gcm::new(&master_key); + let plain_text = cipher.decrypt(nonce, cipher_text)?; + + String::from_utf8(plain_text).map_err(|err| err.into()) +} diff --git a/gpgui/src-tauri/src/main.rs b/gpgui/src-tauri/src/main.rs index 7ba0387..0b23c2f 100644 --- a/gpgui/src-tauri/src/main.rs +++ b/gpgui/src-tauri/src/main.rs @@ -2,93 +2,26 @@ all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] - -use crate::utils::get_openssl_conf_path; -use env_logger::Env; -use gpcommon::{Client, ClientStatus, VpnStatus}; -use log::{info, warn}; -use serde::Serialize; -use std::{path::PathBuf, sync::Arc}; -use tauri::{Manager, Wry}; use tauri_plugin_log::LogTarget; -use tauri_plugin_store::{with_store, StoreCollection}; mod auth; mod commands; +mod crypto; +mod settings; +mod setup; +mod storage; mod utils; -#[derive(Debug, Clone, Serialize)] -struct VpnStatusPayload { - status: VpnStatus, -} - -fn setup(app: &mut tauri::App) -> Result<(), Box> { - let client = Arc::new(Client::default()); - let client_clone = client.clone(); - let app_handle = app.handle(); - - let stores = app.state::>(); - let path = PathBuf::from(".settings.dat"); - let _ = with_store(app_handle.clone(), stores, path, |store| { - let settings_data = store.get("SETTINGS_DATA"); - let custom_openssl = settings_data.map_or(false, |data| { - data["customOpenSSL"].as_bool().unwrap_or(false) - }); - - if custom_openssl { - info!("Using custom OpenSSL config"); - let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string(); - std::env::set_var("OPENSSL_CONF", openssl_conf); - } - Ok(()) - }); - - tauri::async_runtime::spawn(async move { - client_clone.subscribe_status(move |client_status| match client_status { - ClientStatus::Vpn(vpn_status) => { - let payload = VpnStatusPayload { status: vpn_status }; - if let Err(err) = app_handle.emit_all("vpn-status-received", payload) { - warn!("Error emitting event: {}", err); - } - } - ClientStatus::Service(is_online) => { - if let Err(err) = app_handle.emit_all("service-status-changed", is_online) { - warn!("Error emitting event: {}", err); - } - } - }); - - let _ = client_clone.run().await; - }); - - app.manage(client); - - if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { - if desktop == "KDE" { - if let Some(main_window) = app.get_window("main") { - let _ = main_window.set_decorations(false); - } - } - } - - Ok(()) -} - fn main() { - // env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); tauri::Builder::default() .plugin( tauri_plugin_log::Builder::default() - .targets([ - LogTarget::LogDir, - LogTarget::Stdout, /*LogTarget::Webview*/ - ]) + .targets([LogTarget::LogDir, LogTarget::Stdout]) .level(log::LevelFilter::Info) - .with_colors(Default::default()) .build(), ) .plugin(tauri_plugin_store::Builder::default().build()) - .setup(setup) + .setup(setup::setup) .invoke_handler(tauri::generate_handler![ commands::service_online, commands::vpn_status, @@ -98,6 +31,9 @@ fn main() { commands::os_version, commands::openssl_config, commands::update_openssl_config, + commands::store_get, + commands::store_set, + commands::store_save, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/gpgui/src-tauri/src/settings.rs b/gpgui/src-tauri/src/settings.rs new file mode 100644 index 0000000..e69af13 --- /dev/null +++ b/gpgui/src-tauri/src/settings.rs @@ -0,0 +1,19 @@ +use crate::storage::{AppStorage, KeyHint}; +use serde::Deserialize; +use tauri::{AppHandle, Manager}; + +const STORAGE_KEY: &str = "SETTINGS_DATA"; + +#[derive(Debug, Deserialize)] +struct Settings { + #[serde(rename = "customOpenSSL")] + custom_openssl: bool, +} + +pub(crate) fn is_custom_openssl_enabled(app_handle: &AppHandle) -> bool { + let app_storage = app_handle.state::(); + let hint = KeyHint::new(STORAGE_KEY, false); + let settings = app_storage.get::(hint); + + settings.map_or(false, |settings| settings.custom_openssl) +} diff --git a/gpgui/src-tauri/src/setup.rs b/gpgui/src-tauri/src/setup.rs new file mode 100644 index 0000000..252738f --- /dev/null +++ b/gpgui/src-tauri/src/setup.rs @@ -0,0 +1,81 @@ +use crate::{settings, storage::AppStorage, utils::get_openssl_conf_path}; +use gpcommon::{Client, ClientStatus, VpnStatus}; +use log::{info, warn}; +use serde::Serialize; +use std::sync::Arc; +use tauri::Manager; + +#[derive(Debug, Clone, Serialize)] +struct VpnStatusPayload { + status: VpnStatus, +} + +fn setup_window(app: &mut tauri::App) -> Result<(), Box> { + let desktop = std::env::var("XDG_CURRENT_DESKTOP")?; + if desktop == "KDE" { + if let Some(main_window) = app.get_window("main") { + let _ = main_window.set_decorations(false); + } + } + + Ok(()) +} + +fn setup_app_storage(app: &mut tauri::App) -> Result<(), Box> { + let app_handle = app.app_handle(); + let app_storage = AppStorage::new(app_handle); + + app.manage(app_storage); + + Ok(()) +} + +fn setup_app_env(app: &mut tauri::App) -> Result<(), Box> { + let app_handle = app.app_handle(); + let use_custom_openssl = settings::is_custom_openssl_enabled(&app_handle); + + if use_custom_openssl { + info!("Using custom OpenSSL config"); + + let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string(); + std::env::set_var("OPENSSL_CONF", openssl_conf); + } + + Ok(()) +} + +fn setup_vpn_client(app: &mut tauri::App) -> Result<(), Box> { + let app_handle = app.handle(); + let client = Arc::new(Client::default()); + let client_clone = client.clone(); + + app.manage(client_clone); + + tauri::async_runtime::spawn(async move { + client.subscribe_status(move |client_status| match client_status { + ClientStatus::Vpn(vpn_status) => { + let payload = VpnStatusPayload { status: vpn_status }; + if let Err(err) = app_handle.emit_all("vpn-status-received", payload) { + warn!("Error emitting event: {}", err); + } + } + ClientStatus::Service(is_online) => { + if let Err(err) = app_handle.emit_all("service-status-changed", is_online) { + warn!("Error emitting event: {}", err); + } + } + }); + let _ = client.run().await; + }); + + Ok(()) +} + +pub(crate) fn setup(app: &mut tauri::App) -> Result<(), Box> { + setup_window(app)?; + setup_app_storage(app)?; + setup_app_env(app)?; + setup_vpn_client(app)?; + + Ok(()) +} diff --git a/gpgui/src-tauri/src/storage.rs b/gpgui/src-tauri/src/storage.rs new file mode 100644 index 0000000..6a6847d --- /dev/null +++ b/gpgui/src-tauri/src/storage.rs @@ -0,0 +1,87 @@ +use crate::crypto::{decrypt, encrypt}; +use log::warn; +use serde::{de::DeserializeOwned, Deserialize}; +use std::fmt::Debug; +use tauri::{AppHandle, Manager, Wry}; +use tauri_plugin_store::{with_store, Error, StoreCollection}; + +const STORE_PATH: &str = ".data.json"; + +#[derive(Debug, Deserialize)] +pub(crate) struct KeyHint<'a> { + key: &'a str, + encrypted: bool, +} + +impl<'a> KeyHint<'a> { + pub(crate) fn new(key: &'a str, encrypted: bool) -> Self { + Self { key, encrypted } + } +} + +pub(crate) struct AppStorage<'a> { + path: &'a str, + app_handle: AppHandle, +} + +impl AppStorage<'_> { + pub(crate) fn new(app_handle: AppHandle) -> Self { + Self { + path: STORE_PATH, + app_handle, + } + } + + pub fn get(&self, hint: KeyHint) -> Option { + let stores = self.app_handle.state::>(); + with_store(self.app_handle.clone(), stores, self.path, |store| { + store + .get(hint.key) + .ok_or_else(|| Error::Deserialize("Value not found".into())) + .and_then(|value| { + if !hint.encrypted { + return Ok(serde_json::from_value::(value.clone())?); + } + + let value = value + .as_str() + .ok_or_else(|| Error::Deserialize("Value is not a string".into()))?; + let value = decrypt(value).map_err(|err| { + Error::Deserialize(format!("Failed to decrypt value: {}", err).into()) + })?; + + Ok(serde_json::from_str::(&value)?) + }) + }) + .map_err(|err| warn!("Error getting value: {:?}", err)) + .ok() + } + + pub fn set(&self, hint: KeyHint, value: &T) -> Result<(), Error> { + let stores = self.app_handle.state::>(); + + with_store(self.app_handle.clone(), stores, self.path, |store| { + let value = if hint.encrypted { + let json_str = serde_json::to_string(value)?; + let encrypted = encrypt(&json_str).map_err(|err| { + Error::Serialize(format!("Failed to encrypt value: {}", err).into()) + })?; + serde_json::to_value(encrypted)? + } else { + serde_json::to_value(value)? + }; + + store.insert(hint.key.to_string(), value)?; + Ok(()) + }) + } + + pub fn save(&self) -> Result<(), Error> { + let stores = self.app_handle.state::>(); + + with_store(self.app_handle.clone(), stores, self.path, |store| { + store.save()?; + Ok(()) + }) + } +} diff --git a/gpgui/src/atoms/gateway.ts b/gpgui/src/atoms/gateway.ts index fe5ba88..a82fc20 100644 --- a/gpgui/src/atoms/gateway.ts +++ b/gpgui/src/atoms/gateway.ts @@ -14,7 +14,11 @@ export const portalGatewaysAtom = atom((get) => { }); export const selectedGatewayAtom = atom( - (get) => get(currentPortalDataAtom).selectedGateway, + (get) => { + const { selectedGateway } = get(currentPortalDataAtom); + const gateways = get(portalGatewaysAtom); + return gateways.find(({ name }) => name === selectedGateway); + }, async (get, set, update: string) => { const portalData = get(currentPortalDataAtom); await set(updatePortalDataAtom, { ...portalData, selectedGateway: update }); diff --git a/gpgui/src/atoms/loginPortal.ts b/gpgui/src/atoms/loginPortal.ts index 6d24619..f818312 100644 --- a/gpgui/src/atoms/loginPortal.ts +++ b/gpgui/src/atoms/loginPortal.ts @@ -62,7 +62,7 @@ export const loginPortalAtom = atom( } // Here, we have got the portal config successfully, refresh the cached portal data - const previousSelectedGateway = get(selectedGatewayAtom); + const previousSelectedGateway = get(selectedGatewayAtom)?.name; const selectedGateway = gateways.find( ({ name }) => name === previousSelectedGateway ); diff --git a/gpgui/src/atoms/portal.ts b/gpgui/src/atoms/portal.ts index eb63572..5f30dfa 100644 --- a/gpgui/src/atoms/portal.ts +++ b/gpgui/src/atoms/portal.ts @@ -1,7 +1,7 @@ import { atom } from "jotai"; import { atomWithDefault } from "jotai/utils"; import { PortalCredential } from "../services/portalService"; -import { atomWithTauriStorage } from "../services/storeService"; +import { atomWithTauriStorage } from "../services/storageService"; import { unwrap } from "./unwrap"; export type GatewayData = { @@ -31,7 +31,11 @@ const DEFAULT_APP_DATA: AppData = { clearCookies: true, }; -export const appDataAtom = atomWithTauriStorage("APP_DATA", DEFAULT_APP_DATA); +const keyHint = { + key: "APP_DATA", + encrypted: true, +}; +export const appDataAtom = atomWithTauriStorage(keyHint, DEFAULT_APP_DATA); const unwrappedAppDataAtom = atom( (get) => get(unwrap(appDataAtom)) || DEFAULT_APP_DATA ); @@ -51,6 +55,11 @@ export const currentPortalDataAtom = atom((get) => { return portalData || { address: portalAddress, gateways: [] }; }); +export const allPortalsAtom = atom((get) => { + const { portals } = get(unwrappedAppDataAtom); + return portals.map(({ address }) => address); +}); + export const updatePortalDataAtom = atom( null, async (get, set, update: PortalData) => { diff --git a/gpgui/src/atoms/settings.ts b/gpgui/src/atoms/settings.ts index f11d8da..7ea0449 100644 --- a/gpgui/src/atoms/settings.ts +++ b/gpgui/src/atoms/settings.ts @@ -5,7 +5,7 @@ import settingsService, { DEFAULT_SETTINGS_DATA, SETTINGS_DATA, } from "../services/settingsService"; -import { atomWithTauriStorage } from "../services/storeService"; +import { atomWithTauriStorage } from "../services/storageService"; import { unwrap } from "./unwrap"; const settingsDataAtom = atomWithTauriStorage( diff --git a/gpgui/src/atoms/status.ts b/gpgui/src/atoms/status.ts index cc6331b..2a73c9e 100644 --- a/gpgui/src/atoms/status.ts +++ b/gpgui/src/atoms/status.ts @@ -4,6 +4,7 @@ import vpnService from "../services/vpnService"; import { selectedGatewayAtom, switchGatewayAtom } from "./gateway"; import { notifyErrorAtom, notifySuccessAtom } from "./notification"; import { unwrap } from "./unwrap"; +import { portalAddressAtom } from "./portal"; export type Status = | "disconnected" @@ -75,14 +76,16 @@ export const statusTextAtom = atom((get) => { if (status === "connected") { const selectedGateway = get(selectedGatewayAtom); - return selectedGateway - ? `Gateway: ${selectedGateway}` - : statusTextMap[status]; + const portalAddress = get(portalAddressAtom); + + return selectedGateway?.address === portalAddress + ? statusTextMap[status] + : selectedGateway?.address!; } if (switchingGateway) { const selectedGateway = get(selectedGatewayAtom); - return `Switching to ${selectedGateway}`; + return `Switching to ${selectedGateway?.name}`; } return statusTextMap[status]; diff --git a/gpgui/src/components/AppShell/index.tsx b/gpgui/src/components/AppShell/index.tsx index 84e181d..0873263 100644 --- a/gpgui/src/components/AppShell/index.tsx +++ b/gpgui/src/components/AppShell/index.tsx @@ -1,13 +1,8 @@ -import { - Box, - CssBaseline, - ThemeProvider, - createTheme, - useMediaQuery, -} from "@mui/material"; -import React, { Suspense, useMemo } from "react"; +import { Box, CssBaseline, ThemeProvider } from "@mui/material"; +import React, { Suspense } from "react"; import { createRoot } from "react-dom/client"; import "./styles.css"; +import useGlobalTheme from "./useGlobalTheme"; function Loading() { console.warn("Loading rendered"); @@ -27,16 +22,7 @@ function Loading() { } function AppShell({ children }: { children: React.ReactNode }) { - const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); - const theme = useMemo( - () => - createTheme({ - palette: { - mode: prefersDarkMode ? "dark" : "light", - }, - }), - [prefersDarkMode] - ); + const theme = useGlobalTheme(); return ( diff --git a/gpgui/src/components/AppShell/useGlobalTheme.ts b/gpgui/src/components/AppShell/useGlobalTheme.ts new file mode 100644 index 0000000..4627be7 --- /dev/null +++ b/gpgui/src/components/AppShell/useGlobalTheme.ts @@ -0,0 +1,31 @@ +import { createTheme, useMediaQuery } from "@mui/material"; +import { useMemo } from "react"; + +export default function useGlobalTheme() { + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + return useMemo( + () => + createTheme({ + palette: { + mode: prefersDarkMode ? "dark" : "light", + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + }, + }), + [prefersDarkMode] + ); +} diff --git a/gpgui/src/components/ConnectForm/PasswordAuth.tsx b/gpgui/src/components/ConnectForm/PasswordAuth.tsx index 905e186..a66892e 100644 --- a/gpgui/src/components/ConnectForm/PasswordAuth.tsx +++ b/gpgui/src/components/ConnectForm/PasswordAuth.tsx @@ -57,7 +57,7 @@ export default function PasswordAuth() { @@ -66,7 +66,6 @@ export default function PasswordAuth() { type="submit" loading={loading} disabled={loading} - sx={{ flex: 1, textTransform: "none" }} > Login diff --git a/gpgui/src/components/ConnectForm/PortalForm.tsx b/gpgui/src/components/ConnectForm/PortalForm.tsx index 83ebec0..a77bcc2 100644 --- a/gpgui/src/components/ConnectForm/PortalForm.tsx +++ b/gpgui/src/components/ConnectForm/PortalForm.tsx @@ -1,12 +1,12 @@ -import { Button, TextField } from "@mui/material"; +import { Autocomplete, Button, TextField } from "@mui/material"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { ChangeEvent } from "react"; +import { ChangeEvent, useState } from "react"; import { cancelConnectPortalAtom, connectPortalAtom, } from "../../atoms/connectPortal"; import { switchGatewayAtom } from "../../atoms/gateway"; -import { portalAddressAtom } from "../../atoms/portal"; +import { allPortalsAtom, portalAddressAtom } from "../../atoms/portal"; import { backgroundServiceStartedAtom, isProcessingAtom, @@ -26,6 +26,7 @@ function normalizePortalAddress(input: string) { export default function PortalForm() { const backgroundServiceStarted = useAtomValue(backgroundServiceStartedAtom); + const allPortals = useAtomValue(allPortalsAtom); const [portalAddress, setPortalAddress] = useAtom(portalAddressAtom); // Use useAtom instead of useSetAtom, otherwise the onMount of the atom is not triggered const [, connectPortal] = useAtom(connectPortalAtom); @@ -35,8 +36,10 @@ export default function PortalForm() { const disconnectVpn = useSetAtom(disconnectVpnAtom); const switchingGateway = useAtomValue(switchGatewayAtom); - function handlePortalAddressChange(e: ChangeEvent) { - setPortalAddress(normalizePortalAddress(e.target.value)); + const readOnly = status !== "disconnected" || switchingGateway; + + function handlePortalAddressChange(e: unknown, value: string) { + setPortalAddress(normalizePortalAddress(value)); } function handleSubmit(e: ChangeEvent) { @@ -46,16 +49,35 @@ export default function PortalForm() { return (
- ( + + )} /> {status === "disconnected" && !switchingGateway && ( @@ -64,7 +86,6 @@ export default function PortalForm() { type="submit" variant="contained" disabled={!backgroundServiceStarted} - sx={{ textTransform: "none" }} > Connect @@ -81,19 +102,13 @@ export default function PortalForm() { switchingGateway } onClick={cancelConnectPortal} - sx={{ textTransform: "none" }} > Cancel )} {status === "connected" && ( - )} diff --git a/gpgui/src/components/ConnectionStatus/StatusIcon.tsx b/gpgui/src/components/ConnectionStatus/StatusIcon.tsx index cb761c0..e215cce 100644 --- a/gpgui/src/components/ConnectionStatus/StatusIcon.tsx +++ b/gpgui/src/components/ConnectionStatus/StatusIcon.tsx @@ -1,7 +1,18 @@ import { GppBad, VerifiedUser as VerifiedIcon } from "@mui/icons-material"; -import { Box, CircularProgress, styled, useTheme } from "@mui/material"; -import { useAtomValue } from "jotai"; +import { + Box, + Button, + CircularProgress, + Tooltip, + styled, + useTheme, +} from "@mui/material"; +import { useAtomValue, useSetAtom } from "jotai"; import { BeatLoader } from "react-spinners"; +import { + openGatewaySwitcherAtom, + selectedGatewayAtom, +} from "../../atoms/gateway"; import { isProcessingAtom, statusAtom } from "../../atoms/status"; function useStatusColor() { @@ -59,11 +70,48 @@ function ProcessingIcon() { return ; } -const ConnectedIcon = styled(VerifiedIcon)(({ theme }) => ({ - position: "relative", - fontSize: 80, - color: theme.palette.success.main, -})); +const ConnectedIcon = () => { + const selectedGateway = useAtomValue(selectedGatewayAtom); + const openGatewaySwitcher = useSetAtom(openGatewaySwitcherAtom); + + return ( + + theme.palette.success.main, + }} + /> + + + + + ); +}; const IconContainer = styled(Box)(({ theme }) => theme.unstable_sx({ diff --git a/gpgui/src/components/GatewaySwitcher/index.tsx b/gpgui/src/components/GatewaySwitcher/index.tsx index 9722510..d5b30c5 100644 --- a/gpgui/src/components/GatewaySwitcher/index.tsx +++ b/gpgui/src/components/GatewaySwitcher/index.tsx @@ -20,7 +20,7 @@ export default function GatewaySwitcher() { gatewaySwitcherVisibleAtom ); const gateways = useAtomValue(portalGatewaysAtom); - const selectedGateway = useAtomValue(selectedGatewayAtom); + const selectedGateway = useAtomValue(selectedGatewayAtom)?.name; const switchGateway = useSetAtom(switchGatewayAtom); const handleClose = () => { diff --git a/gpgui/src/components/settings/index.tsx b/gpgui/src/components/settings/index.tsx index c96f2cb..b3ffeb9 100644 --- a/gpgui/src/components/settings/index.tsx +++ b/gpgui/src/components/settings/index.tsx @@ -43,14 +43,12 @@ export default function SettingsPanel() { value="simulation" icon={} iconPosition="start" - sx={{ textTransform: "none" }} /> } iconPosition="start" - sx={{ textTransform: "none" }} /> @@ -61,12 +59,8 @@ export default function SettingsPanel() { - - + + diff --git a/gpgui/src/pages/main.tsx b/gpgui/src/pages/main.tsx index 040b122..b5fc846 100644 --- a/gpgui/src/pages/main.tsx +++ b/gpgui/src/pages/main.tsx @@ -1,4 +1,6 @@ import { Box } from "@mui/material"; +import { useAtomValue } from "jotai"; +import { appDataAtom } from "../atoms/portal"; import { renderToRoot } from "../components/AppShell"; import ConnectForm from "../components/ConnectForm"; import ConnectionStatus from "../components/ConnectionStatus"; @@ -8,6 +10,10 @@ import MainMenu from "../components/MainMenu"; import Notification from "../components/Notification"; export default function App() { + // Use the this atom to trigger loading data from the storage + // And render the loading indicator + useAtomValue(appDataAtom); + return ( diff --git a/gpgui/src/services/authService.ts b/gpgui/src/services/authService.ts index 753dfa6..cebdc7d 100644 --- a/gpgui/src/services/authService.ts +++ b/gpgui/src/services/authService.ts @@ -1,5 +1,6 @@ import { emit, listen } from "@tauri-apps/api/event"; import invokeCommand from "../utils/invokeCommand"; +import settingsService from "./settingsService"; export type AuthData = { username: string; @@ -30,9 +31,12 @@ class AuthService { // binding: "POST" | "REDIRECT" async samlLogin(binding: string, request: string, clearCookies: boolean) { + const { userAgent } = await settingsService.getSimulation(); + return invokeCommand("saml_login", { binding, request, + userAgent: `${userAgent} ${navigator.userAgent}`, clearCookies, }); } diff --git a/gpgui/src/services/gatewayService.ts b/gpgui/src/services/gatewayService.ts index 81bed65..89649db 100644 --- a/gpgui/src/services/gatewayService.ts +++ b/gpgui/src/services/gatewayService.ts @@ -1,5 +1,6 @@ import { Body, ResponseType, fetch } from "@tauri-apps/api/http"; import { parseXml } from "../utils/parseXml"; +import settingsService from "./settingsService"; type LoginParams = { user: string; @@ -15,6 +16,9 @@ class GatewayService { throw new Error("Gateway address is required"); } + const { userAgent, clientOS, osVersion } = + await settingsService.getSimulation(); + const loginUrl = `https://${gateway}/ssl-vpn/login.esp`; const body = Body.form({ prot: "https:", @@ -25,8 +29,8 @@ class GatewayService { direct: "yes", "ipv6-support": "yes", clientVer: "4100", - clientos: "Linux", - "os-version": "Linux", + clientos: clientOS, + "os-version": osVersion, server: gateway, user, passwd: passwd || "", @@ -38,7 +42,7 @@ class GatewayService { const response = await fetch(loginUrl, { method: "POST", headers: { - "User-Agent": "PAN GlobalProtect", + "User-Agent": userAgent, }, responseType: ResponseType.Text, body, diff --git a/gpgui/src/services/portalService.ts b/gpgui/src/services/portalService.ts index a3dcc28..57e871f 100644 --- a/gpgui/src/services/portalService.ts +++ b/gpgui/src/services/portalService.ts @@ -2,6 +2,7 @@ import { Body, ResponseType, fetch } from "@tauri-apps/api/http"; import ErrorWithTitle from "../utils/ErrorWithTitle"; import { parseXml } from "../utils/parseXml"; import { Gateway } from "./types"; +import settingsService from "./settingsService"; export type SamlPrelogin = { isSamlAuth: true; @@ -37,12 +38,15 @@ export type PortalCredential = { class PortalService { async prelogin(portal: string): Promise { const preloginUrl = `https://${portal}/global-protect/prelogin.esp`; + const { userAgent, clientOS, osVersion } = + await settingsService.getSimulation(); + let response; try { response = await fetch(preloginUrl, { method: "POST", headers: { - "User-Agent": "PAN GlobalProtect", + "User-Agent": userAgent, }, responseType: ResponseType.Text, query: { @@ -51,8 +55,8 @@ class PortalService { body: Body.form({ tmp: "tmp", clientVer: "4100", - clientos: "Linux", - "os-version": "Linux", + clientos: clientOS, + "os-version": osVersion, "ipv6-support": "yes", "default-browser": "0", "cas-support": "yes", @@ -118,6 +122,9 @@ class PortalService { } async fetchConfig(portal: string, params: PortalCredential) { + const { userAgent, clientOS, osVersion, clientVersion } = + await settingsService.getSimulation(); + const { user, passwd, @@ -132,12 +139,12 @@ class PortalService { inputStr: "", jnlpReady: "jnlpReady", computer: "Linux", // TODO - clientos: "Linux", + clientos: clientOS, ok: "Login", direct: "yes", clientVer: "4100", - "os-version": "Linux", - clientgpversion: "6.0.1-19", + "os-version": osVersion, + clientgpversion: clientVersion, "ipv6-support": "yes", server: portal, host: portal, @@ -151,7 +158,7 @@ class PortalService { const response = await fetch(configUrl, { method: "POST", headers: { - "User-Agent": "PAN GlobalProtect", + "User-Agent": userAgent, }, responseType: ResponseType.Text, body, @@ -166,8 +173,6 @@ class PortalService { } private parsePortalConfigResponse(response: string): PortalConfig { - // console.log(response); - const result = parseXml(response); const gateways = result.all("gateways list > entry").map((entry) => { const address = entry.attr("name"); diff --git a/gpgui/src/services/settingsService.ts b/gpgui/src/services/settingsService.ts index 534fa80..063fb88 100644 --- a/gpgui/src/services/settingsService.ts +++ b/gpgui/src/services/settingsService.ts @@ -1,6 +1,6 @@ import { UserAttentionType, WebviewWindow } from "@tauri-apps/api/window"; import invokeCommand from "../utils/invokeCommand"; -import { appStore } from "./storeService"; +import { appStorage } from "./storageService"; export type TabValue = "simulation" | "openssl"; const SETTINGS_WINDOW_LABEL = "settings"; @@ -68,9 +68,10 @@ export const DEFAULT_SETTINGS_DATA: SettingsData = { customOpenSSL: false, }; -async function getSimulationSettings(): Promise { +async function getSimulation(): Promise { const { clientOS, osVersion, clientVersion } = - (await appStore.get(SETTINGS_DATA)) || DEFAULT_SETTINGS_DATA; + (await appStorage.get(SETTINGS_DATA)) || + DEFAULT_SETTINGS_DATA; const currentOsVersion = await getCurrentOsVersion(); return { @@ -81,8 +82,8 @@ async function getSimulationSettings(): Promise { clientVersion ), clientOS, - osVersion, - clientVersion, + osVersion: determineOsVersion(clientOS, osVersion, currentOsVersion), + clientVersion: clientVersion || DEFAULT_CLIENT_VERSION, }; } @@ -131,7 +132,7 @@ export default { openSettings, closeSettings, getCurrentOsVersion, - getSimulationSettings, + getSimulation, buildUserAgent, determineOsVersion, getOpenSSLConfig, diff --git a/gpgui/src/services/storageService.ts b/gpgui/src/services/storageService.ts new file mode 100644 index 0000000..a52ccfa --- /dev/null +++ b/gpgui/src/services/storageService.ts @@ -0,0 +1,70 @@ +import { atom } from "jotai"; +import { RESET, atomWithDefault } from "jotai/utils"; +import invokeCommand from "../utils/invokeCommand"; + +type SetStateActionWithReset = + | T + | typeof RESET + | ((prev: T) => T | typeof RESET); + +type KeyHint = + | { + key: string; + encrypted: boolean; + } + | string; + +type AppStorage = { + get: (key: KeyHint) => Promise; + set: (key: KeyHint, value: T) => Promise; + save: () => Promise; +}; + +export const appStorage: AppStorage = { + get: async (key) => { + const hint = typeof key === "string" ? { key, encrypted: false } : key; + return invokeCommand("store_get", { hint }); + }, + set: async (key, value) => { + const hint = typeof key === "string" ? { key, encrypted: false } : key; + return invokeCommand("store_set", { hint, value }); + }, + save: async () => { + return invokeCommand("store_save"); + }, +}; + +export function atomWithTauriStorage(key: KeyHint, initialValue: T) { + const baseAtom = atomWithDefault>(async () => { + const storedValue = await appStorage.get(key); + if (!storedValue) { + return initialValue; + } + return storedValue; + }); + + const anAtom = atom( + (get) => get(baseAtom), + async (get, set, update: SetStateActionWithReset) => { + const value = await get(baseAtom); + let newValue: T | typeof RESET; + if (typeof update === "function") { + newValue = (update as (prev: T) => T | typeof RESET)(value); + } else { + newValue = update as T | typeof RESET; + } + + if (newValue === RESET) { + set(baseAtom, initialValue); + await appStorage.set(key, initialValue); + } else { + set(baseAtom, newValue); + await appStorage.set(key, newValue); + } + + await appStorage.save(); + } + ); + + return anAtom; +} diff --git a/gpgui/src/services/storeService.ts b/gpgui/src/services/storeService.ts deleted file mode 100644 index fc9a771..0000000 --- a/gpgui/src/services/storeService.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { atom } from "jotai"; -import { RESET, atomWithDefault } from "jotai/utils"; -import { Store } from "tauri-plugin-store-api"; - -type SetStateActionWithReset = - | T - | typeof RESET - | ((prev: T) => T | typeof RESET); - -export const appStore = new Store(".settings.dat"); - -export function atomWithTauriStorage(key: string, initialValue: T) { - const baseAtom = atomWithDefault>(async () => { - const storedValue = await appStore.get(key); - if (!storedValue) { - return initialValue; - } - return storedValue; - }); - - const anAtom = atom( - (get) => get(baseAtom), - async (get, set, update: SetStateActionWithReset) => { - const value = await get(baseAtom); - let newValue: T | typeof RESET; - if (typeof update === "function") { - newValue = (update as (prev: T) => T | typeof RESET)(value); - } else { - newValue = update as T | typeof RESET; - } - - if (newValue === RESET) { - set(baseAtom, initialValue); - await appStore.set(key, initialValue); - } else { - set(baseAtom, newValue); - await appStore.set(key, newValue); - } - - await appStore.save(); - } - ); - - return anAtom; -} diff --git a/gpgui/src/services/vpnService.ts b/gpgui/src/services/vpnService.ts index bcaa132..47dbfde 100644 --- a/gpgui/src/services/vpnService.ts +++ b/gpgui/src/services/vpnService.ts @@ -1,5 +1,6 @@ import { Event, listen } from "@tauri-apps/api/event"; import invokeCommand from "../utils/invokeCommand"; +import settingsService from "./settingsService"; type VpnStatus = "disconnected" | "connecting" | "connected" | "disconnecting"; type VpnStatusCallback = (status: VpnStatus) => void; @@ -64,7 +65,8 @@ class VpnService { } async connect(server: string, cookie: string) { - return invokeCommand("vpn_connect", { server, cookie }); + const { userAgent } = await settingsService.getSimulation(); + return invokeCommand("vpn_connect", { server, cookie, userAgent }); } async disconnect() { diff --git a/gpservice/Cargo.toml b/gpservice/Cargo.toml index b55bc49..80831c5 100644 --- a/gpservice/Cargo.toml +++ b/gpservice/Cargo.toml @@ -8,8 +8,9 @@ edition = "2021" [dependencies] gpcommon = { path = "../gpcommon" } tokio = { version = "1", features = ["full"] } -env_logger = "0.10" log = "0.4" +fern = "0.6" +humantime = "2.1" # warp = "0.3" # aes-gcm = "0.10" # procfs = "0.15" diff --git a/gpservice/src/main.rs b/gpservice/src/main.rs index 0bb3d53..ea083f9 100644 --- a/gpservice/src/main.rs +++ b/gpservice/src/main.rs @@ -1,9 +1,8 @@ include!(concat!(env!("OUT_DIR"), "/client_hash.rs")); -// use aes_gcm::{aead::OsRng, Aes256Gcm, KeyInit}; use gpcommon::{server, SOCKET_PATH}; -use env_logger::Env; use log::error; +use std::fs::File; use tokio::signal; // static mut HTTP_PORT: u16 = 0; @@ -96,10 +95,10 @@ use tokio::signal; // println!("Shutting down http server"); // } +const LOG_FILE: &str = "/var/log/gpservice.log"; + #[tokio::main] async fn main() -> Result<(), Box> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - // println!("{GPCLIENT_HASH}"); // unsafe { @@ -110,6 +109,22 @@ async fn main() -> Result<(), Box> { // start_http_server().await; // server::start().await + let log_file = File::create(LOG_FILE)?; + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}] {}", + humantime::format_rfc3339_millis(std::time::SystemTime::now()), + record.level(), + record.target(), + message + )) + }) + .level(log::LevelFilter::Info) + .chain(std::io::stdout()) + .chain(log_file) + .apply()?; + if let Err(err) = server::run(SOCKET_PATH, signal::ctrl_c()).await { error!("Error running server: {}", err); }