commit 8efd10d765fb646653bd012af896ee19ff952ee6 Author: pointer-to-bios Date: Wed Feb 21 20:46:15 2024 +0800 最小可用版 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9663784 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/southbay.sqlite3 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d4fa322 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,654 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "accounts" +version = "0.1.0" +dependencies = [ + "clap", + "openssl", + "rusqlite", + "tokio", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "2.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e97ec15 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "accounts" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.5.1", features = ["derive"] } +openssl = "0.10.63" +rusqlite = "0.31.0" +tokio = { version = "1.36.0", features = ["net", "io-std", "sync", "time", "signal", "rt", "rt-multi-thread", "macros", "io-util"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..06db775 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# SouthBay Accounts + +南湾用户登录服务。 + +## 登录流程 + +用户只能每3秒发送一次登录请求。每3秒发送超过30次登录请求视为攻击行为,禁止请求的ip`3600`秒。 + +客户端依次发送以下三个请求。 + +每个请求都先发送一个字节表示请求编号。 + +* 编号1`prelogin`:客户端向服务端请求tcp连接并发送私钥加密的`southbayhub login:{id}`, + 服务端回复一个公钥加密的`southbayhub server`、`already logged`或`unexist`,连接断开。 + 服务端账户由`unlinked`状态进入`logining`状态10秒,超过10秒视为登录失败。 + 客户端通过解密字符串验证服务端的有效性,并选择停止登录或继续登录。 +* 编号2`login`:客户端向服务端请求tcp连接并发送私钥加密的`{id}|{passwd}`, + 服务端根据是否超时以及账号和密码的正确性回复公钥加密的`unprelogged`、`logged in`、 + `passwd incorrect`,连接断开。 + 服务端账户进入`logged`状态60秒,超过60秒进入`nonavailable`状态。 +* 编号3`heartbeat`:客户端向服务端请求tcp连接并发送私钥加密的`{id}`后服务端的`logged`状态计时归零, + 处于`unavailable`状态的换回`logged`状态并将计时归零。处于其它状态的什么都不做。 +* 编号4`logout`:客户端向服务端请求tcp连接并发送私钥加密的`{id}`。所有的用户数据项重新写回数据库。 + +## 数据库 + +使用`sqlite3`。 + +### 用户数据项 + +项目|说明 +:-:|:- +`id`|用户唯一标识符,用户名的md5前8字节。 +`name`|用户名,不可以更改。 +`passwd`|用户密码的sha512值字符串的md5值,客户端应发送sha512,数据表应存储sha512值的md5值16进制字符串。 +`email`|用户邮箱。 +`avatar`|用户头像。一个服务端资源id。 + +```sql +CREATE TABLE users ( +id BIGINT PRIMARY KEY, +name VARCHAR(128), +passwd CHAR(32), +email TEXT, +avatar INT +); +``` + +### 资源数据项 + +项目|说明 +:-:|:- +`id`|资源id +`path`|资源路径 + +```sql +CREATE TABLE resource ( +id INT PRIMARY KEY, +path TEXT +); +``` diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..da5c6a9 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,12 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Config { + /// 监听端口 + #[arg(short, long, default_value_t = 8848)] + pub port: usize, + /// 并行数 + #[arg(short, long, default_value_t = 4)] + pub concurrent: usize, +} diff --git a/src/crypt.rs b/src/crypt.rs new file mode 100644 index 0000000..164a526 --- /dev/null +++ b/src/crypt.rs @@ -0,0 +1,39 @@ +use openssl::{error::ErrorStack, pkey::Private, rsa::Rsa}; + +static mut PRIKEY: Option>> = None; + +pub struct Crypt {} + +impl Crypt { + pub fn init() -> Result<(), ErrorStack> { + let prikey = include_str!("id_rsa"); + let prikey = Rsa::private_key_from_pem(prikey.as_bytes())?; + unsafe { PRIKEY = Some(Box::new(prikey)) }; + Ok(()) + } + + pub fn encrypt(text: &str) -> Vec { + let mut ciphertext = vec![0; unsafe { PRIKEY.as_ref().unwrap() }.size() as usize]; + let encrypted_len = unsafe { PRIKEY.as_ref().unwrap() } + .public_encrypt( + text.as_bytes(), + &mut ciphertext, + openssl::rsa::Padding::PKCS1, + ) + .unwrap(); + ciphertext.truncate(encrypted_len); + ciphertext + } + + pub fn decrypt(data: &[u8]) -> String { + let mut ciphertext = vec![0; unsafe { PRIKEY.as_ref().unwrap() }.size() as usize]; + let encrypted_len = unsafe { PRIKEY.as_ref().unwrap() } + .public_decrypt(data, &mut ciphertext, openssl::rsa::Padding::PKCS1) + .unwrap(); + ciphertext.truncate(encrypted_len); + ciphertext + .into_iter() + .map(|x| x as char) + .collect::() + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..bea958e --- /dev/null +++ b/src/db.rs @@ -0,0 +1,67 @@ +use std::{ + cell::RefCell, + io::{self, ErrorKind}, +}; + +use rusqlite::Connection; + +use crate::userctl::{OnlineState, User}; + +thread_local! { + static CONNECTION: RefCell = RefCell::new(Connection::open("southbay.sqlite3").unwrap()); +} + +pub async fn read_user(id: usize) -> Result> { + let res = CONNECTION.with(|conn| -> Result> { + let conn = conn.borrow(); + let mut statement = conn + .prepare("SELECT name, passwd, email, avatar FROM users IF id = ?1;") + .unwrap(); + let mut q = statement.query([id]).unwrap(); + let row = if let Ok(row) = q.next() { + row + } else { + return Err(Box::new(io::Error::new( + io::ErrorKind::NotFound, + format!("{}", id), + ))); + } + .unwrap(); + Ok(User { + id, + name: row.get(0).unwrap(), + passwd: row.get(1).unwrap(), + email: row.get(2).unwrap(), + avatar: row.get(3).unwrap(), + state: OnlineState::Logging, + }) + })?; + Ok(res) +} + +pub async fn save_user(user: &User) -> io::Result<()> { + CONNECTION.with(|conn| -> io::Result<()> { + let conn = conn.borrow(); + let mut statement = conn + .prepare("INSERT INTO users (name, passwd, email, avatar) VALUES (?1, ?2, ?3, ?4);") + .unwrap(); + let mut q = statement + .query([ + user.name.as_bytes(), + &user.passwd, + user.email.as_bytes(), + &user.avatar.to_le_bytes(), + ]) + .unwrap(); + if let Ok(_) = q.next() { + () + } else { + return Err(io::Error::new( + ErrorKind::NotFound, + format!("Unexist user {}", user.id), + )); + } + Ok(()) + })?; + Ok(()) +} diff --git a/src/id_rsa b/src/id_rsa new file mode 100644 index 0000000..804a69c --- /dev/null +++ b/src/id_rsa @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0zG3gOS43O72S +bL/l3RWtP3eHlxAbn0fT13tfzrFXa3IyWSg4b1rtrEUqgO8ZLaYDpqeYuszLqcW9 +6r45l5CGqY3Iuf7RSmlmM3SI3R5x6QefsR0KWj7pdTJZ6uZzJb49e9PcQffQWQuk +tFH0Y6YpSOfH7frBTj/ipv51NkniRgh6pTUeBv9vFsvmm4HIhPhtle7L+wlTdRuQ +X+MVPH1kYqRBb7PioQONNEuefKRG4MnPtIb9mqtJ92EanzELnal9ypG8ZtajFFpx +uE18wIT+/n4CTR0q4dQyrbphVfEW4fwvtcJeQoKLG5m8QjrDqcQPv3chvZyaHfmz +S63A4eQjAgMBAAECggEAEQGxzDJUwJP5WblVC7WSK8EZPe3dxrC4PEW8Bd/BcBcG +n5R3Y2ebcVk6YFcke9p+upUwlVNCSbNnNbLLwKBwfdpGdf0uP62fiyIynLUqqfhg +49/werV7ztCxXc0/btFmuw3XeJPT+wsv5zGhtpAGZJfGWwNttO/ms9aVf50Nsh+l +LKfBe87gOcfEz4706lXtVHpyBtsRg+pZSIA4tF6TH3PgUHWUAj7dsC9+jd7MGHnl +z+dOemTNtjqqk7d7j71QiKe+ChP6bCxdv6fUQom0Y3XkeSKwHxVF2JATh7JN1c0e +KTJdSRliw8Snd6ccEQPOII5k05sPhY70Ijq28CnBIQKBgQDb7iBH68mCvb+OJTC9 +ijYA1qvGm5IJqeaN+Ac2q4Hft4kDl1DiS2j4+l291MQ7FH3szMuV07AqXkDHWzUQ +i/5wlP5Y6EFlJXyryfTM8Mz9v8XFMGkJXx3EU9nRpCkMEOhQc4grngnCMEayz2F+ +g+Y6I100eqqu6smA38Yap0l2ywKBgQDSc1fLd34Ew2QzZtevnroQtdkG4sO7y0hL +iCvikTy6KVykHHZkvfDsE6k7JeG+2wqXuZUKrHqgIkOXvQ8TDGPFobhCm9zVV8Ur +30Lh+z68mi6CUkysUfqBvoXj0g52AOWoCjocKGYawK4QhciwZRaLmN2CqNm2e9EX +6JUOpdhFCQKBgCbZrNgnncQ7sq/wf+irlpiVU2jVKHoOiky/6L5Ok7WqXUjGHSKs +gZGh4JddE4EAQLJpBvmjmHYTikvtH7WZlUvb9gZMmOi+M4TBOz5Mt1mH2+7We2eN +Ztu8mDjBUALypac4kXBrDXxBPOvvWGCZ+jyA7MENfkNB9HCQlxU0QfT7AoGATamF +F6/0EFvuyU1FgSjJL0t8WJ8sbkv5VO5ei5DfYS/MYT+jrU+u7d/fjtVO9nis8wro +ZnT6F8gtO3zoaDYlR5SDhAkyuIRYXfVZID3wi/c1/J/Uba8fC+w8SrOE5KU2GwoT +IbsC2xKJ6c0FH5tec4upCwO2vDH7GqCZCTvIkMkCgYEA1KavY6TOdj5V8f6nMiLj +PnUuAR2pluzFtpmTrY30DM4IwqA1Nx6awuaLcrhUsoH/uFi+gvr7oItwM7AwF4Al +q0YKwDXCDBOO9K5WSirqUsHoALsZxODWeBoxRytca8sfjm/J7WcEAk3UxY729d+H +G44/LXMaBEtVltDybw9boPs= +-----END PRIVATE KEY----- diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..253245c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,132 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + process::exit, + sync::Arc, + time::Duration, +}; + +use config::Config; +use crypt::Crypt; +use tokio::{ + net::TcpListener, + signal::unix::{signal, SignalKind}, + task, + time::sleep, +}; +use userctl::UserCtl; + +use crate::{thrpool::AsyncThreadPool, userctl::OnlineState}; + +pub mod config; +pub mod crypt; +pub mod db; +pub mod service; +pub mod thrpool; +pub mod userctl; + +pub async fn run(config: Config) -> ! { + Crypt::init().expect("Failed to load the public key."); + + let userctler = UserCtl::new(); + let passedinto_ctler = Arc::clone(&userctler); + task::spawn(async move { + loop { + for _ in 0..3600 { + sleep(Duration::from_secs(1)).await; + { + passedinto_ctler + .read() + .await + .borrow_mut() + .update_timecounts(); + } + } + { + passedinto_ctler + .write() + .await + .borrow_mut() + .users + .write() + .unwrap() + .retain(|_id, user| user.write().unwrap().state != OnlineState::Unlinked); + } + } + }); + + let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.port)) + .await + .unwrap(); + println!("Listening on 0.0.0.0:{}", config.port); + + let mut thread_pool = AsyncThreadPool::new(config.concurrent); + + task::spawn(async move { + loop { + let (socket, _) = listener.accept().await.unwrap(); + + let userctl = Arc::clone(&userctler); + + thread_pool + .send(async move { + let mut buf = [0u8; 1]; + socket.readable().await.unwrap(); + let ip = socket.peer_addr().unwrap(); + if let Ok(1) = socket.try_read(&mut buf) { + match buf[0] { + 1 => { + if let Err(e) = + service::prelogin(socket, userctl.read().await.borrow()).await + { + println!("From {:?} : {}", ip, e.to_string()); + } else { + println!("{:?} logging in.", ip); + } + } + 2 => { + match service::login(socket, userctl.read().await.borrow()).await { + Ok(n) => println!("{:?} login succeded as user {}.", ip, n), + Err(e) => println!("From {:?}: {}", ip, e.to_string()), + } + } + 3 => { + if let Err(e) = + service::heartbeat(socket, userctl.read().await.borrow()).await + { + println!("From {:?} : {}", ip, e.to_string()); + } else { + println!("{:?} heartbeats.", ip); + } + } + 4 => { + if let Err(e) = + service::logout(socket, userctl.read().await.borrow()).await + { + println!("From {:?} : {}", ip, e.to_string()); + } else { + println!("{:?} logout.", ip); + } + } + _ => (), + } + }; + }) + .await + .unwrap(); + } + }); + + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sigint = signal(SignalKind::interrupt()).unwrap(); + + tokio::select! { + _ = sigint.recv() => { + println!("\nReceived SIGINT, exiting..."); + exit(0); + } + _ = sigterm.recv() => { + println!("Received SIGTERM, exiting..."); + exit(0); + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a1c21a2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,11 @@ +use accounts::{config::Config, run}; + +use clap::Parser; +use tokio::io; + +#[tokio::main] +async fn main() -> io::Result<()> { + let config = Config::parse(); + run(config).await; + Ok(()) +} diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..e9313c2 --- /dev/null +++ b/src/service.rs @@ -0,0 +1,217 @@ +use std::{ + io::{self, Error, ErrorKind}, + sync::RwLock, +}; + +use openssl::hash::{Hasher, MessageDigest}; +use tokio::net::TcpStream; + +use crate::{ + crypt::Crypt, + db, + userctl::{OnlineState, User, UserCtl}, +}; + +async fn receive_decrypt_from(socket: &TcpStream) -> io::Result { + let mut buf = [0u8; 1024]; + let mut secret = Vec::new(); + loop { + socket.readable().await?; + let n = socket.try_read(&mut buf)?; + secret.append(&mut buf[..n].to_vec()); + if buf[n - 1] == b'\n' { + break; + } + } + Ok(Crypt::decrypt(&secret[..(secret.len() - 1)])) +} + +pub async fn prelogin(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> { + let text = receive_decrypt_from(&socket).await?; + let text = text.split(":").collect::>(); + if text[0] == "southbayhub login" { + let id = usize::from_str_radix(text[0], 16).unwrap(); + if userctl.users.read().unwrap().contains_key(&id) + && userctl + .users + .read() + .unwrap() + .get(&id) + .unwrap() + .read() + .unwrap() + .state + != OnlineState::Unlinked + && userctl + .users + .read() + .unwrap() + .get(&id) + .unwrap() + .read() + .unwrap() + .state + != OnlineState::Unavailable + { + socket.try_write(&Crypt::encrypt("already logged\n"))?; + } else { + match db::read_user(id).await { + Ok(user) => { + socket.try_write(&Crypt::encrypt("southbayhub server\n"))?; + userctl.users.write().unwrap().insert(id, RwLock::new(user)); + userctl + .state_timecount + .write() + .unwrap() + .insert(id, RwLock::new((0, 10, OnlineState::Unlinked))); + } + Err(e) => { + if let ErrorKind::NotFound = e.kind() { + socket.try_write(&Crypt::encrypt("unexist\n"))?; + } + } + } + } + Ok(()) + } else { + Err(Error::new( + io::ErrorKind::InvalidData, + format!( + "Need \"southbayhub login:{{id}}\" but received \"{}\".", + text[0] + ), + )) + } +} + +// 登录成功返回登录用户的id +pub async fn login(socket: TcpStream, userctl: &UserCtl) -> io::Result { + let text = receive_decrypt_from(&socket).await?; + let text = text.split(":").collect::>(); + let (id, passwd) = (usize::from_str_radix(text[0], 16).unwrap(), text[1]); + if !userctl.users.read().unwrap().contains_key(&id) { + socket.try_write(&Crypt::encrypt("unprelogged\n"))?; + Err(io::Error::new( + ErrorKind::NotFound, + format!("{} didn't prelogged.", id), + )) + } else { + let guard = userctl.users.read().unwrap(); + let mut guard = guard.get(&id).unwrap().write().unwrap(); + let User { + passwd: passident, + state, + .. + } = &mut *guard; + let passident = passident.iter().map(|x| *x as char).collect::(); + if *state == OnlineState::Logged || *state == OnlineState::Unavailable { + socket.try_write(&Crypt::encrypt("logged in\n"))?; + Err(io::Error::new( + ErrorKind::AlreadyExists, + format!("{} had login.", id), + )) + } else if *state == OnlineState::Logging { + let sha512str = { + let sha512str = passwd + .chars() + .map(|x| format!("{:x}", x as u8)) + .collect::>(); + let mut ss = String::new(); + for s in sha512str.into_iter() { + ss += &s; + } + ss + }; + let md5 = { + let mut hasher = Hasher::new(MessageDigest::md5())?; + hasher.update(sha512str.as_bytes())?; + let res = hasher.finish()?; + let res = res + .iter() + .map(|x| format!("{:x}", *x)) + .collect::>(); + let mut ss = String::new(); + for s in res.into_iter() { + ss += &s; + } + ss + }; + if passident == md5 { + *state = OnlineState::Logged; + *userctl + .state_timecount + .read() + .unwrap() + .get(&id) + .unwrap() + .write() + .unwrap() = (0, 60, OnlineState::Unavailable); + socket.try_write(&Crypt::encrypt("ok\n"))?; + Ok(id) + } else { + socket.try_write(&Crypt::encrypt("passwd incorrect\n"))?; + Err(io::Error::new( + ErrorKind::ConnectionRefused, + format!("{}'s passwd incorrect.", id), + )) + } + } else { + socket.try_write(&Crypt::encrypt("unprelogged\n"))?; + Err(io::Error::new( + ErrorKind::NotFound, + format!("{} didn't prelogged.", id), + )) + } + } +} + +pub async fn heartbeat(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> { + let text = receive_decrypt_from(&socket).await?; + let id = usize::from_str_radix(&text, 16).unwrap(); + if !userctl.users.read().unwrap().contains_key(&id) { + return Ok(()); + } else if { + userctl + .users + .read() + .unwrap() + .get(&id) + .unwrap() + .read() + .unwrap() + .state + == OnlineState::Unavailable + } { + userctl + .users + .read() + .unwrap() + .get(&id) + .unwrap() + .write() + .unwrap() + .state = OnlineState::Logged; + } else { + return Ok(()); + } + userctl + .state_timecount + .read() + .unwrap() + .get(&id) + .unwrap() + .write() + .unwrap() + .0 = 0; + Ok(()) +} + +pub async fn logout(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> { + let text = receive_decrypt_from(&socket).await?; + let id = usize::from_str_radix(&text, 16).unwrap(); + let user = userctl.users.write().unwrap().remove(&id).unwrap(); + let user = user.read().unwrap(); + let _ = db::save_user(&*user); + let _ = userctl.state_timecount.write().unwrap().remove(&id); + Ok(()) +} diff --git a/src/thrpool.rs b/src/thrpool.rs new file mode 100644 index 0000000..5fc48a3 --- /dev/null +++ b/src/thrpool.rs @@ -0,0 +1,38 @@ +use std::{future::Future, sync::Arc}; + +use tokio::{ + sync::{ + mpsc::{self, error::SendError, Sender}, + Mutex, + }, + task, +}; + +pub struct AsyncThreadPool + Send> { + sender: Sender, +} + +impl + Send + 'static> AsyncThreadPool { + pub fn new(threads: usize) -> Self { + let (sender, receiver) = mpsc::channel::(64); + let receiver = Arc::new(Mutex::new(receiver)); + for _ in 0..threads { + let inrc = Arc::clone(&receiver); + task::spawn(async move { + loop { + let f = { + let mut f = inrc.lock().await; + f.recv().await.unwrap() + }; + f.await; + } + }); + } + Self { sender } + } + + pub async fn send(&mut self, f: F) -> Result<(), SendError> { + self.sender.send(f).await?; + Ok(()) + } +} diff --git a/src/userctl.rs b/src/userctl.rs new file mode 100644 index 0000000..8c94e3c --- /dev/null +++ b/src/userctl.rs @@ -0,0 +1,50 @@ +use std::{ + collections::HashMap, + sync::{self, Arc}, +}; + +use tokio::sync::RwLock; + +pub struct UserCtl { + pub users: sync::RwLock>>, + pub state_timecount: sync::RwLock>>, +} + +impl UserCtl { + pub fn new() -> Arc> { + Arc::new(RwLock::new(UserCtl { + users: sync::RwLock::new(HashMap::new()), + state_timecount: sync::RwLock::new(HashMap::new()), + })) + } + + pub fn update_timecounts(&self) { + for (id, counts) in self.state_timecount.read().unwrap().iter() { + { + counts.write().unwrap().0 += 1 + }; + if counts.read().unwrap().1 != 0 && counts.read().unwrap().0 == counts.read().unwrap().1 + { + self.users.read().unwrap().get(&id).unwrap().write().unwrap().state = counts.read().unwrap().2; + } + } + } +} + +pub struct User { + pub id: usize, + pub name: String, + pub passwd: Vec, + pub email: String, + pub avatar: u32, + + pub state: OnlineState, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum OnlineState { + Logging, + Logged, + Unavailable, + Unlinked, +}