refactor: encrypt the sensitive data

This commit is contained in:
Kevin Yue 2023-07-22 07:33:53 +08:00
parent bf96a88e21
commit 601f422863
40 changed files with 1274 additions and 275 deletions

View File

@ -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",

695
Cargo.lock generated
View File

@ -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",
]

View File

@ -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> {

View File

@ -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());
}

View File

@ -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

View File

@ -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<dyn std::error::Error>> {
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();

View File

@ -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)
{

View File

@ -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;

View File

@ -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",

28
gpgui/pnpm-lock.yaml generated
View File

@ -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

View File

@ -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

View File

@ -133,7 +133,7 @@ fn build_window(app_handle: &AppHandle, ua: &str) -> tauri::Result<Window> {
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)

View File

@ -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<Client>>,
) -> 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<Option<AuthData>> {
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<Option<Value>, ()> {
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(())
}

View File

@ -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<Key<Aes256Gcm>, 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::<Aes256Gcm>::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<String, anyhow::Error> {
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<String, anyhow::Error> {
let master_key = get_master_key()?;
let encrypted = hex::decode(encrypted)?;
let nonce = Nonce::<U12>::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())
}

View File

@ -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<dyn std::error::Error>> {
let client = Arc::new(Client::default());
let client_clone = client.clone();
let app_handle = app.handle();
let stores = app.state::<StoreCollection<Wry>>();
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");

View File

@ -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::<AppStorage>();
let hint = KeyHint::new(STORAGE_KEY, false);
let settings = app_storage.get::<Settings>(hint);
settings.map_or(false, |settings| settings.custom_openssl)
}

View File

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
setup_window(app)?;
setup_app_storage(app)?;
setup_app_env(app)?;
setup_vpn_client(app)?;
Ok(())
}

View File

@ -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<Wry>,
}
impl AppStorage<'_> {
pub(crate) fn new(app_handle: AppHandle<Wry>) -> Self {
Self {
path: STORE_PATH,
app_handle,
}
}
pub fn get<T: DeserializeOwned + Debug>(&self, hint: KeyHint) -> Option<T> {
let stores = self.app_handle.state::<StoreCollection<Wry>>();
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::<T>(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::<T>(&value)?)
})
})
.map_err(|err| warn!("Error getting value: {:?}", err))
.ok()
}
pub fn set<T: serde::Serialize>(&self, hint: KeyHint, value: &T) -> Result<(), Error> {
let stores = self.app_handle.state::<StoreCollection<Wry>>();
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::<StoreCollection<Wry>>();
with_store(self.app_handle.clone(), stores, self.path, |store| {
store.save()?;
Ok(())
})
}
}

View File

@ -14,7 +14,11 @@ export const portalGatewaysAtom = atom<GatewayData[]>((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 });

View File

@ -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
);

View File

@ -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<PortalData>((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) => {

View File

@ -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(

View File

@ -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<string>((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];

View File

@ -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 (
<React.StrictMode>

View File

@ -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]
);
}

View File

@ -57,7 +57,7 @@ export default function PasswordAuth() {
<Button
variant="outlined"
onClick={cancelPasswordAuth}
sx={{ flex: 1, textTransform: "none" }}
sx={{ flex: 1 }}
>
Cancel
</Button>
@ -66,7 +66,6 @@ export default function PasswordAuth() {
type="submit"
loading={loading}
disabled={loading}
sx={{ flex: 1, textTransform: "none" }}
>
Login
</LoadingButton>

View File

@ -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<HTMLInputElement>) {
setPortalAddress(normalizePortalAddress(e.target.value));
const readOnly = status !== "disconnected" || switchingGateway;
function handlePortalAddressChange(e: unknown, value: string) {
setPortalAddress(normalizePortalAddress(value));
}
function handleSubmit(e: ChangeEvent<HTMLFormElement>) {
@ -46,16 +49,35 @@ export default function PortalForm() {
return (
<form onSubmit={handleSubmit} data-tauri-drag-region>
<TextField
autoFocus
label="Portal address"
placeholder="Hostname or IP address"
fullWidth
<Autocomplete
freeSolo
options={allPortals}
inputValue={portalAddress}
onInputChange={handlePortalAddressChange}
readOnly={readOnly}
forcePopupIcon={!readOnly}
disableClearable
size="small"
value={portalAddress}
onChange={handlePortalAddressChange}
InputProps={{ readOnly: status !== "disconnected" || switchingGateway }}
sx={{ mb: 1 }}
sx={{
mb: 1,
}}
componentsProps={{
paper: {
sx: {
"& .MuiAutocomplete-listbox .MuiAutocomplete-option": {
minHeight: "auto",
},
},
},
}}
renderInput={(params) => (
<TextField
{...params}
autoFocus
label="Portal address"
placeholder="Hostname or IP address"
/>
)}
/>
{status === "disconnected" && !switchingGateway && (
@ -64,7 +86,6 @@ export default function PortalForm() {
type="submit"
variant="contained"
disabled={!backgroundServiceStarted}
sx={{ textTransform: "none" }}
>
Connect
</Button>
@ -81,19 +102,13 @@ export default function PortalForm() {
switchingGateway
}
onClick={cancelConnectPortal}
sx={{ textTransform: "none" }}
>
Cancel
</Button>
)}
{status === "connected" && (
<Button
fullWidth
variant="contained"
onClick={disconnectVpn}
sx={{ textTransform: "none" }}
>
<Button fullWidth variant="contained" onClick={disconnectVpn}>
Disconnect
</Button>
)}

View File

@ -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 <BeatLoader color={theme.palette.info.main} />;
}
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 (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<VerifiedIcon
sx={{
fontSize: 70,
color: (theme) => theme.palette.success.main,
}}
/>
<Tooltip title={`Connected to ${selectedGateway?.name}`}>
<Button
sx={{
position: "relative",
zIndex: 1,
fontSize: "0.75rem",
fontWeight: "bold",
display: "block",
width: 100,
mt: 0.2,
padding: 0.2,
overflow: "hidden",
textOverflow: "ellipsis",
}}
size="small"
color="success"
onClick={openGatewaySwitcher}
>
{selectedGateway?.name}
</Button>
</Tooltip>
</Box>
);
};
const IconContainer = styled(Box)(({ theme }) =>
theme.unstable_sx({

View File

@ -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 = () => {

View File

@ -43,14 +43,12 @@ export default function SettingsPanel() {
value="simulation"
icon={<Devices />}
iconPosition="start"
sx={{ textTransform: "none" }}
/>
<Tab
label="OpenSSL"
value="openssl"
icon={<Https />}
iconPosition="start"
sx={{ textTransform: "none" }}
/>
</TabList>
<Box sx={{ flex: 1 }}>
@ -61,12 +59,8 @@ export default function SettingsPanel() {
</Box>
<Box sx={{ flexShrink: 0, borderTop: 1, borderColor: "divider" }}>
<DialogActions>
<Button sx={{ textTransform: "none" }} onClick={closeWindow}>
Cancel
</Button>
<Button sx={{ textTransform: "none" }} onClick={save}>
Save
</Button>
<Button onClick={closeWindow}>Cancel</Button>
<Button onClick={save}>Save</Button>
</DialogActions>
</Box>
</Box>

View File

@ -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 (
<Box data-tauri-drag-region padding={2} paddingBottom={0}>
<MainMenu />

View File

@ -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<AuthData>("saml_login", {
binding,
request,
userAgent: `${userAgent} ${navigator.userAgent}`,
clearCookies,
});
}

View File

@ -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<string>(loginUrl, {
method: "POST",
headers: {
"User-Agent": "PAN GlobalProtect",
"User-Agent": userAgent,
},
responseType: ResponseType.Text,
body,

View File

@ -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<Prelogin> {
const preloginUrl = `https://${portal}/global-protect/prelogin.esp`;
const { userAgent, clientOS, osVersion } =
await settingsService.getSimulation();
let response;
try {
response = await fetch<string>(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<string>(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");

View File

@ -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<SimulationSettings> {
async function getSimulation(): Promise<SimulationSettings> {
const { clientOS, osVersion, clientVersion } =
(await appStore.get<SettingsData>(SETTINGS_DATA)) || DEFAULT_SETTINGS_DATA;
(await appStorage.get<SettingsData>(SETTINGS_DATA)) ||
DEFAULT_SETTINGS_DATA;
const currentOsVersion = await getCurrentOsVersion();
return {
@ -81,8 +82,8 @@ async function getSimulationSettings(): Promise<SimulationSettings> {
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,

View File

@ -0,0 +1,70 @@
import { atom } from "jotai";
import { RESET, atomWithDefault } from "jotai/utils";
import invokeCommand from "../utils/invokeCommand";
type SetStateActionWithReset<T> =
| T
| typeof RESET
| ((prev: T) => T | typeof RESET);
type KeyHint =
| {
key: string;
encrypted: boolean;
}
| string;
type AppStorage = {
get: <T>(key: KeyHint) => Promise<T | undefined>;
set: <T>(key: KeyHint, value: T) => Promise<void>;
save: () => Promise<void>;
};
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<T>(key: KeyHint, initialValue: T) {
const baseAtom = atomWithDefault<T | Promise<T>>(async () => {
const storedValue = await appStorage.get<T>(key);
if (!storedValue) {
return initialValue;
}
return storedValue;
});
const anAtom = atom(
(get) => get(baseAtom),
async (get, set, update: SetStateActionWithReset<T>) => {
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;
}

View File

@ -1,45 +0,0 @@
import { atom } from "jotai";
import { RESET, atomWithDefault } from "jotai/utils";
import { Store } from "tauri-plugin-store-api";
type SetStateActionWithReset<T> =
| T
| typeof RESET
| ((prev: T) => T | typeof RESET);
export const appStore = new Store(".settings.dat");
export function atomWithTauriStorage<T>(key: string, initialValue: T) {
const baseAtom = atomWithDefault<T | Promise<T>>(async () => {
const storedValue = await appStore.get<T>(key);
if (!storedValue) {
return initialValue;
}
return storedValue;
});
const anAtom = atom(
(get) => get(baseAtom),
async (get, set, update: SetStateActionWithReset<T>) => {
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;
}

View File

@ -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() {

View File

@ -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"

View File

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
// 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);
}