diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 000bb2c..86f2af7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,4 +19,4 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --verbose -- --no-capture diff --git a/Cargo.lock b/Cargo.lock index 9b74719..bef2193 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -82,28 +73,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -129,11 +98,10 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.7.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "async-trait", "axum-core", "bytes", "futures-util", @@ -146,49 +114,31 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "sync_wrapper", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - [[package]] name = "base64" version = "0.21.7" @@ -227,9 +177,9 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -237,9 +187,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -249,9 +199,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -273,22 +223,23 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "console-api" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" +checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3" dependencies = [ "futures-core", "prost", "prost-types", "tonic", + "tonic-prost", "tracing-core", ] [[package]] name = "console-subscriber" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01" +checksum = "fb4915b7d8dd960457a1b6c380114c2944f728e7c65294ab247ae6b6f1f37592" dependencies = [ "console-api", "crossbeam-channel", @@ -401,23 +352,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "h2" version = "0.4.12" @@ -430,19 +364,13 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.16.0" @@ -571,22 +499,12 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.6.0", + "socket2", "tokio", "tower-service", "tracing", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.11.4" @@ -594,18 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.0", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", + "hashbrown", ] [[package]] @@ -685,9 +592,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" @@ -755,15 +662,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -837,15 +735,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.101" @@ -857,9 +746,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", @@ -867,9 +756,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools", @@ -880,9 +769,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" dependencies = [ "prost", ] @@ -896,36 +785,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "redox_syscall" version = "0.5.17" @@ -952,18 +811,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "ryu" version = "1.0.20" @@ -1021,9 +868,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -1058,16 +905,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -1132,30 +969,27 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1188,11 +1022,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ - "indexmap 2.11.4", + "indexmap", "serde_core", "serde_spanned", "toml_datetime", @@ -1203,35 +1037,34 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ - "async-stream", "async-trait", "axum", "base64 0.22.1", @@ -1245,34 +1078,25 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", - "socket2 0.5.10", + "socket2", + "sync_wrapper", "tokio", "tokio-stream", - "tower 0.4.13", + "tower", "tower-layer", "tower-service", "tracing", ] [[package]] -name = "tower" -version = "0.4.13" +name = "tonic-prost" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", + "bytes", + "prost", + "tonic", ] [[package]] @@ -1283,10 +1107,15 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap", "pin-project-lite", + "slab", "sync_wrapper", + "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1303,9 +1132,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1314,9 +1143,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1325,9 +1154,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1346,9 +1175,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -1403,9 +1232,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -1434,6 +1263,15 @@ dependencies = [ "windows-targets 0.53.4", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1568,23 +1406,3 @@ name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 2b9ff44..37dd9d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,16 @@ edition = "2024" [dependencies] async-trait = "0.1.89" -clap = { version = "4.5.48", features = ["derive"] } -once_cell = "1.21.3" -tokio = { version = "1.47.1", features = ["full"] } -console-subscriber = { version = "0.4.1", optional = true } -tracing = "0.1.41" -tracing-subscriber = "0.3.20" +clap = { version = "4.5.53", features = ["derive"] } +tokio = { version = "1.48.0", features = ["full"] } +console-subscriber = { version = "0.5.0", optional = true } +tracing = "0.1.44" +tracing-subscriber = "0.3.22" thiserror = "2.0.17" anyhow = "1.0.100" -toml = "0.9.7" +toml = "0.9.10" serde = { version = "1.0.228", features = ["derive"] } +once_cell = "1.21.3" [features] tokio-console = ["tokio/tracing", "console-subscriber"] diff --git a/docs/technical/client_cap.txt b/docs/technical/client_cap.txt new file mode 100644 index 0000000..8cff81f --- /dev/null +++ b/docs/technical/client_cap.txt @@ -0,0 +1,172 @@ +Client Capab Proposal (v5) +Authors: fl and Beeth +-------------------------- + + +This document aims to define a protocol for capabilities between a server +and a client. These capabs help servers introduce changes to the protocol +that could cause problems with clients that do not support the changes. + +This specification defines the "CAP" command, and the available subcommands. +This is sent in the form: + CAP [subcommand [parameters]] + +CAP +--- + +A client that supports this specification must send one of three things at +the earliest stage of registration. To enter capab negotiation it can either +send the command "CAP" without parameters to request a list of capabs, or it +can send the subcommand "REQ" to request capabs without first seeing the +list of available capabs. A client which does not wish to enter capab +negotiation but supports this spec must send the subcommand "END" to notify +support of the specification. + +The "CAP" command may be used at any time during the connection to request a +list of capabs. If the user enters capab negotiation the server must +postpone registration until the client issues the subcommand "END". + +A server receiving the parameterless "CAP" command from a client must reply +to this command with a list of capabs supported by the server using the "LS" +and "LSL" subcommands. The "LS" subcommand is used when the capab list must +be spread across multiple lines and is used for all except the last line. +The subcommand "LSL" is used to finish the list of capabs. + +The subcommands "LS" and "LSL" are sent in the form: + CAP : [capab]* + +CAP: REQ +-------- + +The "REQ" subcommand is used by a client to give a list of capabs it +wishes to use. + +The subcommand "REQ" is sent in the form: + CAP REQ : [capab]* + +If any of the requested capabs are prefixed with '-' then the client is +notifying the server that it wishes the specified capab to be disabled for +the client. + +The responsibility is placed on the client to request a valid list of capabs. +The server must either accept the request as issued or reject it. A server +must not partially accept parameters of the subcommand "REQ". + +CAP: ACK +-------- + +The "ACK" subcommand is used by a server to acknowledge that the +given capabilities are now being used. The "ACK" subcommand is used by a +client to acknowledge the given capabilities are now being used. + +The server places the capabilities it is acknowledging as parameters. The +parameters of the subcommand "ACK" will be identical to the parameters issued +in the subcommand "REQ" from the client if the request is valid. If any of +the requested capabs modify the protocol stream, after sending the subcommand +"ACK" the server must then transmit any following messages in the modified +protocol stream form. + +The client must issue the subcommand "ACK" for any capabilities that modify +the protocol stream, using the capabs that modify the protocol stream as the +subcommand "ACK" parameters. A client must know which capabs modify the +protocol stream and send the subcommand "ACK" before transmitting any +messages in the modified protocol stream. A client must not issue the +subcommand "ACK" for a capab without first receiving the subcommand "ACK" +containing that capab from the server. + +A client must not issue the subcommand "ACK" for capabs that do not modify +the protocol stream. + +The subcommand "ACK" is sent in the following form: + CAP ACK : [capab]* + +CAP: NAK +-------- + +The "NAK" subcommand is used by a server to notify a client that the +requested list of capabs are invalid. + +A client receiving the subcommand "NAK" must request again the capabs it +wishes to use. The parameters of the subcommand "NAK" will be identical +to the parameters issued in the subcommand "REQ" from the client if the +request is invalid. + +The subcommand "NAK" is sent in the following form: + CAP NAK : [capab]* + +CAP: CLEAR +---------- + +The "CLEAR" subcommand is used by a server to notify a client that all +capabs have been cleared. The "CLEAR" subcommand is used by a client to +request that a server clears its current capab list. + +A server receiving the subcommand "CLEAR" must issue back the subcommand +"CLEAR" using the current protocol stream. It must then clear the stored +list of capabs and revert to receiving/sending a 'normal' protocol stream +from/to the client. + +A client receiving the subcommand "CLEAR" must then revert to expecting to +receive a 'normal' protocol stream. + +The subcommand "CLEAR" is sent in the form: + CAP CLEAR + +CAP: END +-------- + +The "END" subcommand is used by a client to notify the server it has +finished capab negotiation and the server can proceed with registration. + +The subcommand "END" is sent in the form: + CAP END + + +Examples +-------- + +CLIENT: indicates what client sends +SERVER: indicates what server sends + +This example shows a client which doesnt support this specification: + +CLIENT: NICK FOO +CLIENT: USER FOO FOO FOO FOO +SERVER: :SERVERNAME 001 ... + +This example shows a client supporting this specification but not +wishing to use capabs for the connection: + +CLIENT: CAP END +CLIENT: PASS FOO +CLIENT: NICK FOO +CLIENT: USER FOO FOO FOO FOO +SERVER: :SERVERNAME 001 .... + +This example shows a client requesting a list of capabs then successfully +requesting the capabs. + +CLIENT: CAP +CLIENT: NICK FOO +CLIENT: USER FOO FOO FOO FOO +SERVER: CAP LS :A B C D E F G H +SERVER: CAP LSL :I J +CLIENT: CAP REQ :A B C D E F +SERVER: CAP ACK :A B C D E F +CLIENT: CAP END +SERVER: :SERVERNAME 001 ... + +This example shows a client requesting an invalid list of capabs. + +CLIENT: CAP +CLIENT: NICK FOO +CLIENT: USER FOO FOO FOO FOO +SERVER: CAP LSL :A B C D E F G H +CLIENT: CAP REQ :A B +SERVER: CAP NAK :A B +CLIENT: CAP REQ :A +SERVER: CAP ACK :A +CLIENT: CAP REQ :C +SERVER: CAP ACK :C +CLIENT: CAP END +SERVER: :SERVERNAME 001 ... diff --git a/docs/technical/encap.txt b/docs/technical/encap.txt new file mode 100644 index 0000000..23c1cbc --- /dev/null +++ b/docs/technical/encap.txt @@ -0,0 +1,32 @@ +ENCAP DEFINITION +---------------- + +Preamble +-------- + +This document defines the specification for the ENCAP command. + +ENCAP is designed to help fix the situation where new commands do +not propagate over hub servers running older code. + +Definition +---------- + +Support for the ENCAP command is given by the CAPAB token "ENCAP". + +The format of ENCAP is: + : ENCAP + + - The entity generating the command. + + - The entity the command is destined for. This may + include wildcards for servers, but not clients. + + If the wildcard does not match the current server, the + command should be propagated and ignored. + + - The subcommand we're propagating over ENCAP. If the + subcommand is not recognised by the current server, the + command should be propagated and ignored. + + - The parameters that the subcommand have. diff --git a/docs/technical/irc-server-protocol-hybrid.txt b/docs/technical/irc-server-protocol-hybrid.txt new file mode 100644 index 0000000..835cf14 --- /dev/null +++ b/docs/technical/irc-server-protocol-hybrid.txt @@ -0,0 +1,1171 @@ +Network Working Group C. Kalt +Request for Comments: 2813 April 2000 +Updates: 1459 +Category: Informational + + Internet Relay Chat: Server Protocol + +Status of this Memo + + This memo provides information for the Internet community. It does + not specify an Internet standard of any kind. Distribution of this + memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2000). All Rights Reserved. + +Abstract + + While based on the client-server model, the IRC (Internet Relay Chat) + protocol allows servers to connect to each other effectively forming + a network. + + This document defines the protocol used by servers to talk to each + other. It was originally a superset of the client protocol but has + evolved differently. + + First formally documented in May 1993 as part of RFC 1459 [IRC], most + of the changes brought since then can be found in this document as + development was focused on making the protocol scale better. Better + scalability has allowed existing world-wide networks to keep growing + and reach sizes which defy the old specification. + +Table of Contents + + 1. Introduction ............................................... 3 + 2. Global database ............................................ 3 + 2.1 Servers ................................................ 3 + 2.2 Clients ................................................ 4 + 2.2.1 Users ............................................. 4 + 2.2.2 Services .......................................... 4 + 2.3 Channels ............................................... 4 + 3. The IRC Server Specification ............................... 5 + 3.1 Overview ............................................... 5 + 3.2 Character codes ........................................ 5 + 3.3 Messages ............................................... 5 + 3.3.1 Message format in Augmented BNF ................... 6 + 3.4 Numeric replies ........................................ 7 + 4. Message Details ............................................ 7 + 4.1 Connection Registration ................................ 8 + 4.1.1 Password message .................................. 8 + 4.1.2 Server message .................................... 9 + 4.1.3 Nick .............................................. 10 + 4.1.4 Service message ................................... 11 + 4.1.5 Quit .............................................. 12 + 4.1.6 Server quit message ............................... 13 + 4.2 Channel operations ..................................... 14 + 4.2.1 Join message ...................................... 14 + 4.2.2 Njoin message ..................................... 15 + 4.2.3 Mode message ...................................... 16 + 5. Implementation details .................................... 16 + 5.1 Connection 'Liveness' .................................. 16 + 5.2 Accepting a client to server connection ................ 16 + 5.2.1 Users ............................................. 16 + 5.2.2 Services .......................................... 17 + 5.3 Establishing a server-server connection. ............... 17 + 5.3.1 Link options ...................................... 17 + 5.3.1.1 Compressed server to server links ............ 18 + 5.3.1.2 Anti abuse protections ....................... 18 + 5.3.2 State information exchange when connecting ........ 18 + 5.4 Terminating server-client connections .................. 19 + 5.5 Terminating server-server connections .................. 19 + 5.6 Tracking nickname changes .............................. 19 + 5.7 Tracking recently used nicknames ....................... 20 + 5.8 Flood control of clients ............................... 20 + 5.9 Non-blocking lookups ................................... 21 + 5.9.1 Hostname (DNS) lookups ............................ 21 + 5.9.2 Username (Ident) lookups .......................... 21 + 6. Current problems ........................................... 21 + 6.1 Scalability ............................................ 21 + 6.2 Labels ................................................. 22 + + 6.2.1 Nicknames ......................................... 22 + 6.2.2 Channels .......................................... 22 + 6.2.3 Servers ........................................... 22 + 6.3 Algorithms ............................................. 22 + 7. Security Considerations .................................... 23 + 7.1 Authentication ......................................... 23 + 7.2 Integrity .............................................. 23 + 8. Current support and availability ........................... 24 + 9. Acknowledgements ........................................... 24 + 10. References ................................................ 24 + 11. Author's Address .......................................... 25 + 12. Full Copyright Statement ................................... 26 + +1. Introduction + + This document is intended for people working on implementing an IRC + server but will also be useful to anyone implementing an IRC service. + + Servers provide the three basic services required for realtime + conferencing defined by the "Internet Relay Chat: Architecture" + [IRC-ARCH]: client locator (via the client protocol [IRC-CLIENT]), + message relaying (via the server protocol defined in this document) + and channel hosting and management (following specific rules [IRC- + CHAN]). + +2. Global database + + Although the IRC Protocol defines a fairly distributed model, each + server maintains a "global state database" about the whole IRC + network. This database is, in theory, identical on all servers. + +2.1 Servers + + Servers are uniquely identified by their name which has a maximum + length of sixty three (63) characters. See the protocol grammar + rules (section 3.3.1) for what may and may not be used in a server + name. + + Each server is typically known by all other servers, however it is + possible to define a "hostmask" to group servers together according + to their name. Inside the hostmasked area, all the servers have a + name which matches the hostmask, and any other server with a name + matching the hostmask SHALL NOT be connected to the IRC network + outside the hostmasked area. Servers which are outside the area have + no knowledge of the individual servers present inside the area, + instead they are presented with a virtual server which has the + hostmask for name. + +2.2 Clients + + For each client, all servers MUST have the following information: a + netwide unique identifier (whose format depends on the type of + client) and the server to which the client is connected. + +2.2.1 Users + + Each user is distinguished from other users by a unique nickname + having a maximum length of nine (9) characters. See the protocol + grammar rules (section 3.3.1) for what may and may not be used in a + nickname. In addition to the nickname, all servers MUST have the + following information about all users: the name of the host that the + user is running on, the username of the user on that host, and the + server to which the client is connected. + +2.2.2 Services + + Each service is distinguished from other services by a service name + composed of a nickname and a server name. The nickname has a maximum + length of nine (9) characters. See the protocol grammar rules + (section 3.3.1) for what may and may not be used in a nickname. The + server name used to compose the service name is the name of the + server to which the service is connected. In addition to this + service name all servers MUST know the service type. + + Services differ from users by the format of their identifier, but + more importantly services and users don't have the same type of + access to the server: services can request part or all of the global + state information that a server maintains, but have a more restricted + set of commands available to them (See "IRC Client Protocol" [IRC- + CLIENT] for details on which) and are not allowed to join channels. + Finally services are not usually subject to the "Flood control" + mechanism described in section 5.8. + +2.3 Channels + + Alike services, channels have a scope [IRC-CHAN] and are not + necessarily known to all servers. When a channel existence is known + to a server, the server MUST keep track of the channel members, as + well as the channel modes. + +3. The IRC Server Specification + +3.1 Overview + + The protocol as described herein is for use with server to server + connections. For client to server connections, see the IRC Client + Protocol specification. + + There are, however, more restrictions on client connections (which + are considered to be untrustworthy) than on server connections. + +3.2 Character codes + + No specific character set is specified. The protocol is based on a a + set of codes which are composed of eight (8) bits, making up an + octet. Each message may be composed of any number of these octets; + however, some octet values are used for control codes which act as + message delimiters. + + Regardless of being an 8-bit protocol, the delimiters and keywords + are such that protocol is mostly usable from US-ASCII terminal and a + telnet connection. + + Because of IRC's Scandinavian origin, the characters {}|^ are + considered to be the lower case equivalents of the characters []\~, + respectively. This is a critical issue when determining the + equivalence of two nicknames, or channel names. + +3.3 Messages + + Servers and clients send each other messages which may or may not + generate a reply. Most communication between servers do not generate + any reply, as servers mostly perform routing tasks for the clients. + + Each IRC message may consist of up to three main parts: the prefix + (OPTIONAL), the command, and the command parameters (maximum of + fifteen (15)). The prefix, command, and all parameters are separated + by one ASCII space character (0x20) each. + + The presence of a prefix is indicated with a single leading ASCII + colon character (':', 0x3b), which MUST be the first character of the + message itself. There MUST be NO gap (whitespace) between the colon + and the prefix. The prefix is used by servers to indicate the true + origin of the message. If the prefix is missing from the message, it + is assumed to have originated from the connection from which it was + received. Clients SHOULD not use a prefix when sending a message + from themselves; if they use one, the only valid prefix is the + registered nickname associated with the client. + + When a server receives a message, it MUST identify its source using + the (eventually assumed) prefix. If the prefix cannot be found in + the server's internal database, it MUST be discarded, and if the + prefix indicates the message comes from an (unknown) server, the link + from which the message was received MUST be dropped. Dropping a link + in such circumstances is a little excessive but necessary to maintain + the integrity of the network and to prevent future problems. Another + common error condition is that the prefix found in the server's + internal database identifies a different source (typically a source + registered from a different link than from which the message + arrived). If the message was received from a server link and the + prefix identifies a client, a KILL message MUST be issued for the + client and sent to all servers. In other cases, the link from which + the message arrived SHOULD be dropped for clients, and MUST be + dropped for servers. In all cases, the message MUST be discarded. + + The command MUST either be a valid IRC command or a three (3) digit + number represented in ASCII text. + + IRC messages are always lines of characters terminated with a CR-LF + (Carriage Return - Line Feed) pair, and these messages SHALL NOT + exceed 512 characters in length, counting all characters including + the trailing CR-LF. Thus, there are 510 characters maximum allowed + for the command and its parameters. There is no provision for + continuation message lines. See section 5 for more details about + current implementations. + +3.3.1 Message format in Augmented BNF + + The protocol messages must be extracted from the contiguous stream of + octets. The current solution is to designate two characters, CR and + LF, as message separators. Empty messages are silently ignored, + which permits use of the sequence CR-LF between messages without + extra problems. + + The extracted message is parsed into the components , + and list of parameters (). + + The Augmented BNF representation for this is found in "IRC Client + Protocol" [IRC-CLIENT]. + + The extended prefix (["!" user "@" host ]) MUST NOT be used in server + to server communications and is only intended for server to client + messages in order to provide clients with more useful information + about who a message is from without the need for additional queries. + +3.4 Numeric replies + + Most of the messages sent to the server generate a reply of some + sort. The most common reply is the numeric reply, used for both + errors and normal replies. The numeric reply MUST be sent as one + message consisting of the sender prefix, the three digit numeric, and + the target of the reply. A numeric reply is not allowed to originate + from a client; any such messages received by a server are silently + dropped. In all other respects, a numeric reply is just like a normal + message, except that the keyword is made up of 3 numeric digits + rather than a string of letters. A list of different replies is + supplied in "IRC Client Protocol" [IRC-CLIENT]. + +4. Message Details + + All the messages recognized by the IRC server and client are + described in the IRC Client Protocol specification. + + Where the reply ERR_NOSUCHSERVER is returned, it means that the + target of the message could not be found. The server MUST NOT send + any other replies after this error for that command. + + The server to which a client is connected is required to parse the + complete message, returning any appropriate errors. If the server + encounters a fatal error while parsing a message, an error MUST be + sent back to the client and the parsing terminated. A fatal error + may follow from incorrect command, a destination which is otherwise + unknown to the server (server, client or channel names fit this + category), not enough parameters or incorrect privileges. + + If a full set of parameters is presented, then each MUST be checked + for validity and appropriate responses sent back to the client. In + the case of messages which use parameter lists using the comma as an + item separator, a reply MUST be sent for each item. + + In the examples below, some messages appear using the full format: + + :Name COMMAND parameter list + + Such examples represent a message from "Name" in transit between + servers, where it is essential to include the name of the original + sender of the message so remote servers may send back a reply along + the correct path. + + The message details for client to server communication are described + in the "IRC Client Protocol" [IRC-CLIENT]. Some sections in the + following pages apply to some of these messages, they are additions + to the message specifications which are only relevant to server to + + server communication, or to the server implementation. The messages + which are introduced here are only used for server to server + communication. + +4.1 Connection Registration + + The commands described here are used to register a connection with + another IRC server. + +4.1.1 Password message + + Command: PASS + Parameters: [] + + The PASS command is used to set a 'connection password'. The + password MUST be set before any attempt to register the connection is + made. Currently this means that servers MUST send a PASS command + before any SERVER command. Only one (1) PASS command SHALL be + accepted from a connection. + + The last three (3) parameters MUST be ignored if received from a + client (e.g. a user or a service). They are only relevant when + received from a server. + + The parameter is a string of at least four (4) characters, + and up to fourteen (14) characters. The first four (4) characters + MUST be digits and indicate the protocol version known by the server + issuing the message. The protocol described by this document is + version 2.10 which is encoded as "0210". The remaining OPTIONAL + characters are implementation dependent and should describe the + software version number. + + The parameter is a string of up to one hundred (100) + characters. It is composed of two substrings separated by the + character "|" (%x7C). If present, the first substring MUST be the + name of the implementation. The reference implementation (See + Section 8, "Current support and availability") uses the string "IRC". + If a different implementation is written, which needs an identifier, + then that identifier should be registered through publication of an + RFC. The second substring is implementation dependent. Both + substrings are OPTIONAL, but the character "|" is REQUIRED. The + character "|" MUST NOT appear in either substring. + + Finally, the last parameter, , is used for link options. + The only options defined by the protocol are link compression (using + the character "Z"), and an abuse protection flag (using the character + + "P"). See sections 5.3.1.1 (Compressed server to server links) and + 5.3.1.2 (Anti abuse protections) respectively for more information on + these options. + + Numeric Replies: + + ERR_NEEDMOREPARAMS ERR_ALREADYREGISTRED + + Example: + + PASS moresecretpassword 0210010000 IRC|aBgH$ Z + +4.1.2 Server message + + Command: SERVER + Parameters: + + The SERVER command is used to register a new server. A new connection + introduces itself as a server to its peer. This message is also used + to pass server data over whole net. When a new server is connected + to net, information about it MUST be broadcasted to the whole + network. + + The parameter may contain space characters. + + is used to give all servers some internal information on + how far away each server is. Local peers have a value of 0, and each + passed server increments the value. With a full server list, it + would be possible to construct a map of the entire server tree, but + hostmasks prevent this from being done. + + The parameter is an unsigned number used by servers as an + identifier. This identifier is subsequently used to reference a + server in the NICK and SERVICE messages sent between servers. Server + tokens only have a meaning for the point-to-point peering they are + used and MUST be unique for that connection. They are not global. + + The SERVER message MUST only be accepted from either (a) a connection + which is yet to be registered and is attempting to register as a + server, or (b) an existing connection to another server, in which + case the SERVER message is introducing a new server behind that + server. + + Most errors that occur with the receipt of a SERVER command result in + the connection being terminated by the destination host (target + SERVER). Because of the severity of such event, error replies are + usually sent using the "ERROR" command rather than a numeric. + + If a SERVER message is parsed and it attempts to introduce a server + which is already known to the receiving server, the connection, from + which that message arrived, MUST be closed (following the correct + procedures), since a duplicate route to a server has been formed and + the acyclic nature of the IRC tree breaks. In some conditions, the + connection from which the already known server has registered MAY be + closed instead. It should be noted that this kind of error can also + be the result of a second running server, problem which cannot be + fixed within the protocol and typically requires human intervention. + This type of problem is particularly insidious, as it can quite + easily result in part of the IRC network to be isolated, with one of + the two servers connected to each partition therefore making it + impossible for the two parts to unite. + + Numeric Replies: + + ERR_ALREADYREGISTRED + + Example: + + SERVER test.oulu.fi 1 1 :Experimental server ; New server + test.oulu.fi introducing itself and + attempting to register. + + :tolsun.oulu.fi SERVER csd.bu.edu 5 34 :BU Central Server ; Server + tolsun.oulu.fi is our uplink for + csd.bu.edu which is 5 hops away. The + token "34" will be used by + tolsun.oulu.fi when introducing new + users or services connected to + csd.bu.edu. + +4.1.3 Nick + + Command: NICK + Parameters: + + + This form of the NICK message MUST NOT be allowed from user + connections. However, it MUST be used instead of the NICK/USER pair + to notify other servers of new users joining the IRC network. + + This message is really the combination of three distinct messages: + NICK, USER and MODE [IRC-CLIENT]. + + The parameter is used by servers to indicate how far away + a user is from its home server. A local connection has a hopcount of + 0. The hopcount value is incremented by each passed server. + + The parameter replaces the parameter of + the USER (See section 4.1.2 for more information on server tokens). + + Examples: + + NICK syrk 5 kalt millennium.stealth.net 34 +i :Christophe Kalt ; New + user with nickname "syrk", username + "kalt", connected from host + "millennium.stealth.net" to server + "34" ("csd.bu.edu" according to the + previous example). + + :krys NICK syrk ; The other form of the NICK message, + as defined in "IRC Client Protocol" + [IRC-CLIENT] and used between + servers: krys changed his nickname to + syrk + +4.1.4 Service message + + Command: SERVICE + Parameters: + + + The SERVICE command is used to introduce a new service. This form of + the SERVICE message SHOULD NOT be allowed from client (unregistered, + or registered) connections. However, it MUST be used between servers + to notify other servers of new services joining the IRC network. + + The is used to identify the server to which the service + is connected. (See section 4.1.2 for more information on server + tokens). + + The parameter is used by servers to indicate how far away + a service is from its home server. A local connection has a hopcount + of 0. The hopcount value is incremented by each passed server. + + The parameter is used to specify the visibility of a + service. The service may only be known to servers which have a name + matching the distribution. For a matching server to have knowledge + of the service, the network path between that server and the server + to which the service is connected MUST be composed of servers whose + names all match the mask. Plain "*" is used when no restriction is + wished. + + The parameter is currently reserved for future usage. + + Numeric Replies: + + ERR_ALREADYREGISTRED ERR_NEEDMOREPARAMS + ERR_ERRONEUSNICKNAME + RPL_YOURESERVICE RPL_YOURHOST + RPL_MYINFO + + Example: + +SERVICE dict@irc.fr 9 *.fr 0 1 :French Dictionary r" registered on + server "9" is being announced to + another server. This service will + only be available on servers whose + name matches "*.fr". + +4.1.5 Quit + + Command: QUIT + Parameters: [] + + A client session ends with a quit message. The server MUST close the + connection to a client which sends a QUIT message. If a "Quit + Message" is given, this will be sent instead of the default message, + the nickname or service name. + + When "netsplit" (See Section 4.1.6) occur, the "Quit Message" is + composed of the names of two servers involved, separated by a space. + The first name is that of the server which is still connected and the + second name is either that of the server which has become + disconnected or that of the server to which the leaving client was + connected: + + = ":" servername SPACE servername + + Because the "Quit Message" has a special meaning for "netsplits", + servers SHOULD NOT allow a client to use a in the + format described above. + + If, for some other reason, a client connection is closed without the + client issuing a QUIT command (e.g. client dies and EOF occurs on + socket), the server is REQUIRED to fill in the quit message with some + sort of message reflecting the nature of the event which caused it to + happen. Typically, this is done by reporting a system specific + error. + + Numeric Replies: + + None. + + Examples: + + :WiZ QUIT :Gone to have lunch ; Preferred message format. + +4.1.6 Server quit message + + Command: SQUIT + Parameters: + + The SQUIT message has two distinct uses. + + The first one (described in "Internet Relay Chat: Client Protocol" + [IRC-CLIENT]) allows operators to break a local or remote server + link. This form of the message is also eventually used by servers to + break a remote server link. + + The second use of this message is needed to inform other servers when + a "network split" (also known as "netsplit") occurs, in other words + to inform other servers about quitting or dead servers. If a server + wishes to break the connection to another server it MUST send a SQUIT + message to the other server, using the name of the other server as + the server parameter, which then closes its connection to the + quitting server. + + The is filled in by servers which SHOULD place an error or + similar message here. + + Both of the servers which are on either side of the connection being + closed are REQUIRED to send out a SQUIT message (to all its other + server connections) for all other servers which are considered to be + behind that link. + + Similarly, a QUIT message MAY be sent to the other still connected + servers on behalf of all clients behind that quitting link. In + addition to this, all channel members of a channel which lost a + member due to the "split" MUST be sent a QUIT message. Messages to + channel members are generated by each client's local server. + + If a server connection is terminated prematurely (e.g., the server on + the other end of the link died), the server which detects this + disconnection is REQUIRED to inform the rest of the network that the + connection has closed and fill in the comment field with something + appropriate. + + When a client is removed as the result of a SQUIT message, the server + SHOULD add the nickname to the list of temporarily unavailable + nicknames in an attempt to prevent future nickname collisions. See + + section 5.7 (Tracking recently used nicknames) for more information + on this procedure. + + Numeric replies: + + ERR_NOPRIVILEGES ERR_NOSUCHSERVER + ERR_NEEDMOREPARAMS + + Example: + + SQUIT tolsun.oulu.fi :Bad Link ? ; the server link tolson.oulu.fi + has been terminated because of "Bad + Link". + + :Trillian SQUIT cm22.eng.umd.edu :Server out of control ; message + from Trillian to disconnect + "cm22.eng.umd.edu" from the net + because "Server out of control". + +4.2 Channel operations + + This group of messages is concerned with manipulating channels, their + properties (channel modes), and their contents (typically users). In + implementing these, a number of race conditions are inevitable when + users at opposing ends of a network send commands which will + ultimately clash. It is also REQUIRED that servers keep a nickname + history to ensure that wherever a parameter is given, the + server check its history in case it has recently been changed. + +4.2.1 Join message + + Command: JOIN + Parameters: [ %x7 ] + *( "," [ %x7 ] ) + + The JOIN command is used by client to start listening a specific + channel. Whether or not a client is allowed to join a channel is + checked only by the local server the client is connected to; all + other servers automatically add the user to the channel when the + command is received from other servers. + + Optionally, the user status (channel modes 'O', 'o', and 'v') on the + channel may be appended to the channel name using a control G (^G or + ASCII 7) as separator. Such data MUST be ignored if the message + wasn't received from a server. This format MUST NOT be sent to + clients, it can only be used between servers and SHOULD be avoided. + + The JOIN command MUST be broadcast to all servers so that each server + knows where to find the users who are on the channel. This allows + optimal delivery of PRIVMSG and NOTICE messages to the channel. + + Numeric Replies: + + ERR_NEEDMOREPARAMS ERR_BANNEDFROMCHAN + ERR_INVITEONLYCHAN ERR_BADCHANNELKEY + ERR_CHANNELISFULL ERR_BADCHANMASK + ERR_NOSUCHCHANNEL ERR_TOOMANYCHANNELS + ERR_TOOMANYTARGETS ERR_UNAVAILRESOURCE + RPL_TOPIC + + Examples: + + :WiZ JOIN #Twilight_zone ; JOIN message from WiZ + +4.2.2 Njoin message + + Command: NJOIN + Parameters: [ "@@" / "@" ] [ "+" ] + *( "," [ "@@" / "@" ] [ "+" ] ) + + The NJOIN message is used between servers only. If such a message is + received from a client, it MUST be ignored. It is used when two + servers connect to each other to exchange the list of channel members + for each channel. + + Even though the same function can be performed by using a succession + of JOIN, this message SHOULD be used instead as it is more efficient. + The prefix "@@" indicates that the user is the "channel creator", the + character "@" alone indicates a "channel operator", and the character + '+' indicates that the user has the voice privilege. + + Numeric Replies: + + ERR_NEEDMOREPARAMS ERR_NOSUCHCHANNEL + ERR_ALREADYREGISTRED + + Examples: + + :ircd.stealth.net NJOIN #Twilight_zone :@WiZ,+syrk,avalon ; NJOIN + message from ircd.stealth.net + announcing users joining the + #Twilight_zone channel: WiZ with + channel operator status, syrk with + voice privilege and avalon with no + privilege. + +4.2.3 Mode message + + The MODE message is a dual-purpose command in IRC. It allows both + usernames and channels to have their mode changed. + + When parsing MODE messages, it is RECOMMENDED that the entire message + be parsed first, and then the changes which resulted passed on. + + It is REQUIRED that servers are able to change channel modes so that + "channel creator" and "channel operators" may be created. + +5. Implementation details + + A the time of writing, the only current implementation of this + protocol is the IRC server, version 2.10. Earlier versions may + implement some or all of the commands described by this document with + NOTICE messages replacing many of the numeric replies. Unfortunately, + due to backward compatibility requirements, the implementation of + some parts of this document varies with what is laid out. One + notable difference is: + + * recognition that any LF or CR anywhere in a message marks + the end of that message (instead of requiring CR-LF); + + The rest of this section deals with issues that are mostly of + importance to those who wish to implement a server but some parts + also apply directly to clients as well. + +5.1 Connection 'Liveness' + + To detect when a connection has died or become unresponsive, the + server MUST poll each of its connections. The PING command (See "IRC + Client Protocol" [IRC-CLIENT]) is used if the server doesn't get a + response from its peer in a given amount of time. + + If a connection doesn't respond in time, its connection is closed + using the appropriate procedures. + +5.2 Accepting a client to server connection + +5.2.1 Users + + When a server successfully registers a new user connection, it is + REQUIRED to send to the user unambiguous messages stating: the user + identifiers upon which it was registered (RPL_WELCOME), the server + name and version (RPL_YOURHOST), the server birth information + (RPL_CREATED), available user and channel modes (RPL_MYINFO), and it + MAY send any introductory messages which may be deemed appropriate. + + In particular the server SHALL send the current user/service/server + count (as per the LUSER reply) and finally the MOTD (if any, as per + the MOTD reply). + + After dealing with registration, the server MUST then send out to + other servers the new user's nickname (NICK message), other + information as supplied by itself (USER message) and as the server + could discover (from DNS servers). The server MUST NOT send this + information out with a pair of NICK and USER messages as defined in + "IRC Client Protocol" [IRC-CLIENT], but MUST instead take advantage + of the extended NICK message defined in section 4.1.3. + +5.2.2 Services + + Upon successfully registering a new service connection, the server is + subject to the same kind of REQUIREMENTS as for a user. Services + being somewhat different, only the following replies are sent: + RPL_YOURESERVICE, RPL_YOURHOST, RPL_MYINFO. + + After dealing with this, the server MUST then send out to other + servers (SERVICE message) the new service's nickname and other + information as supplied by the service (SERVICE message) and as the + server could discover (from DNS servers). + +5.3 Establishing a server-server connection. + + The process of establishing a server-to-server connection is fraught + with danger since there are many possible areas where problems can + occur - the least of which are race conditions. + + After a server has received a connection following by a PASS/SERVER + pair which were recognized as being valid, the server SHOULD then + reply with its own PASS/SERVER information for that connection as + well as all of the other state information it knows about as + described below. + + When the initiating server receives a PASS/SERVER pair, it too then + checks that the server responding is authenticated properly before + accepting the connection to be that server. + +5.3.1 Link options + + Server links are based on a common protocol (defined by this + document) but a particular link MAY set specific options using the + PASS message (See Section 4.1.1). + +5.3.1.1 Compressed server to server links + + If a server wishes to establish a compressed link with its peer, it + MUST set the 'Z' flag in the options parameter to the PASS message. + If both servers request compression and both servers are able to + initialize the two compressed streams, then the remainder of the + communication is to be compressed. If any server fails to initialize + the stream, it will send an uncompressed ERROR message to its peer + and close the connection. + + The data format used for the compression is described by RFC 1950 + [ZLIB], RFC 1951 [DEFLATE] and RFC 1952 [GZIP]. + +5.3.1.2 Anti abuse protections + + Most servers implement various kinds of protections against possible + abusive behaviours from non trusted parties (typically users). On + some networks, such protections are indispensable, on others they are + superfluous. To require that all servers implement and enable such + features on a particular network, the 'P' flag is used when two + servers connect. If this flag is present, it means that the server + protections are enabled, and that the server REQUIRES all its server + links to enable them as well. + + Commonly found protections are described in sections 5.7 (Tracking + recently used nicknames) and 5.8 (Flood control of clients). + +5.3.2 State information exchange when connecting + + The order of state information being exchanged between servers is + essential. The REQUIRED order is as follows: + + * all known servers; + + * all known client information; + + * all known channel information. + + Information regarding servers is sent via extra SERVER messages, + client information with NICK and SERVICE messages and channels with + NJOIN/MODE messages. + + NOTE: channel topics SHOULD NOT be exchanged here because the TOPIC + command overwrites any old topic information, so at best, the two + sides of the connection would exchange topics. + + By passing the state information about servers first, any collisions + with servers that already exist occur before nickname collisions + caused by a second server introducing a particular nickname. Due to + the IRC network only being able to exist as an acyclic graph, it may + be possible that the network has already reconnected in another + location. In this event, the place where the server collision occurs + indicates where the net needs to split. + +5.4 Terminating server-client connections + + When a client connection unexpectedly closes, a QUIT message is + generated on behalf of the client by the server to which the client + was connected. No other message is to be generated or used. + +5.5 Terminating server-server connections + + If a server-server connection is closed, either via a SQUIT command + or "natural" causes, the rest of the connected IRC network MUST have + its information updated by the server which detected the closure. + The terminating server then sends a list of SQUITs (one for each + server behind that connection). (See Section 4.1.6 (SQUIT)). + +5.6 Tracking nickname changes + + All IRC servers are REQUIRED to keep a history of recent nickname + changes. This is important to allow the server to have a chance of + keeping in touch of things when nick-change race conditions occur + with commands manipulating them. Messages which MUST trace nick + changes are: + + * KILL (the nick being disconnected) + + * MODE (+/- o,v on channels) + + * KICK (the nick being removed from channel) + + No other commands need to check nick changes. + + In the above cases, the server is required to first check for the + existence of the nickname, then check its history to see who that + nick now belongs to (if anyone!). This reduces the chances of race + conditions but they can still occur with the server ending up + affecting the wrong client. When performing a change trace for an + above command it is RECOMMENDED that a time range be given and + entries which are too old ignored. + + For a reasonable history, a server SHOULD be able to keep previous + nickname for every client it knows about if they all decided to + change. This size is limited by other factors (such as memory, etc). + +5.7 Tracking recently used nicknames + + This mechanism is commonly known as "Nickname Delay", it has been + proven to significantly reduce the number of nickname collisions + resulting from "network splits"/reconnections as well as abuse. + + In addition of keeping track of nickname changes, servers SHOULD keep + track of nicknames which were recently used and were released as the + result of a "network split" or a KILL message. These nicknames are + then unavailable to the server local clients and cannot be re-used + (even though they are not currently in use) for a certain period of + time. + + The duration for which a nickname remains unavailable SHOULD be set + considering many factors among which are the size (user wise) of the + IRC network, and the usual duration of "network splits". It SHOULD + be uniform on all servers for a given IRC network. + +5.8 Flood control of clients + + With a large network of interconnected IRC servers, it is quite easy + for any single client attached to the network to supply a continuous + stream of messages that result in not only flooding the network, but + also degrading the level of service provided to others. Rather than + require every 'victim' to provide their own protection, flood + protection was written into the server and is applied to all clients + except services. The current algorithm is as follows: + + * check to see if client's `message timer' is less than current time + (set to be equal if it is); + + * read any data present from the client; + + * while the timer is less than ten (10) seconds ahead of the current + time, parse any present messages and penalize the client by two (2) + seconds for each message; + + * additional penalties MAY be used for specific commands which + generate a lot of traffic across the network. + + This in essence means that the client may send one (1) message every + two (2) seconds without being adversely affected. Services MAY also + be subject to this mechanism. + +5.9 Non-blocking lookups + + In a real-time environment, it is essential that a server process + does as little waiting as possible so that all the clients are + serviced fairly. Obviously this requires non-blocking IO on all + network read/write operations. For normal server connections, this + was not difficult, but there are other support operations that may + cause the server to block (such as disk reads). Where possible, such + activity SHOULD be performed with a short timeout. + +5.9.1 Hostname (DNS) lookups + + Using the standard resolver libraries from Berkeley and others has + meant large delays in some cases where replies have timed out. To + avoid this, a separate set of DNS routines were written for the + current implementation. Routines were setup for non-blocking IO + operations with local cache, and then polled from within the main + server IO loop. + +5.9.2 Username (Ident) lookups + + Although there are numerous ident libraries (implementing the + "Identification Protocol" [IDENT]) for use and inclusion into other + programs, these caused problems since they operated in a synchronous + manner and resulted in frequent delays. Again the solution was to + write a set of routines which would cooperate with the rest of the + server and work using non-blocking IO. + +6. Current problems + + There are a number of recognized problems with this protocol, all of + which are hoped to be solved sometime in the near future during its + rewrite. Currently, work is underway to find working solutions to + these problems. + +6.1 Scalability + + It is widely recognized that this protocol does not scale + sufficiently well when used in a large arena. The main problem comes + from the requirement that all servers know about all other servers + and clients and that information regarding them be updated as soon as + it changes. It is also desirable to keep the number of servers low + so that the path length between any two points is kept minimal and + the spanning tree as strongly branched as possible. + +6.2 Labels + + The current IRC protocol has 4 types of labels: the nickname, the + channel name, the server name and the service name. Each of the four + types has its own domain and no duplicates are allowed inside that + domain. Currently, it is possible for users to pick the label for + any of the first three, resulting in collisions. It is widely + recognized that this needs reworking, with a plan for unique names + for nicks that don't collide being desirable as well as a solution + allowing a cyclic tree. + +6.2.1 Nicknames + + The idea of the nickname on IRC is very convenient for users to use + when talking to each other outside of a channel, but there is only a + finite nickname space and being what they are, it's not uncommon for + several people to want to use the same nick. If a nickname is chosen + by two people using this protocol, either one will not succeed or + both will be removed by use of KILL (See Section 3.7.1 of "IRC Client + Protocol" [IRC-CLIENT]). + +6.2.2 Channels + + The current channel layout requires that all servers know about all + channels, their inhabitants and properties. Besides not scaling + well, the issue of privacy is also a concern. A collision of + channels is treated as an inclusive event (people from both nets on + channel with common name are considered to be members of it) rather + than an exclusive one such as used to solve nickname collisions. + + This protocol defines "Safe Channels" which are very unlikely to be + the subject of a channel collision. Other channel types are kept for + backward compatibility. + +6.2.3 Servers + + Although the number of servers is usually small relative to the + number of users and channels, they too are currently REQUIRED to be + known globally, either each one separately or hidden behind a mask. + +6.3 Algorithms + + In some places within the server code, it has not been possible to + avoid N^2 algorithms such as checking the channel list of a set of + clients. + + In current server versions, there are only few database consistency + checks, most of the time each server assumes that a neighbouring + server is correct. This opens the door to large problems if a + connecting server is buggy or otherwise tries to introduce + contradictions to the existing net. + + Currently, because of the lack of unique internal and global labels, + there are a multitude of race conditions that exist. These race + conditions generally arise from the problem of it taking time for + messages to traverse and effect the IRC network. Even by changing to + unique labels, there are problems with channel-related commands being + disrupted. + +7. Security Considerations + +7.1 Authentication + + Servers only have two means of authenticating incoming connections: + plain text password, and DNS lookups. While these methods are weak + and widely recognized as unsafe, their combination has proven to be + sufficient in the past: + + * public networks typically allow user connections with only few + restrictions, without requiring accurate authentication. + + * private networks which operate in a controlled environment often + use home-grown authentication mechanisms not available on the + internet: reliable ident servers [IDENT], or other proprietary + mechanisms. + + The same comments apply to the authentication of IRC Operators. + + It should also be noted that while there has been no real demand over + the years for stronger authentication, and no real effort to provide + better means to safely authenticate users, the current protocol + offers enough to be able to easily plug-in external authentication + methods based on the information that a client can submit to the + server upon connection: nickname, username, password. + +7.2 Integrity + + Since the PASS and OPER messages of the IRC protocol are sent in + clear text, a stream layer encryption mechanism (like "The TLS + Protocol" [TLS]) could be used to protect these transactions. + +8. Current support and availability + + Mailing lists for IRC related discussion: + General discussion: ircd-users@irc.org + Protocol development: ircd-dev@irc.org + + Software implementations: + ftp://ftp.irc.org/irc/server + ftp://ftp.funet.fi/pub/unix/irc + ftp://coombs.anu.edu.au/pub/irc + + Newsgroup: alt.irc + +9. Acknowledgements + + Parts of this document were copied from the RFC 1459 [IRC] which + first formally documented the IRC Protocol. It has also benefited + from many rounds of review and comments. In particular, the + following people have made significant contributions to this + document: + + Matthew Green, Michael Neumayer, Volker Paulsen, Kurt Roeckx, Vesa + Ruokonen, Magnus Tjernstrom, Stefan Zehl. + +10. References + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [IRC] Oikarinen, J. and D. Reed, "Internet Relay Chat + Protocol", RFC 1459, May 1993. + + [IRC-ARCH] Kalt, C., "Internet Relay Chat: Architecture", RFC 2810, + April 2000. + + [IRC-CLIENT] Kalt, C., "Internet Relay Chat: Client Protocol", RFC + 2812, April 2000. + + [IRC-CHAN] Kalt, C., "Internet Relay Chat: Channel Management", RFC + 2811, April 2000. + + [ZLIB] Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data + Format Specification version 3.3", RFC 1950, May 1996. + + [DEFLATE] Deutsch, P., "DEFLATE Compressed Data Format + Specification version 1.3", RFC 1951, May 1996. + + [GZIP] Deutsch, P., "GZIP file format specification version + 4.3", RFC 1952, May 1996. + + [IDENT] St. Johns, M., "The Identification Protocol", RFC 1413, + February 1993. + + [TLS] Dierks, T. and C. Allen, "The TLS Protocol", RFC 2246, + January 1999. + +11. Author's Address + + Christophe Kalt + 99 Teaneck Rd, Apt #117 + Ridgefield Park, NJ 07660 + USA + + EMail: kalt@stealth.net + +12. Full Copyright Statement + + Copyright (C) The Internet Society (2000). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. diff --git a/docs/technical/ts6-charybdis.txt b/docs/technical/ts6-charybdis.txt new file mode 100644 index 0000000..bc0c03a --- /dev/null +++ b/docs/technical/ts6-charybdis.txt @@ -0,0 +1,1255 @@ +=== NOTE NOTE NOTE === +This is mostly irrelevant to us as our IRCd +is supposed to be compatible with ircd-hybrid, +not charybdis. However, this file explains +some things better than the doc in ircd-hybrid, +therefore I'm putting it here +=== NOTE NOTE NOTE === + + +TS6 protocol description +Written by Jilles Tjoelker +Edits by Elizabeth Myers to add TS rules described by Lee Harvey. + +General format: much like rfc1459 +Maximum parameters for a command: 15 (this does not include the prefix +and command name) + +SID: a server's unique ID. It is configured in each server and consists of +a digit and two alphanumerics. Sending SIDs with lowercase letters is +questionable. + +UID: a client's unique ID. It consists of the server's SID and six +alphanumerics (so it is nine characters long). The first of the alphanumerics +should be a letter, numbers are legal but reserved for future use. + +hunted: a parameter type used for various remote requests. From local users, +nicknames and server names are accepted, possibly with wildcards; from servers, +UIDs/SIDs (sending names or even wildcards is deprecated). This is done with +the function hunt_server(). Any rate limiting should be done locally. + + +duration: a parameter type used for ban durations. It is a duration in seconds. +A value of 0 means a permanent ban. + +IP addresses: IP addresses are converted to text in the usual way, including +'::' shortening in IPv6, with the exception that a zero is prepended to any +IP address that starts with a colon. + +propagation: to which other servers the command is sent + +For all commands with a hunted parameter, the propagation is determined by +that, and not otherwise specified. + +For all commands with a target server mask parameter, the propagation is +determined by that, and not otherwise specified. The command is sent to all +servers with names matching the given mask (for example '*', '*.example.com', +'irc.example.com'). Those servers do not have to be directly connected. +Targets cannot be SIDs. + +Propagation broadcast means the command is sent to all servers. + +Propagation one-to-one means the command is only sent to the target or the +server the target is on. + +Propagation none means the command is never sent to another server if it is +received. + +For some other commands, the propagation depends on the parameters and is +described in text. + +services server: server mentioned in a service{} block. There are no services +servers on EFnet. + +service: client with umode +S. This implies that it is on a services server. + +connection setup: +The initiator sends the PASS, CAPAB and SERVER messages. Upon receiving the +SERVER, the listener will check the information, and if it is valid, it will +send its own PASS, CAPAB and SERVER messages, followed by SVINFO and the burst. +Upon receiving the SERVER, the initiator will send SVINFO and the burst. If +ziplinks are used, SVINFO is the first compressed message. + +The burst consists of SID and SERVER messages for all known servers, BAN +messages for all propagated bans, UID or EUID messages for all known users +(possibly followed by ENCAP REALHOST, ENCAP LOGIN and/or AWAY) and SJOIN +messages for all known channels (possibly followed by BMASK and/or TB). + +user modes: +(all) ++D (deaf: does not receive channel messages) ++S (network service) (only settable on burst from a services server) ++a (appears as server administrator) ++i (invisible, see rfc1459) ++o (IRC operator, see rfc1459) ++w (wallops, see rfc1459) (always propagated for historical reasons) +(charybdis TS6) ++Q/+R/+g/+l/+s/+z (only locally effective) ++Z (ssl user) (only settable on burst) +possibly more added by modules + +channel modes: +(all) +statuses ++o (prefix @) (ops) ++v (prefix +) (voice) +type A ++b (ban) ++e (ban exception) (capab: EX) ++I (invite exception) (capab: IE) +type B ++k (key: password required to join, <= 23 ascii chars, no : or , or whitespace) +type C ++l (limit: maximum number of members before further joins are disallowed) +type D ++m (moderated) ++n (no external messages) ++p (private: does not appear in /whois to non-members, no /knock allowed) ++r (only registered users may join) (only if a services server exists) (capab: SERVICES) ++s (secret) ++t (only chanops may change topic) +(charybdis TS6) +type A ++q (quiet) +type C ++f (forward: channel name <= 30 chars) ++j (join throttle: N:T with integer N and T) +type D ++F (free target for +f) ++L (large ban list) ++P (permanent: does not disappear when empty) ++Q (ignore forwards to this) ++c (strip colours) ++g (allow any member to /invite) ++z (send messages blocked by +m to chanops) + +Nick TS rules: +A server receiving a command that requires nick TS rules must check for a +collision between an existing user, and the nick in the received message. +(the "new user"). The collisions must obey the rules specified in Nick TS +collisions. + +If the TS received is lower than the TS of the existing user the server will +collide the existing user if the clients user@host are different, if the +clients user@hosts are identical it will collide the new user. + +If the TS received is equal to the TS of the existing user both clients are +collided. + +If the TS received is higher than the TS of the existing user, the server +will collide the existing user if the user@hosts are identical, if the +clients user@host are different it will collide the new user and drop the +message. + +Nick TS collisions: +If both users are to be collided, we must issue a KILL for the existing +user to all servers. If the new user has a UID then we must also issue a +KILL for that UID back to the server sending us data causing the collision. + +If only the existing user is being collided, we must issue a KILL for the +existing user to all servers except the server sending us data. If the +existing user has a UID and the server sending us data supports TS6 then +we must also issue a KILL for the existing users UID to the server sending +us data. + +If only the new user is being collided, we must issue a KILL for the new user +back to the server sending us data if the new user has a UID. + +Channel TS rules: +A server receiving a command that requires normal channel TS rules must +apply the following rules to the command. + +If the TS received is lower than our TS of the channel a TS6 server must +remove status modes (+ov etc) and channel modes (+nt etc). If the +originating server is TS6 capable (ie, it has a SID), the server must +also remove any ban modes (+b etc). The new modes and statuses are then +accepted. + +If any bans are removed, the server must send to non-TS6, directly connected +servers mode changes removing the bans after the command is propagated. +This prevents desync with banlists, and has to be sent after as clients are +still able to send mode changes before the triggering command arrives. + +If the TS received is equal to our TS of the channel the server should keep +its current modes and accept the received modes and statuses. + +If the TS received is higher than our TS of the channel the server should keep +its current modes and ignore the received modes and statuses. Any statuses +given in the received message will be removed. A server must mark clients +losing their op (+o) status who do not have a UID as 'deopped'. A server must +ignore any "MODE" commands from a user marked as 'deopped'. + +Simple channel TS rules: + +A server receiving a command that requires simple channel TS rules must +apply the following rules to the command. + +If the TS received is lower, or equal to our TS of the channel the modes are +accepted. If the TS received is higher than our TS of the channel the modes +are ignored and dropped. + +Simple channel TS rules do not affect current modes in the channel except +for the modes we are accepting. + + + +source: server +parameters: target, any... + +The command name should be three decimal ASCII digits. + +Propagates a "numeric" command reply, such as from a remote WHOIS request. + +If the first digit is 0 (indicating a reply about the local connection), it +should be changed to 1 before propagation or sending to a user. + +Numerics to the local server may be sent to opers. + +To avoid infinite loops, servers should not send any replies to numerics. + +The target can be: +- a client + propagation: one-to-one +- a channel name + propagation: all servers with -D users on the channel + +Numerics to channels are broken in some older servers. + +ADMIN +source: user +parameters: hunted + +Remote ADMIN request. + +AWAY +source: user +propagation: broadcast +parameters: opt. away reason + +If the away reason is empty or not present, mark the user as not away. +Otherwise, mark the user as away. + +Changing away reason from one non-empty string to another non-empty string +may not be propagated. + +BAN +charybdis TS6 +capab: BAN +source: any +propagation: broadcast (restricted) +parameters: type, user mask, host mask, creation TS, duration, lifetime, oper, reason + +Propagates a network wide ban. + +The type is K for K:lines, R for resvs and X for X:lines; other types are +reserved. The user mask field is only used for K:lines; for resvs and X:lines +the field is ignored in input and sent as an asterisk. + +The creation TS indicates when this ban was last modified. An incoming ban MUST +be ignored and not propagated if the creation TS is older than the creation TS +of the current ban. If the ban is identical, it SHOULD NOT be propagated to +avoid unnecessary network traffic. (Two changes to bans that set the TS to the +same value may cause desynchronization.) + +The duration is 0 for an unban and relative to the creation TS for a ban. +When the duration has passed, the ban is no longer active but it may still +be necessary to remember it. + +The lifetime is relative to the creation TS and indicates for how long this +ban needs to be remembered and propagated. This MUST be at least the duration. +Initially, it is usually set the same as the duration but when the ban is +modified later, it SHOULD be set such that the modified ban is remembered at +least as long as the original ban. This ensures that the original ban does not +revive via split servers. This requirement is only a SHOULD to allow for +implementations that only inject bans and do not remember any; implementations +that remember and propagate bans MUST set the lifetime appropriately. + +The oper field indicates the oper that originally set the ban. If this message +is the initial propagation of a change, it SHOULD be sent as * (an asterisk). + +The reason field indicates the reason for the ban. Any part after a | (vertical +bar) MUST NOT be shown to normal users. The rest of the field and the creation +TS and duration MAY be shown to normal users. + +BMASK +source: server +propagation: broadcast +parameters: channelTS, channel, type, space separated masks + +If the channelTS in the message is greater (newer) than the current TS of +the channel, drop the message and do not propagate it. + +Type is the mode letter of a ban-like mode. In efnet TS6 this is 'b', 'e' or +'I'. In charybdis TS6 additionally 'q' is possible. + +Add all the masks to the given list of the channel. + +All ban-like modes must be bursted using this command, not using MODE or TMODE. + +CAPAB +source: unregistered server +propagation: none +parameters: space separated capability list + +Sends capabilities of the server. This must include QS and ENCAP, and for +charybdis TS6 also EX and IE. It is also strongly recommended to include EX, +CHW, IE and KNOCK, and for charybdis TS6 also SAVE and EUID. For use with +services, SERVICES and RSFNC are strongly recommended. + +The capabilities may depend on the configuration for the server they are sent +to. + +CHGHOST +charybdis TS6 +source: any +propagation: broadcast +parameters: client, new hostname + +Changes the visible hostname of a client. + +Opers are notified unless the source is a server or a service. + +CONNECT +source: any +parameters: server to connect to, port, hunted + +Remote connect request. A server WALLOPS should be sent by the receiving +server. + +The port can be 0 for the default port. + +DLINE +charybdis TS6 +encap only +source: user +parameters: duration, mask, reason + +Sets a D:line (IP ban checked directly after accepting connection). + +The mask must be an IP address or CIDR mask. + +ENCAP +source: any +parameters: target server mask, subcommand, opt. parameters... + +Sends a command to matching servers. Propagation is independent of +understanding the subcommand. + +Subcommands are listed elsewhere with "encap only". + +ERROR +source: server or unregistered server +propagation: none +parameters: error message + +Reports a (usually fatal) error with the connection. + +Error messages may contain IP addresses and have a negative effect on server +IP hiding. + +ETB +capab: EOPMOD +source: any +propagation: broadcast +parameters: channelTS, channel, topicTS, topic setter, opt. extensions, topic + +Propagates a channel topic change or propagates a channel topic as part of a +burst. + +If the channel had no topic yet, the channelTS in the message is lower (older) +than the current TS of the channel, or the channelTSes are equal and the +topicTS in the message is newer than the topicTS of the current topic on the +channel, set the topic with topicTS and topic setter, and propagate the +message. Otherwise ignore the message and do not propagate it. + +Unlike a TB message, an ETB message can change the topicTS without changing +the topic text. In this case, the message should be propagated to servers but +local users should not be notified. + +Services can send a channelTS of 0 to force restoring an older topic (unless +the channel's TS is 0). Therefore, the channelTS should be propagated as given +and should not be replaced by the current TS of the channel. + +An ETB message with a newer channelTS can still set a topic on a channel +without topic. This corresponds to SJOIN not clearing the topic when lowering +TS on a channel. + +If ETB comes from a user, it can be propagated to non-EOPMOD servers using +TOPIC, TB or a combination of TOPIC to clear the topic and TB to set a new +topic with topicTS. However, this can be somewhat noisy. On the other hand, if +ETB comes from a server, there is no way to force setting a newer topicTS. It +is possible to set the topic text but the incorrect topicTS may lead to desync +later on. + +This document does not document the optional extensions between topic setter +and topic. + +ETRACE +encap only +encap target: single server +source: oper +parameters: client + +Remote ETRACE information request. + +EUID +charybdis TS6 +capab: EUID +source: server +parameters: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address, UID, real hostname, account name, gecos +propagation: broadcast + +Introduces a client. The client is on the source server of this command. + +The IP address MUST be '0' (a zero) if the true address is not sent such as +because of a spoof. Otherwise, and if there is no dynamic spoof (i.e. the +visible and real hostname are equal), the IP address MAY be shown to normal +users. + +The account name is '*' if the user is not logged in with services. + +Nick TS rules apply. + +EUID is similar to UID but includes the ENCAP REALHOST and ENCAP LOGIN +information. + +GCAP +encap only +encap target: * +source: server +parameters: space separated capability list + +Capability list of remote server. + +GLINE +efnet TS6 +capab: GLN +source: user +parameters: user mask, host mask, reason +propagation: broadcast + +Propagates a G:line vote. Once votes from three different opers (based on +user@host mask) on three different servers have arrived, trigger the G:line. +Pending G:lines expire after some time, usually ten minutes. Triggered G:lines +expire after a configured time which may differ across servers. + +Requests from server connections must be propagated, unless they are found to +be syntactically invalid (e.g. '!' in user mask). Therefore, disabling glines +must not affect propagation, and too wide glines, double votes and glines that +already exist locally must still be propagated. + +Of course, servers are free to reject gline requests from their own operators. + +GUNGLINE +efnet TS6 +encap only +encap target: * +source: user +parameters: user mask, host mask, reason +propagation: broadcast + +Propagates a G:line removal vote. Once three votes have arrived (as with +G:lines), remove the G:line. Pending G:lines removals expire after some time, +usually ten minutes. + +Pending G:line removals do not interact with pending G:lines. Triggering a +G:line does not affect a pending G:line removal. Triggering a G:line removal +does not affect a pending G:line. + +INFO +source: user +parameters: hunted + +Remote INFO request. + +INVITE +source: user +parameters: target user, channel, opt. channelTS +propagation: one-to-one + +Invites a user to a channel. + +If the channelTS is greater (newer) than the current TS of the channel, drop +the message. + +Not sending the channelTS parameter is deprecated. + +JOIN +1. +source: user +parameters: '0' (one ASCII zero) +propagation: broadcast + +Parts the source user from all channels. + +2. +source: user +parameters: channelTS, channel, '+' (a plus sign) +propagation: broadcast + +Joins the source user to the given channel. If the channel does not exist yet, +it is created with the given channelTS and no modes. If the channel already +exists and has a greater (newer) TS, wipe all simple modes and statuses and +change the TS, notifying local users of this but not servers (note that +ban-like modes remain intact; invites may or may not be cleared). + +A JOIN is propagated with the new TS of the channel. + +JUPE +capab: JUPE +source: any +propagation: broadcast (restricted) +parameters: target server mask, add or delete, server name, oper, reason + +Adds or removes a jupe for a server. If the server is presently connected, +it MUST be SQUIT by the server's uplink when the jupe is applied. + +The oper field indicates the oper that originally set the jupe. If this message +is the initial propagation of a removal, it SHOULD be sent as * (an asterisk). + +The reason field indicates the reason for the jupe. It SHOULD be displayed +as the linking error message to the juped server if it tries to reconnect. + +KICK +source: any +parameters: channel, target user, opt. reason +propagation: broadcast + +Kicks the target user from the given channel. + +Unless the channel's TS is 0, no check is done whether the source user has ops. + +Not sending the reason parameter is questionable. + +KILL +source: any +parameters: target user, path +propagation: broadcast + +Removes the user from the network. + +The format of the path parameter is some sort of description of the source of +the kill followed by a space and a parenthesized reason. To avoid overflow, +it is recommended not to add anything to the path. + +KLINE +1. +encap only +source: user +parameters: duration, user mask, host mask, reason + +Sets a K:line (ban on user@host). + +2. +capab: KLN +source: user +parameters: target server mask, duration, user mask, host mask, reason + +As form 1, deprecated. + +KNOCK +capab: KNOCK +source: user +parameters: channel +propagation: broadcast + +Requests an invite to a channel that is locked somehow (+ikl). Notifies all +operators of the channel. (In charybdis, on +g channels all members are +notified.) + +This is broadcast so that each server can store when KNOCK was used last on +a channel. + +LINKS +source: user +parameters: hunted, server mask + +Remote LINKS request. The server mask limits which servers are listed. + +LOCOPS +1. +encap only +source: user +parameters: text + +Sends a message to operators (with umode +l set). This is intended to be +used for strict subsets of the network. + +2. +capab: CLUSTER +source: user +parameters: target server mask, text + +As form 1, deprecated. + +LOGIN +encap only +source: user +parameters: account name + +In a burst, states that the source user is logged in as the account. + +LUSERS +source: user +parameters: server mask, hunted + +Remote LUSERS request. Most servers ignore the server mask, treating it as '*'. + +MLOCK +charybdis TS6 +source: services server +parameters: channelTS, channel, mode letters +propagation: broadcast (restricted) + +Propagates a channel mode lock change. + +If the channelTS is greater (newer) than the current TS of the channel, drop +the message. + +The final parameter is a list of mode letters that may not be changed by local +users. This applies to setting or unsetting simple modes, and changing or +removing mode parameters. + +An MLOCK message with no modes disables the MLOCK, therefore the MLOCK message +always contains the literal MLOCK for simplicity. + +MODE +1. +source: user +parameters: client, umode changes +propagation: broadcast + +Propagates a user mode change. The client parameter must refer to the same user +as the source. + +Not all umodes are propagated to other servers. + +2. +source: any +parameters: channel, cmode changes, opt. cmode parameters... + +Propagates a channel mode change. + +This is deprecated because the channelTS is not included. If it is received, +it should be propagated as TMODE. + +MOTD +source: user +parameters: hunted + +Remote MOTD request. + +NICK +1. +source: user +parameters: new nickname, new nickTS +propagation: broadcast + +Propagates a nick change. + +2. +source: server +parameters: nickname, hopcount, nickTS, umodes, username, hostname, server, gecos + +Historic TS5 user introduction. The user is on the server indicated by the +server parameter; the source server is meaningless (local link). + +NICKDELAY +charybdis TS6 +encap only +encap target: * +source: services server +parameters: duration, nickname + +If duration is greater than 0, makes the given nickname unavailable for that +time. + +If duration is 0, removes a nick delay entry for the given nickname. + +There may or may not be a client with the given nickname; this does not affect +the operation. + +NOTICE +source: any +parameters: msgtarget, message + +As PRIVMSG, except NOTICE messages are sent out, server sources are permitted +and most error messages are suppressed. + +Servers may not send '$$', '$#' and opers@server notices. Older servers may +not allow servers to send to specific statuses on a channel. + +OPER +source: user +parameters: opername, privset + +Sets the source user's oper name and privset. Sent after the +o mode change, or +during burst, to inform other servers of an oper's privileges. + +OPERSPY +encap only +encap target: * +source: user +parameters: command name, parameters + +Reports operspy usage. + +OPERWALL +source: user +parameters: message +propagation: broadcast + +Sends a message to operators (with umode +z set). + +PART +source: user +parameters: comma separated channel list, message + +Parts the source user from the given channels. + +PASS +source: unregistered server +parameters: password, 'TS', TS version, SID + +Sends the server link password, TS version and SID. + +PING +source: any +parameters: origin, opt. destination server + +Sends a PING to the destination server, which will reply with a PONG. If the +destination server parameter is not present, the server receiving the message +must reply. + +The origin field is not used in the server protocol. It is sent as the name +(not UID/SID) of the source. + +Remote PINGs are used for end-of-burst detection, therefore all servers must +implement them. + +PONG +source: server +parameters: origin, destination + +Routes a PONG back to the destination that originally sent the PING. + +PRIVMSG +source: user +parameters: msgtarget, message + +Sends a normal message (PRIVMSG) to the given target. + +The target can be: +- a client + propagation: one-to-one +- a channel name + propagation: all servers with -D users on the channel + (cmode +m/+n should be checked everywhere, bans should not be checked + remotely) +- a status character ('@'/'+') followed by a channel name, to send to users + with that status or higher only. + capab: CHW + propagation: all servers with -D users with appropriate status +- '=' followed by a channel name, to send to chanops only, for cmode +z. + capab: CHW and EOPMOD + propagation: all servers with -D chanops +- a user@server message, to send to users on a specific server. The exact + meaning of the part before the '@' is not prescribed, except that "opers" + allows IRC operators to send to all IRC operators on the server in an + unspecified format. + propagation: one-to-one +- a message to all users on server names matching a mask ('$$' followed by mask) + propagation: broadcast + Only allowed to IRC operators. +- a message to all users with hostnames matching a mask ('$#' followed by mask). + Note that this is often implemented poorly. + propagation: broadcast + Only allowed to IRC operators. + +In charybdis TS6, services may send to any channel and to statuses on any +channel. + +PRIVS +charybdis TS6 +encap only +encap target: single server +source: oper +parameters: client + +Remote PRIVS information request. + +QUIT +source: user +parameters: comment + +Propagates quitting of a client. No QUIT should be sent for a client that +has been removed as result of a KILL message. + +REALHOST +charybdis TS6 +encap only +encap target: * +source: user +parameters: real hostname + +In a burst, propagates the real host of a dynamically-spoofed user. + +REHASH +charybdis TS6 +encap only +source: user +parameters: opt. rehash type + +Remote REHASH request. If the rehash type is omitted, it is equivalent to +a regular /rehash, otherwise it is equivalent to /rehash . + +RESV +1. +encap only +source: user +parameters: duration, mask, reason + +Sets a RESV, making a nickname mask or exact channel unavailable. + +2. +capab: CLUSTER +source: user +parameters: target server mask, duration, mask, reason + +As form 1, deprecated. + +RSFNC +encap only +capab: RSFNC +encap target: single server +source: services server +parameters: target user, new nickname, new nickTS, old nickTS + +Forces a nickname change and propagates it. + +The command is ignored if the nick TS of the user is not equal to the old +nickTS parameter. If the new nickname already exists (and is not the target +user), it is killed first. + +SASL +charybdis TS6 +encap only +1. +encap target: * +source: server +parameters: source uid, '*', 'S', sasl mechanism name + +Requests that a SASL agent (a service) initiate the authentication process. +The source uid is that of an unregistered client. This is why it is not sent +as the prefix. + +2. +encap target: single server +source: server +parameters: source uid, target uid, mode, data + +Part of a SASL authentication exchange. The mode is 'C' to send some data +(base64 encoded), or 'D' to end the exchange (data indicates type of +termination: 'A' for abort, 'F' for authentication failure, 'S' for +authentication success). + +3. +encap target: * +source: server +parameters: source uid, '*', 'H', hostname, ip, tls + +Provides information on a client. The "tls" data is either 'P' for a +plaintext connection or any other string for a TLS connection. +The source uid is that of an unregistered client. This is why it is not sent +as the prefix. + +SAVE +capab: SAVE +source: server +propagation: broadcast +parameters: target uid, TS + +Resolve a nick collision by changing a nickname to the UID. + +The server should verify that the UID belongs to a registered user, the user +does not already have their UID as their nick and the TS matches the user's +nickTS. If not, drop the message. + +SAVE should be propagated as a regular NICK change to links without SAVE capab. +present. + +SERVER +1. +source: unregistered server +parameters: server name, hopcount, server description + +Registers the connection as a server. PASS and CAPAB must have been sent +before, SVINFO should be sent afterwards. + +If there is no such server configured or authentication failed, the connection +should be dropped. + +This is propagated as a SID message. + +2. +source: server +propagation: broadcast +parameters: server name, hopcount, server description + +Introduces a new TS5 server, directly connected to the source of this command. +This is only used for jupes as TS5 servers may do little else than existing. + +SID +source: server +propagation: broadcast +parameters: server name, hopcount, sid, server description + +Introduces a new server, directly connected to the source of this command. + +SIGNON +source: user +propagation: broadcast +parameters: new nickname, new username, new visible hostname, new nickTS, new login name + +Broadcasts a change of several user parameters at once. + +Currently only sent after an SVSLOGIN. + +SJOIN +source: server +propagation: broadcast +parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist + +Broadcasts a channel creation or bursts a channel. + +The nicklist consists of users joining the channel, with status prefixes for +their status ('@+', '@', '+' or ''), for example: +'@+1JJAAAAAB +2JJAAAA4C 1JJAAAADS'. All users must be behind the source server +so it is not possible to use this message to force users to join a channel. + +The interpretation depends on the channelTS and the current TS of the channel. +If either is 0, set the channel's TS to 0 and accept all modes. Otherwise, if +the incoming channelTS is greater (newer), ignore the incoming simple modes +and statuses and join and propagate just the users. If the incoming channelTS +is lower (older), wipe all modes and change the TS, notifying local users of +this but not servers (invites may be cleared). In the latter case, kick on +split riding may happen: if the key (+k) differs or the incoming simple modes +include +i, kick all local users, sending KICK messages to servers. + +An SJOIN is propagated with the new TS and modes of the channel. The statuses +are propagated if and only if they were accepted. + +SJOIN must be used to propagate channel creation and in netbursts. For regular +users joining channels, JOIN must be used. Pseudoservers may use SJOIN to join +a user with ops. + +SNOTE +charybdis TS6 +encap only +source: server +parameters: snomask letter, text + +Sends the text as a server notice from the source server to opers with the +given snomask set. + +SQUIT +parameters: target server, comment + +Removes the target server and all servers and users behind it from the network. + +If the target server is the receiving server or the local link this came from, +this is an announcement that the link is being closed. + +Otherwise, if the target server is locally connected, the server should send +a WALLOPS announcing the SQUIT. + +STATS +source: user +parameters: stats letter, hunted + +Remote STATS request. Privileges are checked on the server executing the +actual request. + +SU +encap only +encap target: * +source: services server +parameters: target user, new login name (optional) + +If the new login name is not present or empty, mark the target user as not +logged in, otherwise mark the target user as logged in as the given account. + +SVINFO +source: server +propagation: none +parameters: current TS version, minimum TS version, '0', current time + +Verifies TS protocol compatibility and clock. If anything is not in order, +the link is dropped. + +The current TS version is the highest version supported by the source server +and the minimum TS version is the lowest version supported. + +The current time is sent as a TS in the usual way. + +SVSLOGIN +charybdis TS6 +encap only +encap target: single server +source: services server +parameters: target, new nick, new username, new visible hostname, new login name + +Sent after successful SASL authentication. + +The target is a UID, typically an unregistered one. + +Any of the "new" parameters can be '*' to leave the corresponding field +unchanged. The new login name can be '0' to log the user out. + +If the UID is registered on the network, a SIGNON with the changes will be +broadcast, otherwise the changes will be stored, to be used when registration +completes. + +TB +capab: TB +source: server +propagation: broadcast +parameters: channel, topicTS, opt. topic setter, topic + +Propagates a channel topic as part of a burst. + +If the channel had no topic yet or the topicTS in the message is older than +the topicTS of the current topic on the channel and the topics differ, set +the topic with topicTS and topic setter, and propagate the message. Otherwise +ignore the message and do not propagate it. + +If the topic setter is not present, use a server name instead. + +TIME +source: user +parameters: hunted + +Remote TIME request. + +TMODE +source: any +parameters: channelTS, channel, cmode changes, opt. cmode parameters... + +Propagates a channel mode change. + +If the channelTS is greater (newer) than the current TS of the channel, drop +the message. + +On input, only the limit on parameters per line restricts how many cmode +parameters can be present. Apart from this, arbitrary modes shall be +processed. Redundant modes may be dropped. For example, +n-n may be applied and +propagated as +n-n, -n or (if the channel was already -n) nothing, but not as ++n. + +The parameter for mode -k (removing a key) shall be ignored. + +On output, at most ten cmode parameters should be sent; if there are more, +multiple TMODE messages should be sent. + +TOPIC +source: user +propagation: broadcast +parameters: channel, topic + +Propagates a channel topic change. The server may verify that the source has +ops in the channel. + +The topicTS shall be set to the current time and the topic setter shall be +set indicating the source user. Note that this means that the topicTS of a +topic set with TOPIC is not necessarily consistent across the network. + +TRACE +source: user +1. +parameters: hunted + +Performs a trace to the target, sending 200 numerics from each server passing +the message on. The target server sends a description of the target followed +by a 262 numeric. + +TRACE, STATS l and STATS L are the only commands using hunt_server that use the +hunted parameter for more than just determining which server the command +should be executed on. + +2. +parameters: target name, hunted + +Executes a trace command on the target server. No 200 numerics are sent. +The target name is a name, not a UID, and should be on the target server. + +UID +source: server +propagation: broadcast +parameters: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address, UID, gecos +propagation: broadcast + +Introduces a client. The client is on the source server of this command. + +The IP address MUST be '0' (a zero) if the true address is not sent such as +because of a spoof. Otherwise, and if there is no dynamic spoof (ENCAP +REALHOST, charybdis TS6 only), the IP address MAY be shown to normal users. + +Nick TS rules apply. + +UNDLINE +charybdis TS6 +encap only +source: user +parameters: mask + +Removes a D:line (IP ban checked directly after accepting connection). + +The mask must be an IP address or CIDR mask. + +UNKLINE +1. +encap only +source: user +parameters: user mask, host mask + +Removes a K:line (ban on user@host). + +2. +capab: UNKLN +source: user +parameters: target server mask, user mask, host mask + +As form 1, deprecated. + +UNRESV +1. +encap only +source: user +parameters: mask + +Removes a RESV. + +2. +capab: CLUSTER +source: user +parameters: target server mask, mask + +As form 1, deprecated. + +UNXLINE +1. +encap only +source: user +parameters: mask + +Removes an X:line (ban on realname). + +2. +capab: CLUSTER +source: user +parameters: target server mask, mask + +As form 1, deprecated. + +USERS +source: user +parameters: hunted + +Remote USERS request. + +VERSION +source: any +parameters: hunted + +Remote VERSION request. + +WALLOPS +1. +source: user +parameters: message +propagation: broadcast + +In efnet TS6, sends a message to operators (with umode +z set). This is a +deprecated equivalent to OPERWALL. + +In charybdis TS6, sends a message to local users with umode +w set (or possibly +another indication that WALLOPS messages should be sent), including non-opers. + +2. +source: server +parameters: message +propagation: broadcast + +Sends a message to local users with umode +w set (or possibly another +indication that WALLOPS messages should be sent). + +In efnet TS6 this may include non-opers, in charybdis TS6 this may only be +sent to opers. + +WHOIS +source: user +parameters: hunted, target nick + +Remote WHOIS request. + +WHOWAS +source: user +parameters: nickname, limit, hunted + +Remote WHOWAS request. Not implemented in all servers. + +Different from a local WHOWAS request, the limit is mandatory and servers should +apply a maximum to it. + +XLINE +1. +encap only +source: user +parameters: duration, mask, reason + +Sets an X:line (ban on realname). + +2. +capab: CLUSTER +source: user +parameters: target server mask, duration, mask, reason + +As form 1, deprecated. + +Local only commands (charybdis 3.1): + +ACCEPT +AUTHENTICATE +CAP +CHALLENGE +CHANTRACE +CLOSE +DIE +GET +HELP +ISON +LIST +MAP +MASKTRACE +MODLIST +MODLOAD +MODRELOAD +MODRESTART +MODUNLOAD +MONITOR +NAMES +POST +PUT +RESTART +SCAN +SET +TESTGECOS +TESTLINE +TESTMASK +UHELP +UNREJECT +USER +USERHOST +WEBIRC +WHO +WHOWAS diff --git a/docs/technical/ts6-hybrid.txt b/docs/technical/ts6-hybrid.txt new file mode 100644 index 0000000..d3e5063 --- /dev/null +++ b/docs/technical/ts6-hybrid.txt @@ -0,0 +1,296 @@ +TS6 Proposal (v8) +Written by Lee H +Ideas borrowed heavily from ircnet (Beeth, jv, Q) + +- Changes between v7 and v8 - +----------------------------- + +In the v7 specification, the JOIN command included the channel modes of a +channel, and acted on them following TS rules. In the v8 specification, +JOIN will never send modes. + +Desyncs can occur both when they are sent and when they are not. If they +are sent, then you can have a situation where a user on one side of the +network issues "MODE #channel -l", and a user on another side of the network +issues "JOIN #channel" whilst the +l still exists. As the JOIN string sent +server<->server includes the full modes at the time of the user joining, +this will propagate the +l, but there is a -l crossing in the other +direction. Desync will occur beyond where they intersect. + +If the modes are not sent, then a lower TS JOIN command, or a JOIN command +that creates a channel will cause a desync. + +It is judged that the desync with sending the modes is worse than the desync +by not sending them, as such the v8 specification dictates modes are not +sent with a JOIN command server<->server. + +The v8 specification also clarifies that servers may issue TMODE. + +- Introduction - +---------------- + +This document aims to fix some of the flaws that are still present in the +current TS system. + +Whilst only one person may use a nickname at any one time, they are not +a reliable method of directing commands between servers. Clients can change +their nicknames, which can create desyncs. A reliable method of directing +messages between servers is required so that a message will always reach the +intended destination, even if the client changes nicks in between. + +UID solves this problem by ensuring that a client has a unique ID for the +duration of his connection. + +This document also aims to solve the lack of TS rules to channel 'bans' on +a netburst. Bans from both sides of a TS war (losing/winning) are kept. +Bursting the bans with a TS solves this problem. + +There is also a race condition in the current TS system, where a user can +issue a mode during a netburst and the mode will be set on the server +we are bursting to. + + +- Definitions - +--------------- + +Throughout this document, the following terms are used: + +SID - A servers unique ID. This is three characters long and must be in + the form [0-9][A-Z0-9][A-Z0-9] +ID - A clients unique ID. This is six characters long and must be in + the form [A-Z][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]. The + numbers [0-9] at the beginning of an ID are legal characters, but + reserved for future use. +UID - An ID concateneted to a SID. This forms the clients UID. +TS6 - The TS version 6. + + +- Support - +----------- + +Support for this document is given by the TS version 6. + +Wherever a destination parameter or source parameter is used, it must use +the SID or UID if the server/client has one. A TS6 capable server must +translate any SIDs/UIDs back into the server/clients name when communicating +with a server that does not support TS6. + +A TS6 server must also support the QS (quitstorm) system, and the encap +specification found here: +http://www.leeh.co.uk/ircd/encap.txt + +The TS6 protocol does not supports masked entities. + + +- Nick TS rules - +----------------- + +A server receiving a command that requires nick TS rules must check for a +collision between an existing user, and the nick in the received message. +(the "new user"). The collisions must obey the rules specified in Nick TS +collisions. + +If the TS received is lower than the TS of the existing user the server will +collide the existing user if the clients user@host are different, if the +clients user@hosts are identical it will collide the new user. + +If the TS received is equal to the TS of the existing user both clients are +collided. + +If the TS received is higher than the TS of the existing user, the server +will collide the existing user if the user@hosts are identical, if the +clients user@host are different it will collide the new user and drop the +message. + + +- Nick TS collisions - +---------------------- + +If both users are to be collided, we must issue a KILL for the existing +user to all servers. If the new user has a UID then we must also issue a +KILL for that UID back to the server sending us data causing the collision. + +If only the existing user is being collided, we must issue a KILL for the +existing user to all servers except the server sending us data. If the +existing user has a UID and the server sending us data supports TS6 then +we must also issue a KILL for the existing users UID to the server sending +us data. + +If only the new user is being collided, we must issue a KILL for the new user +back to the server sending us data if the new user has a UID. + + +- Channel TS rules - +-------------------- + +A server receiving a command that requires normal channel TS rules must +apply the following rules to the command. + +If the TS received is lower than our TS of the channel a TS6 server must +remove status modes (+ov etc) and channel modes (+nt etc). If the +originating server is TS6 capable (ie, it has a SID), the server must +also remove any ban modes (+b etc). The new modes and statuses are then +accepted. + +If any bans are removed, the server must send to non-TS6, directly connected +servers mode changes removing the bans after the command is propagated. +This prevents desync with banlists, and has to be sent after as clients are +still able to send mode changes before the triggering command arrives. + +If the TS received is equal to our TS of the channel the server should keep +its current modes and accept the received modes and statuses. + +If the TS received is higher than our TS of the channel the server should keep +its current modes and ignore the received modes and statuses. Any statuses +given in the received message will be removed. A server must mark clients +losing their op (+o) status who do not have a UID as 'deopped'. A server must +ignore any "MODE" commands from a user marked as 'deopped'. + + +- Simple channel TS rules - +--------------------------- + +A server receiving a command that requires simple channel TS rules must +apply the following rules to the command. + +If the TS received is lower, or equal to our TS of the channel the modes are +accepted. If the TS received is higher than our TS of the channel the modes +are ignored and dropped. + +Simple channel TS rules do not affect current modes in the channel except +for the modes we are accepting. + + +- The following commands are defined here as the TS6 protocol - +--------------------------------------------------------------- + +- PASS - + PASS TS : + +This command is used for password verification with the server we are +connecting to. + +Due to the burst being sent on verification of the "SERVER" command, and +"SVINFO" being sent after "SERVER", we need to be aware of the TS version +earlier to decide whether to send a TS6 burst or not. + +The field is the password we have stored for this server, + is our current TS version. If this field is not present then +the server does not support TS6. is the SID of the server. + +- UID - + : UID + : + +This command is used for introducing clients to the network. + +The field is the SID of the server the client is connected to. +The field is the nick of the client being introduced. The +field is the amount of server hops between the server being burst to and +the server the client is on. The field is the TS of the client, either +the time they connected or the time they last changed nick. The +field contains the clients usermodes that need to be transmitted between +servers. The field contains the clients username/ident. The + field contains the clients host. + +The field contains the clients IP. If the IP is not to be sent +(due to a spoof etc), the field must be sent as "0". The field is the +clients UID. The field is the clients gecos. + +A server receiving a UID command must apply nick TS rules to the nick. + +- SID - + : SID : + +This command is used for introducing servers to the network. + +The first field is the SID of the new servers uplink. The + field is the new servers name. The field is the hops +between the server being introduced nd the server being burst to. + +The second field is the SID of the new server. The field i +is the new servers gecos. + +Upon receiving the SID command servers must check for a SID collision. +Two servers must not be allowed to link to the network with the same SID. +If a server detects a SID collision it must drop the link to the directly +connected server through which the command was received. + +Client and servers which do not have a UID/SID must be introduced by old +methods. + +- SJOIN - + : SJOIN + : + +This command is used for introducing users to channels. + +The field is the SID of the server introducing users to the channel. +The field is the channels current TS, is the channels +current name, are the channels current modes. is a +space delimited list of clients UIDs to join to the channel. Each clients +UID is prefixed with their status on the channel, ie "@UID" for an opped +user. Multiple prefixes are allowed, "peons" (clients without a status) are +not prefixed. + +A server receiving an SJOIN must apply normal channel TS rules to the SJOIN. + +A TS6 server must not use the SJOIN command outside of a netburst +to introduce a single user to an existing channel. It must instead +use the "JOIN" command defined in this specification. A TS6 server must +still use SJOIN for creating channels. + +- JOIN - + : JOIN + + +This command is used for introducing one user unopped to an existing channel. + +The field is the UID of the client joining the channel. The + field is the channels current TS, is the channels +current name. + +A server receiving a JOIN must apply normal channel TS rules to the JOIN. + +No channel modes are sent with the JOIN command. In previous versions of +this specification, the "+" parameter contained the channels current modes. +A server following this version of the specification must not interpret this +argument and must not propagate any value other than "+" for this parameter. + +It should be noted that whilst JOIN would not normally create a +channel or lower the timestamp, during specific conditions it can. This +can create a desync that this specification does not rectify. + +- BMASK - + : BMASK : + +This command is used for bursting channel bans to a network. + +The field is the SID of the server bursting the bans. The + field is the channels current TS, is the channels +name. is a single character identifying the mode type (ie, +for a ban 'b'). is a space delimited list of masks of the +given mode,limited only in length to the size of the buffer as defined +by RFC1459. + +A server receiving a BMASK must apply simple channel TS rules to the BMASK. + +A TS6 server must translate BMASKs into raw modes for non-TS6 +capable servers. This command must be used only after SJOIN has +been sent for the given channel. + +It should be noted however, that a BMASK with a lower TS should +not be possible without a desync, due to it being sent after +SJOIN. + +- TMODE - + : TMODE + +This command is used for clients issuing modes on a channel. + + is either the UID of the client setting the mode, or the SID of +the server setting the mode. is the current TS of the channel, + is the channels name. is the raw mode the client is +setting. + +A server receiving a TMODE must apply simple channel TS rules to the TMODE. + +A TS6 server must translate MODEs issued by a local client, or received from +a server into TMODE to send to other TS6 capable servers. diff --git a/examples/example_config.toml b/examples/example_config.toml index b09e747..385ec4b 100644 --- a/examples/example_config.toml +++ b/examples/example_config.toml @@ -3,3 +3,5 @@ port = 6667 server_hostname = "irc.foo.bar" network_name = "MyCoolFooNet" # this SHOULDN'T HAVE SPACES! operators = [] +server_incoming_passwords = ["unimpl"] +server_outgoing_password = "root" diff --git a/src/commands/cap.rs b/src/commands/cap.rs index b13135a..cb37f1a 100644 --- a/src/commands/cap.rs +++ b/src/commands/cap.rs @@ -14,7 +14,10 @@ impl IrcHandler for Cap { _arguments: Vec, _authenticated: bool, _user_state: &mut User, - ) -> super::IrcAction { - IrcAction::DoNothing + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { + vec![IrcAction::DoNothing] } } diff --git a/src/commands/join.rs b/src/commands/join.rs index 1dc9775..e42c400 100644 --- a/src/commands/join.rs +++ b/src/commands/join.rs @@ -16,7 +16,10 @@ impl IrcHandler for Join { arguments: Vec, authenticated: bool, user_state: &mut User, - ) -> super::IrcAction { + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { let mut joined_channels = JOINED_CHANNELS.lock().await; let mut channels = Vec::new(); @@ -28,7 +31,7 @@ impl IrcHandler for Join { } if !authenticated { - return IrcAction::ErrorAuthenticateFirst; + return vec![IrcAction::ErrorAuthenticateFirst]; } for existing_channel in joined_channels.clone() { @@ -53,6 +56,6 @@ impl IrcHandler for Join { } } - IrcAction::JoinChannels(channels) + vec![IrcAction::JoinChannels(channels)] } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a1bf566..cabedb2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,11 +8,12 @@ use crate::{ SENDER, channels::Channel, commands::{ - cap::Cap, join::Join, nick::Nick, ping::Ping, privmsg::PrivMsg, user::User as UserHandler, - who::Who, + cap::Cap, join::Join, nick::Nick, pass::Pass, ping::Ping, privmsg::PrivMsg, + user::User as UserHandler, who::Who, }, + config::ServerInfo, error_structs::CommandExecError, - messages::{JoinMessage, Message}, + messages::{ChanJoinMessage, Message}, sender::IrcResponse, user::User, }; @@ -20,6 +21,7 @@ use crate::{ mod cap; mod join; mod nick; +mod pass; mod ping; mod privmsg; mod user; @@ -40,10 +42,17 @@ pub enum IrcAction { SendText(IrcResponse), SendMessage(Message), JoinChannels(Vec), + UpgradeToServerConn, ErrorAuthenticateFirst, DoNothing, } +pub enum ReturnAction { + Nothing, + ServerConn, + CloseConn, +} + #[async_trait] pub trait IrcHandler: Send + Sync { async fn handle( @@ -51,17 +60,25 @@ pub trait IrcHandler: Send + Sync { command: Vec, authenticated: bool, user_state: &mut User, - ) -> IrcAction; + server_outgoing_password: String, + server_incoming_passwords: Vec, + user_passwords: Vec, + ) -> Vec; } pub struct SendMessage(Option); impl IrcCommand { - pub fn new(command_with_arguments: String) -> Self { - let split_command: Vec<&str> = command_with_arguments + pub async fn new(command_with_arguments: String) -> Self { + let mut split_command: Vec<&str> = command_with_arguments .split_whitespace() .into_iter() .collect(); + + if split_command[0].starts_with(":") { + split_command.remove(0); + } + let command = split_command[0].to_owned(); let mut arguments = Vec::new(); let mut buffer: Option = None; @@ -94,7 +111,8 @@ impl IrcCommand { writer: &mut BufWriter, hostname: &str, user_state: &mut User, - ) -> Result<(), CommandExecError> { + config: &ServerInfo, + ) -> Result, CommandExecError> { let mut command_map: HashMap = HashMap::new(); let broadcast_sender = SENDER.lock().await.clone().unwrap(); @@ -106,6 +124,7 @@ impl IrcCommand { command_map.insert("PING".to_owned(), &Ping); command_map.insert("JOIN".to_owned(), &Join); command_map.insert("WHO".to_owned(), &Who); + command_map.insert("PASS".to_owned(), &Pass); println!("{self:#?}"); @@ -114,18 +133,28 @@ impl IrcCommand { .map(|v| *v) .ok_or(CommandExecError::NonexistantCommand)?; - let action = command_to_execute + let actions = command_to_execute .handle( self.arguments.clone(), user_state.is_populated(), user_state, + config.server_outgoing_password.clone(), + config.server_incoming_passwords.clone(), + vec![], // TODO ) .await; - action - .execute(writer, hostname, &user_state, broadcast_sender) - .await; - Ok(()) + let mut return_actions = Vec::new(); + + for action in actions { + let return_action = action + .execute(writer, hostname, &user_state, broadcast_sender.clone()) + .await; + + return_actions.push(return_action); + } + + Ok(return_actions) } } @@ -136,7 +165,7 @@ impl IrcAction { hostname: &str, user_state: &User, sender: Sender, - ) { + ) -> ReturnAction { match self { IrcAction::SendText(msg) => { msg.send(hostname, writer, false).await.unwrap(); @@ -144,11 +173,11 @@ impl IrcAction { IrcAction::JoinChannels(channels) => { for channel in channels { - let join_message = JoinMessage { + let join_message = ChanJoinMessage { sender: user_state.clone().unwrap_all(), channel: channel.clone(), }; - sender.send(Message::JoinMessage(join_message)).unwrap(); + sender.send(Message::ChanJoinMessage(join_message)).unwrap(); } } @@ -156,7 +185,13 @@ impl IrcAction { sender.send(msg.clone()).unwrap(); } + IrcAction::UpgradeToServerConn => { + return ReturnAction::ServerConn; + } + _ => {} } + + return ReturnAction::Nothing; } } diff --git a/src/commands/nick.rs b/src/commands/nick.rs index aea53f2..8c5e660 100644 --- a/src/commands/nick.rs +++ b/src/commands/nick.rs @@ -14,9 +14,26 @@ impl IrcHandler for Nick { command: Vec, _authenticated: bool, user_state: &mut User, - ) -> IrcAction { - user_state.nickname = Some(command[0].clone()); + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { + user_state.nickname = Some({ + if command[0].len() > 9 { + String::from_utf8( + command[0] + .clone() + .chars() + .map(|x| x.clone() as u8) + .collect::>()[0..8] + .to_vec(), + ) + .unwrap() + } else { + command[0].clone() + } + }); - IrcAction::DoNothing + vec![IrcAction::DoNothing] } } diff --git a/src/commands/pass.rs b/src/commands/pass.rs new file mode 100644 index 0000000..979ef34 --- /dev/null +++ b/src/commands/pass.rs @@ -0,0 +1,36 @@ +use async_trait::async_trait; + +use crate::{ + commands::{IrcAction, IrcHandler}, + user::User, +}; + +pub struct Pass; + +#[async_trait] +impl IrcHandler for Pass { + async fn handle( + &self, + command: Vec, + _authenticated: bool, + _user_state: &mut User, + server_outgoing_password: String, + server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { + if server_incoming_passwords.contains(&command[0]) { + vec![ + IrcAction::SendText(crate::sender::IrcResponse { + sender: None, + command: "PASS".to_owned(), + receiver: None, + arguments: Vec::new(), + message: server_outgoing_password.clone(), + }), + IrcAction::UpgradeToServerConn, + ] + } else { + vec![IrcAction::DoNothing] + } + } +} diff --git a/src/commands/ping.rs b/src/commands/ping.rs index dee180d..6caf357 100644 --- a/src/commands/ping.rs +++ b/src/commands/ping.rs @@ -15,17 +15,20 @@ impl IrcHandler for Ping { command: Vec, authenticated: bool, user_state: &mut User, - ) -> IrcAction { + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { if authenticated { - IrcAction::SendText(IrcResponse { + vec![IrcAction::SendText(IrcResponse { sender: None, command: "PONG".into(), arguments: Vec::new(), receiver: Some(user_state.username.clone().unwrap()), message: format!(":{}", command[0].clone()), - }) + })] } else { - IrcAction::DoNothing + vec![IrcAction::DoNothing] } } } diff --git a/src/commands/privmsg.rs b/src/commands/privmsg.rs index 6088734..9a3ed2c 100644 --- a/src/commands/privmsg.rs +++ b/src/commands/privmsg.rs @@ -1,9 +1,8 @@ use async_trait::async_trait; use crate::{ - CONNECTED_USERS, commands::{IrcAction, IrcHandler}, - messages::{Message, PrivMessage}, + messages::{Message, PrivMessage, Receiver}, user::User, }; @@ -16,21 +15,26 @@ impl IrcHandler for PrivMsg { command: Vec, authenticated: bool, user_state: &mut User, - ) -> IrcAction { + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { if !authenticated { - return IrcAction::ErrorAuthenticateFirst; + return vec![IrcAction::ErrorAuthenticateFirst]; } - let connected_users = CONNECTED_USERS.lock().await; - println!("{connected_users:#?}"); - drop(connected_users); + let receiver = if command[0].clone().starts_with("#") { + Receiver::ChannelName(command[0].clone()) + } else { + Receiver::Username(command[0].clone()) + }; let message = PrivMessage { sender: user_state.clone().unwrap_all(), - receiver: command[0].clone(), + receiver, text: command[1].clone(), }; - IrcAction::SendMessage(Message::PrivMessage(message)) + vec![IrcAction::SendMessage(Message::PrivMessage(message))] } } diff --git a/src/commands/user.rs b/src/commands/user.rs index 91b6a45..dcfdabc 100644 --- a/src/commands/user.rs +++ b/src/commands/user.rs @@ -14,13 +14,32 @@ impl IrcHandler for User { command: Vec, _authenticated: bool, user_state: &mut UserState, - ) -> IrcAction { + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { if command.len() < 4 { - return IrcAction::DoNothing; // XXX: return an error + return vec![IrcAction::DoNothing]; // XXX: return an error } - user_state.username = Some(command[0].clone()); + + // oh my god this is a mess + user_state.username = Some({ + if command[0].len() > 9 { + String::from_utf8( + command[0] + .clone() + .chars() + .map(|x| x.clone() as u8) + .collect::>()[0..8] + .to_vec(), + ) + .unwrap() + } else { + command[0].clone() + } + }); user_state.realname = Some(command[3].clone()); - IrcAction::DoNothing + vec![IrcAction::DoNothing] } } diff --git a/src/commands/who.rs b/src/commands/who.rs index 2636d52..0d57f47 100644 --- a/src/commands/who.rs +++ b/src/commands/who.rs @@ -14,7 +14,10 @@ impl IrcHandler for Who { _arguments: Vec, _authenticated: bool, _user_state: &mut User, - ) -> super::IrcAction { - IrcAction::DoNothing // TODO + _server_outgoing_password: String, + _server_incoming_passwords: Vec, + _user_passwords: Vec, + ) -> Vec { + vec![IrcAction::DoNothing] // TODO } } diff --git a/src/config.rs b/src/config.rs index 1b702cd..bd1021a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,6 +11,8 @@ pub struct ServerInfo { pub server_hostname: String, pub network_name: String, pub operators: Vec, + pub server_incoming_passwords: Vec, + pub server_outgoing_password: String, } fn get_config_path() -> Result { diff --git a/src/main.rs b/src/main.rs index e299967..850cad2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use std::{ + clone, collections::HashSet, net::{SocketAddr, TcpListener, TcpStream}, str::FromStr, - time::Duration, + time::{Duration, SystemTime}, }; use anyhow::Error as AnyhowError; @@ -25,8 +26,13 @@ use crate::{ config::ServerInfo, error_structs::{HandlerError, ListenerError}, login::send_motd, - messages::Message, + messages::Receiver as MsgReceiver, + messages::{Message, NetJoinMessage}, sender::{IrcResponse, IrcResponseCodes}, + ts6::{ + Ts6, + structs::{ServerId, UserId}, + }, user::{User, UserUnwrapped}, }; @@ -37,10 +43,15 @@ mod error_structs; mod login; mod messages; mod sender; +mod ts6; mod user; +mod userid_gen; +mod usermodes; pub static CONNECTED_USERS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); +pub static FOREIGN_CONNECTED_USERS: Lazy>> = + Lazy::new(|| Mutex::new(HashSet::new())); pub static JOINED_CHANNELS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); pub static SENDER: Lazy>>> = Lazy::new(|| Mutex::new(None)); @@ -53,6 +64,11 @@ struct Args { pub config_path: Option, } +enum TcpListenerResult { + UpdatedUser(User), + ServerConnectionInit, +} + #[tokio::main] async fn main() -> Result<(), AnyhowError> { #[cfg(feature = "tokio-console")] @@ -92,37 +108,79 @@ async fn handle_connection( let mut message_receiver = tx.clone().subscribe(); let mut tcp_reader = TokioBufReader::new(TokioTcpStream::from_std(stream.try_clone()?)?); let mut tcp_writer = TokioBufWriter::new(TokioTcpStream::from_std(stream)?); - let mut state = User::default(); - let hostname = info.server_hostname.clone(); + 'connection_handler: { + let mut state = User::default(); - loop { - tokio::select! { - result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader) => { - match result { - Ok(modified_user) => { - state = modified_user; - } + let hostname = info.server_hostname.clone(); - Err(_) => { - break; - } - } - }, - result = message_listener(&state, &mut message_receiver, &mut tcp_writer, &hostname) => { - match result { - Ok(_) => {}, - Err(err) => { - match err { - ListenerError::ConnectionError => { - break; + // TODO: generate randomally and allow overriding from config + let my_server_id = ServerId::try_from("000".to_owned()).unwrap(); + + loop { + tokio::select! { + result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader, my_server_id.clone()) => { + match result { + Ok(tcp_listener_result) => { + match tcp_listener_result { + TcpListenerResult::UpdatedUser(user) => { + state = user; + } + + TcpListenerResult::ServerConnectionInit => { + break; + } } + } - _ => {} - }; + Err(_) => { + break 'connection_handler; + } } - } - }, + }, + result = message_listener(&state, &mut message_receiver, &mut tcp_writer, &hostname) => { + match result { + Ok(_) => {}, + Err(err) => { + match err { + ListenerError::ConnectionError => { + break 'connection_handler; + } + + _ => {} + }; + } + } + }, + } + } + + println!("upgrade to server connection"); + + let mut ts6_server_status = Ts6::default(); + + loop { + tokio::select! { + result = ts6_server_status.tcp_listener(&stream_tcp, &info, &mut tcp_reader, &my_server_id) => { + match result { + Ok(new_status) => { + println!("{new_status:#?}"); + ts6_server_status = new_status; + }, + Err(_) => { + break; + } + } + }, + result = ts6_server_status.message_listener(&mut message_receiver, &mut tcp_writer, &my_server_id, &hostname) => { + match result { + Ok(_) => {}, + Err(_) => { + break; + } + } + }, + } } } @@ -133,56 +191,89 @@ async fn handle_connection( async fn tcp_listener( stream: &TcpStream, - mut state: User, + mut user_state: User, info: &ServerInfo, reader: &mut TokioBufReader, -) -> Result { + our_sid: ServerId, +) -> Result { let mut buffer = String::new(); let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?); - buffer.clear(); match reader.read_line(&mut buffer).await { Ok(0) => return Err(ListenerError::ConnectionError), Ok(_) => {} Err(_) => { let mut conneted_users = CONNECTED_USERS.lock().await; - let _ = conneted_users.remove(&state.clone().unwrap_all()); + let _ = conneted_users.remove(&user_state.clone().unwrap_all()); return Err(ListenerError::ConnectionError); } } - let command = commands::IrcCommand::new(buffer.clone()); + let command = commands::IrcCommand::new(buffer.clone()).await; match command - .execute(&mut writer, &info.server_hostname, &mut state) + .execute(&mut writer, &info.server_hostname, &mut user_state, info) .await { - Ok(_) => {} - Err(error) => { - let error_string = format!("error processing your command: {error:#?}\n"); - let error = IrcResponseCodes::UnknownCommand; + Ok(return_actions) => { + for return_action in return_actions { + match return_action { + commands::ReturnAction::ServerConn => { + return Ok(TcpListenerResult::ServerConnectionInit); + } - error - .into_irc_response("*".into(), error_string.into()) - .send(&info.server_hostname, &mut writer, true) - .await - .unwrap(); + _ => {} + } + } } + Err(error) => match error { + error_structs::CommandExecError::NonexistantCommand => { + let error_string = format!("error processing your command: {error:#?}\n"); + let error = IrcResponseCodes::UnknownCommand; + + error + .into_irc_response("*".into(), error_string.into()) + .send(&info.server_hostname, &mut writer, true) + .await + .unwrap(); + } + }, } - if !state.identified && state.is_populated() { - send_motd(info.clone(), state.clone(), &mut writer).await?; + if !user_state.identified && user_state.is_populated_without_uid() { + let id = userid_gen::increase_user_id() + .await + .unwrap() + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(""); + let user_id = format!("{our_sid}{id}"); + + user_state.identified = true; + user_state.user_id = Some(UserId::try_from(user_id).unwrap()); // XXX: error handling + user_state.timestamp = Some(SystemTime::now()); + + send_motd(info.clone(), user_state.clone(), &mut writer).await?; + + let broadcast_sender = SENDER.lock().await.clone().unwrap(); + + broadcast_sender + .send(Message::NetJoinMessage(NetJoinMessage { + user: user_state.clone().unwrap_all(), + server_id: our_sid.clone(), + })) + .unwrap(); - state.identified = true; CONNECTED_USERS .lock() .await - .insert(state.clone().unwrap_all()); + .insert(user_state.clone().unwrap_all()); } - Ok(state) + Ok(TcpListenerResult::UpdatedUser(user_state)) } async fn message_listener( @@ -208,12 +299,32 @@ async fn message_listener( match message { Message::PrivMessage(message) => { for channel in joined_channels.clone() { - if channel.joined_users.contains(user_wrapped) && channel.name == message.receiver { + if let MsgReceiver::ChannelName(channelname) = message.clone().receiver + && channelname == channel.name + && channel.joined_users.contains(user_wrapped) + { channel_name = Some(channel.name.clone()); } } - if user.nickname.clone().to_ascii_lowercase() == message.receiver.to_ascii_lowercase() { + dbg!(&message); + + if match message.clone().receiver { + MsgReceiver::UserId(userid) => { + println!("{userid} ?= {}", user.user_id); + if userid == user.user_id { true } else { false } + } + + MsgReceiver::Username(username) => { + if username.to_lowercase() == user.username.to_lowercase() { + true + } else { + false + } + } + + _ => false, + } { IrcResponse { sender: Some(message.sender.hostmask()), command: "PRIVMSG".into(), @@ -238,7 +349,7 @@ async fn message_listener( } } - Message::JoinMessage(message) => { + Message::ChanJoinMessage(message) => { if message.channel.joined_users.contains(user_wrapped) || message.sender == user { let channel = message.channel.clone(); @@ -263,7 +374,35 @@ async fn message_listener( .unwrap(); } } + + Message::NetJoinMessage(_) => {} // we don't care about these here :) } Ok(()) } + +#[cfg(test)] +mod tests { + use crate::userid_gen; + + #[tokio::test] + async fn test_user_id_generator() { + while let Ok(userid) = userid_gen::increase_user_id().await { + if userid == ['A', 'B', 'C', 'D', 'E', 'F'] { + userid_gen::manually_set_user_id(['Z', 'Z', 'Z', 'Z', 'Z', 'Y'].to_vec()).await; + break; + } + + dbg!(userid); + } + + while let Ok(userid) = userid_gen::increase_user_id().await { + if userid == ['A', '1', '2', '3', '4', '5'] { + // ff a bit + userid_gen::manually_set_user_id(['Z', '1', '2', '3', '4', '5'].to_vec()).await; + } + + dbg!(userid); + } + } +} diff --git a/src/messages.rs b/src/messages.rs index 6cae5e7..1c4a277 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,22 +1,42 @@ -use crate::{channels::Channel, user::UserUnwrapped}; +use crate::{ + channels::Channel, + ts6::structs::{ServerId, UserId}, + user::UserUnwrapped, +}; #[derive(Debug, Clone)] pub enum Message { PrivMessage(PrivMessage), - JoinMessage(JoinMessage), + ChanJoinMessage(ChanJoinMessage), + NetJoinMessage(NetJoinMessage), } #[allow(dead_code)] #[derive(Debug, Clone)] -pub struct JoinMessage { +pub struct ChanJoinMessage { pub sender: UserUnwrapped, pub channel: Channel, } +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct NetJoinMessage { + pub user: UserUnwrapped, + pub server_id: ServerId, +} + #[allow(dead_code)] #[derive(Debug, Clone)] pub struct PrivMessage { pub sender: UserUnwrapped, - pub receiver: String, + pub receiver: Receiver, pub text: String, } + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub enum Receiver { + Username(String), + UserId(UserId), + ChannelName(String), +} diff --git a/src/sender.rs b/src/sender.rs index 32a92f9..a6db89b 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -5,7 +5,7 @@ use tokio::{ use crate::error_structs::SenderError; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct IrcResponse { pub sender: Option, pub command: String, @@ -15,16 +15,17 @@ pub struct IrcResponse { } #[derive(Clone, Copy)] +#[repr(u16)] pub enum IrcResponseCodes { - UnknownCommand, - Welcome, - YourHost, - MyInfo, - ISupport, - NoMotd, - NoTopic, - NameReply, - EndOfNames, + UnknownCommand = 421, + Welcome = 001, + YourHost = 002, + MyInfo = 004, + ISupport = 005, + NoMotd = 422, + NoTopic = 331, + NameReply = 353, + EndOfNames = 366, } impl IrcResponse { @@ -38,8 +39,8 @@ impl IrcResponse { let mut full_response = Vec::new(); full_response.push(sender); - full_response.extend_from_slice(&self.arguments); full_response.push(self.command.clone()); + full_response.extend_from_slice(&self.arguments); if let Some(receiver) = self.receiver.clone() { full_response.push(receiver); } @@ -52,29 +53,17 @@ impl IrcResponse { writer.write_all(full_response.join(" ").as_bytes()).await?; writer.flush().await?; - Ok(()) - } -} + println!("sending: {full_response:#?}"); -impl From for &str { - fn from(value: IrcResponseCodes) -> Self { - match value { - IrcResponseCodes::UnknownCommand => "421", - IrcResponseCodes::Welcome => "001", - IrcResponseCodes::YourHost => "002", - IrcResponseCodes::MyInfo => "004", - IrcResponseCodes::ISupport => "005", - IrcResponseCodes::NoMotd => "422", - IrcResponseCodes::NoTopic => "331", - IrcResponseCodes::NameReply => "353", - IrcResponseCodes::EndOfNames => "366", - } + Ok(()) } } impl From for String { fn from(value: IrcResponseCodes) -> Self { - Into::<&str>::into(value).to_string() + let value = value as u16; + + value.to_string() } } diff --git a/src/ts6/commands/capab.rs b/src/ts6/commands/capab.rs new file mode 100644 index 0000000..ee85988 --- /dev/null +++ b/src/ts6/commands/capab.rs @@ -0,0 +1,38 @@ +use crate::ts6::{ + ServerId, Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, +}; +use async_trait::async_trait; + +pub struct Capab; + +// TODO: handle capabilities + +#[async_trait] +impl Ts6Handler for Capab { + async fn handle( + &self, + command: Vec, + _server_status: Ts6, + _my_sid: ServerId, + sender: Option, + _hostname: &str, + ) -> Vec { + let args = { + let args_without_command = command[1..].to_vec(); + + if args_without_command.len() == 1 { + args_without_command[0] + .split_whitespace() + .map(|x| x.to_owned()) + .collect::>() + } else { + args_without_command + } + }; + + println!("{args:#?}"); + + vec![Ts6Action::DoNothing] + } +} diff --git a/src/ts6/commands/mod.rs b/src/ts6/commands/mod.rs new file mode 100644 index 0000000..e9752f9 --- /dev/null +++ b/src/ts6/commands/mod.rs @@ -0,0 +1,201 @@ +use std::collections::HashMap; + +use crate::{ + SENDER, + commands::IrcMessage, + messages::Message, + sender::IrcResponse, + ts6::{ + ServerId, Ts6, + commands::{ + capab::Capab, ping::Ping, privmsg::Privmsg, server::Server, svinfo::Svinfo, uid::Uid, + }, + structs::UserId, + }, +}; +use anyhow::anyhow; +use async_trait::async_trait; +use tokio::{io::BufWriter, net::TcpStream}; + +mod capab; +mod ping; +mod privmsg; +mod server; +mod svinfo; +mod uid; + +#[derive(Clone, Debug)] +pub struct Ts6Info { + pub sid: Option, + pub hopcount: Option, + pub description: Option, + pub name: Option, + + pub identified: Option, +} + +#[derive(Clone, Debug)] +pub enum Ts6Action { + SetInfo(Ts6Info), + SendText(IrcResponse), + SendMessage(Message), + DoNothing, +} + +#[async_trait] +pub trait Ts6Handler: Send + Sync { + async fn handle( + &self, + command: Vec, + server_status: Ts6, + my_sid: ServerId, + sender: Option, + hostname: &str, + ) -> Vec; +} + +#[derive(Debug)] +pub struct Ts6Command { + command: String, + arguments: Vec, + sender: Option, +} + +#[derive(Clone, Debug)] +pub enum CommandSender { + User(UserId), + Server(ServerId), +} + +impl Ts6Command { + pub async fn new(command_with_arguments: String) -> Self { + let mut command_sender = None; + let mut split_command: Vec<&str> = command_with_arguments + .split_whitespace() + .into_iter() + .collect(); + + if split_command[0].starts_with(":") { + let sender = split_command.remove(0).to_string().replace(":", ""); + + dbg!(&sender); + + match sender.len() { + 3 => { + if let Ok(sid) = ServerId::try_from(sender) { + command_sender = Some(CommandSender::Server(sid)); + } + } + + 9 => { + if let Ok(uid) = UserId::try_from(sender) { + command_sender = Some(CommandSender::User(uid)); + } + } + + _ => {} + } + } + + let command = split_command[0].to_owned(); + let mut arguments = Vec::new(); + let mut buffer: Option = None; + + split_command[1..] + .iter() + .for_each(|e| match (buffer.as_mut(), e.starts_with(":")) { + (None, false) => arguments.push(e.to_string()), + (None, true) => { + buffer = Some(e[1..].to_string()); + } + (Some(buf), starts_with_colon) => { + buf.push(' '); + buf.push_str(if starts_with_colon { &e[1..] } else { &e }); + } + }); + + if let Some(buf) = buffer { + arguments.push(buf.to_string()); + } + + Self { + command: command, + arguments: arguments, + sender: command_sender, + } + } + + pub async fn execute( + &self, + ts6_status: &mut Ts6, + hostname: &str, + my_sid: &ServerId, + writer: &mut BufWriter, + ) -> Result<(), anyhow::Error> { + let mut command_map: HashMap = HashMap::new(); + let message_sender = SENDER.lock().await.clone().unwrap(); + + command_map.insert("CAPAB".to_owned(), &Capab); + command_map.insert("SERVER".to_owned(), &Server); + command_map.insert("PING".to_owned(), &Ping); + command_map.insert("SVINFO".to_owned(), &Svinfo); + command_map.insert("UID".to_owned(), &Uid); + command_map.insert("PRIVMSG".to_owned(), &Privmsg); + + let command_to_execute = command_map + .get(&self.command.to_uppercase()) + .map(|v| *v) + .ok_or(anyhow!("error"))?; // TODO: error handling!!! + + let actions = command_to_execute + .handle( + self.arguments.clone(), + ts6_status.clone(), + ServerId::try_from(my_sid.clone().to_owned()).unwrap(), + self.sender.clone(), + hostname, + ) + .await; + + println!("{actions:#?}"); + + for action in actions { + match action { + Ts6Action::DoNothing => {} + Ts6Action::SetInfo(new_info) => { + if let Some(sid) = new_info.sid { + (*ts6_status).server_id = sid; + }; + + if let Some(hopcount) = new_info.hopcount { + (*ts6_status).hopcount = hopcount; + }; + + if let Some(name) = new_info.name { + (*ts6_status).hostname = name; + }; + + if let Some(description) = new_info.description { + (*ts6_status).description = description; + }; + + if let Some(identified) = new_info.identified { + (*ts6_status).identified = identified; + } + } + Ts6Action::SendText(response) => { + response + .send(&my_sid.to_string(), writer, false) + .await + .unwrap(); + // TODO: error handling + } + Ts6Action::SendMessage(message) => { + message_sender.send(message.clone()).unwrap(); + } + } + } + + Ok(()) + } +} diff --git a/src/ts6/commands/ping.rs b/src/ts6/commands/ping.rs new file mode 100644 index 0000000..04ff137 --- /dev/null +++ b/src/ts6/commands/ping.rs @@ -0,0 +1,31 @@ +use async_trait::async_trait; + +use crate::{ + sender::IrcResponse, + ts6::{ + ServerId, Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, + }, +}; + +pub struct Ping; + +#[async_trait] +impl Ts6Handler for Ping { + async fn handle( + &self, + command: Vec, + _server_status: Ts6, + my_sid: ServerId, + sender: Option, + _hostname: &str, + ) -> Vec { + vec![Ts6Action::SendText(IrcResponse { + sender: None, + command: "PONG".into(), + arguments: Vec::new(), + receiver: None, + message: format!("{my_sid} {}", command[0].clone()), + })] + } +} diff --git a/src/ts6/commands/privmsg.rs b/src/ts6/commands/privmsg.rs new file mode 100644 index 0000000..068229d --- /dev/null +++ b/src/ts6/commands/privmsg.rs @@ -0,0 +1,58 @@ +use async_trait::async_trait; + +use crate::{ + FOREIGN_CONNECTED_USERS, + messages::{PrivMessage, Receiver}, + ts6::{ + Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, + structs::{ServerId, UserId}, + }, + user::UserUnwrapped, +}; + +pub struct Privmsg; + +#[async_trait] +impl Ts6Handler for Privmsg { + async fn handle( + &self, + command: Vec, + server_status: Ts6, + my_sid: ServerId, + sender: Option, + hostname: &str, + ) -> Vec { + 'priv_msg: { + let mut sending_user: Option = None; + + dbg!(&sender); + + if let Ok(user_id) = UserId::try_from(command[0].clone()) { + if let Some(CommandSender::User(command_sender)) = sender { + let foreign_users = FOREIGN_CONNECTED_USERS.lock().await; + + for user in foreign_users.iter() { + if user.user_id == command_sender { + sending_user = Some(user.clone()) + } + } + } else { + dbg!("sender"); + break 'priv_msg vec![]; + } + + vec![Ts6Action::SendMessage( + crate::messages::Message::PrivMessage(PrivMessage { + sender: sending_user.unwrap(), + receiver: Receiver::UserId(user_id), + text: command[1].clone(), + }), + )] + } else { + dbg!("userid"); + vec![] + } + } + } +} diff --git a/src/ts6/commands/server.rs b/src/ts6/commands/server.rs new file mode 100644 index 0000000..8b3f320 --- /dev/null +++ b/src/ts6/commands/server.rs @@ -0,0 +1,58 @@ +use crate::ts6::{ + ServerId, Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, +}; +use async_trait::async_trait; + +pub struct Server; + +// TODO: handle *ALL* params + +#[async_trait] +impl Ts6Handler for Server { + async fn handle( + &self, + command: Vec, + _server_status: Ts6, + my_sid: ServerId, + sender: Option, + hostname: &str, + ) -> Vec { + let name = Some(command[0].clone()); + let hopcount = Some(command[1].parse::().unwrap()); + let sid = { + if let Some(sid) = ServerId::try_from(command[2].clone()).ok() { + Some(sid) + } else { + None + } + }; + let _flags = Some(command[3].clone()); + let description = Some(command[4].clone()); + + println!("server cmd"); + + vec![ + Ts6Action::SetInfo(super::Ts6Info { + sid, + hopcount, + description, + name, + + identified: Some(true), + }), + Ts6Action::SendText(crate::sender::IrcResponse { + sender: Some(hostname.to_owned().clone()), + command: "SERVER".to_owned(), + receiver: None, + arguments: vec![ + hostname.to_owned().clone(), + "1".to_owned(), + my_sid.clone().to_string(), + "+".to_owned(), + ], + message: String::from(":TODO"), + }), + ] + } +} diff --git a/src/ts6/commands/svinfo.rs b/src/ts6/commands/svinfo.rs new file mode 100644 index 0000000..017ce43 --- /dev/null +++ b/src/ts6/commands/svinfo.rs @@ -0,0 +1,47 @@ +use std::time::SystemTime; + +use crate::{ + sender::IrcResponse, + ts6::{ + ServerId, Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, + }, +}; +use async_trait::async_trait; + +pub struct Svinfo; + +const TS_CURRENT: u8 = 6; +const TS_MINIMUM: u8 = 6; + +#[async_trait] +impl Ts6Handler for Svinfo { + async fn handle( + &self, + command: Vec, + _server_status: Ts6, + _my_sid: ServerId, + sender: Option, + _hostname: &str, + ) -> Vec { + let ts_current = command[0].parse::().unwrap(); + let ts_minimum = command[1].parse::().unwrap(); + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + + // XXX: we need to properly disconnect with a QUIT message but we currently don't handle + // that.. we probably need a Ts6Action for that. same goes for regular irc commands + assert_eq!(ts_current, TS_CURRENT); + assert_eq!(ts_minimum, TS_MINIMUM); + + vec![Ts6Action::SendText(IrcResponse { + sender: None, + command: "SVINFO".to_owned(), + receiver: None, + arguments: vec!["6".to_owned(), "6".to_owned(), "0".to_owned()], + message: format!(":{}", current_time), + })] + } +} diff --git a/src/ts6/commands/uid.rs b/src/ts6/commands/uid.rs new file mode 100644 index 0000000..8ee556b --- /dev/null +++ b/src/ts6/commands/uid.rs @@ -0,0 +1,58 @@ +use std::{ + net::{IpAddr, Ipv4Addr}, + str::FromStr, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use crate::{ + FOREIGN_CONNECTED_USERS, + ts6::{ + ServerId, Ts6, + commands::{CommandSender, Ts6Action, Ts6Handler}, + structs::UserId, + }, + user::UserUnwrapped, + usermodes::Usermodes, +}; +use async_trait::async_trait; + +pub struct Uid; + +#[async_trait] +impl Ts6Handler for Uid { + async fn handle( + &self, + command: Vec, + server_status: Ts6, + my_sid: ServerId, + sender: Option, + hostname: &str, + ) -> Vec { + let username = command[0].clone(); + let hops = command[1].clone().parse::().unwrap(); + let timestamp = UNIX_EPOCH + Duration::new(command[2].parse::().unwrap(), 0); + let usermodes = Usermodes::default(); // XXX + let ip = IpAddr::from_str(&command[7]).unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); + let user_id = UserId::try_from(command[8].clone()).unwrap(); + // TODO: error handling + + let user = UserUnwrapped { + username: username.clone(), + nickname: username.clone(), + realname: username.clone(), + hopcount: hops, + identified: true, + user_id, + usermodes, + timestamp, + ip, + }; + + dbg!(&user); + + let mut foreign_users = FOREIGN_CONNECTED_USERS.lock().await; + foreign_users.insert(user.clone()); + + vec![] + } +} diff --git a/src/ts6/mod.rs b/src/ts6/mod.rs new file mode 100644 index 0000000..144a816 --- /dev/null +++ b/src/ts6/mod.rs @@ -0,0 +1,148 @@ +// TODO: better error handling + +use std::{ + net::TcpStream, + time::{Duration, UNIX_EPOCH}, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter}, + net::TcpStream as TokioTcpStream, + sync::broadcast::Receiver, + time::sleep, +}; + +use crate::{ + config::ServerInfo, + messages::Message, + sender::IrcResponse, + ts6::{commands::Ts6Command, structs::ServerId}, +}; + +#[derive(Clone, Debug, Default)] +pub struct Ts6 { + pub server_id: ServerId, + pub hopcount: u16, + pub description: String, + pub hostname: String, + + identified: bool, +} + +mod commands; +pub mod structs; + +impl Ts6 { + pub async fn handle_command( + &mut self, + _my_server_id: &ServerId, + args: String, + hostname: &str, + my_sid: &ServerId, + writer: &mut TokioBufWriter, + ) { + println!("server command: {}", self.server_id); + let args = Ts6Command::new(args).await; + println!("args: {args:#?}"); + + // XXX + let result = args.execute(self, hostname, my_sid, writer).await; + if result.is_err() { + println!("{result:#?}"); + } + } + + pub async fn tcp_listener( + &self, + stream: &TcpStream, + info: &ServerInfo, + reader: &mut TokioBufReader, + my_server_id: &ServerId, + ) -> Result { + let mut buffer = String::new(); + let mut self_clone = self.clone(); + + let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?); + + match reader.read_line(&mut buffer).await { + Ok(0) => anyhow::bail!(""), + Ok(_) => {} + + Err(_) => { + anyhow::bail!(""); + } + } + + println!("ts6: {buffer}"); + + let args = buffer + .split_whitespace() + .map(|x| x.to_string()) + .collect::>(); + + self_clone + .handle_command( + my_server_id, + args.join(" "), + &info.server_hostname, + my_server_id, + &mut writer, + ) + .await; + + Ok(self_clone) + } + + pub async fn message_listener( + &self, + receiver: &mut Receiver, + writer: &mut TokioBufWriter, + my_sid: &ServerId, + hostname: &str, + ) -> Result<(), anyhow::Error> { + if !self.identified { + sleep(Duration::from_millis(250)).await; // avoid immediate returns b'cuz they result in high + // cpu usage + return Ok(()); // TODO: error handling + } + + let message: Message = receiver.recv().await.unwrap(); + + match message { + Message::NetJoinMessage(net_join_message) => { + let user = net_join_message.user.clone(); + + // TODO: refactor this entire thing. we need hostmask and ip and such fully working + IrcResponse { + sender: Some(my_sid.clone().to_string()), + command: "UID".to_string(), + receiver: None, + arguments: vec![ + user.nickname.clone(), + (user.hopcount + 1).to_string(), + user.timestamp + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string(), + user.usermodes.into(), + format!("~{}", user.username.clone()), + user.ip.to_string(), + user.ip.to_string(), + user.ip.to_string(), + user.user_id.to_string().clone(), + "*".to_owned(), + format!(":{}", user.username.clone()), + ], + message: String::new(), + } + .send(hostname, writer, false) + .await + .unwrap(); + } + + _ => {} + } + + Ok(()) + } +} diff --git a/src/ts6/structs.rs b/src/ts6/structs.rs new file mode 100644 index 0000000..04c2066 --- /dev/null +++ b/src/ts6/structs.rs @@ -0,0 +1,215 @@ +const A_TO_Z: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVW"; +const ZERO_TO_9: &'static [u8] = b"0123456789"; + +#[derive(Clone, Default, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct ServerId([char; 3]); + +#[derive(Clone, Default, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct UserId([char; 9]); + +impl UserId { + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + pub fn get_server_id(&self) -> ServerId { + let vector = self.to_vec(); + let server_id_chars = vector[..3].to_vec(); + + let server_id = ServerId::try_from(server_id_chars).unwrap(); + + server_id + } + + pub fn get_id(&self) -> Vec { + let vector = self.to_vec(); + let id_chars = vector[3..].to_vec(); + + id_chars + } +} + +impl Into for UserId { + fn into(self) -> String { + String::from_utf8_lossy( + self.to_vec() + .iter() + .map(|x| x.clone() as u8) + .collect::>() + .as_slice(), + ) + .to_string() + } +} + +impl TryFrom for UserId { + type Error = &'static str; + + fn try_from(value: String) -> Result { + dbg!(&value); + + let chars = value.chars().into_iter().collect::>(); + + if chars.len() != 9 || !ServerId::is_server_id(&value[..3]) { + return Err("string isn't a user id"); + } + + Ok(Self([ + chars[0].clone(), + chars[1].clone(), + chars[2].clone(), + chars[3].clone(), + chars[4].clone(), + chars[5].clone(), + chars[6].clone(), + chars[7].clone(), + chars[8].clone(), + ])) + } +} + +impl TryFrom> for UserId { + type Error = &'static str; + + fn try_from(chars: Vec) -> Result { + if chars.len() != 9 + || !ServerId::is_server_id( + &String::from_utf8_lossy( + &chars.iter().map(|x| x.clone() as u8).collect::>()[..3], + ) + .to_string(), + ) + { + return Err("string isn't a user id"); + } + + Ok(Self([ + chars[0].clone(), + chars[1].clone(), + chars[2].clone(), + chars[3].clone(), + chars[4].clone(), + chars[5].clone(), + chars[6].clone(), + chars[7].clone(), + chars[8].clone(), + ])) + } +} + +impl std::fmt::Display for UserId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // We could just call our implementation of Into, but as we can return an error + // here, this seems a better option + if let Some(string) = String::from_utf8( + self.to_vec() + .iter() + .map(|x| x.clone() as u8) + .collect::>(), + ) + .ok() + { + f.write_str(&string)?; + } else { + return Err(std::fmt::Error); + } + + Ok(()) + } +} + +impl ServerId { + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + // there might be a cleaner way to do this? + pub fn is_server_id(id: &str) -> bool { + let chars = id.chars().collect::>(); + + if chars.len() != 3 { + return false; + } + + if !ZERO_TO_9.contains(&(chars[0] as u8)) { + return false; + } + + if !(A_TO_Z.contains(&(chars[1] as u8)) || ZERO_TO_9.contains(&(chars[1] as u8))) { + return false; + } + + if !(A_TO_Z.contains(&(chars[2] as u8)) || ZERO_TO_9.contains(&(chars[2] as u8))) { + return false; + } + + true + } +} + +impl Into for ServerId { + fn into(self) -> String { + String::from_utf8_lossy( + self.to_vec() + .iter() + .map(|x| x.clone() as u8) + .collect::>() + .as_slice(), + ) + .to_string() + } +} + +impl TryFrom for ServerId { + type Error = &'static str; + + fn try_from(value: String) -> Result { + let chars = value.chars().into_iter().collect::>(); + + if chars.len() != 3 || !Self::is_server_id(&value) { + return Err("string isn't a server id"); + } + + Ok(Self([chars[0].clone(), chars[1].clone(), chars[2].clone()])) + } +} + +impl TryFrom> for ServerId { + type Error = &'static str; + + fn try_from(chars: Vec) -> Result { + if chars.len() != 3 + || !Self::is_server_id( + &String::from_utf8_lossy( + &chars.iter().map(|x| x.clone() as u8).collect::>(), + ) + .to_string(), + ) + { + return Err("string isn't a server id"); + } + + Ok(Self([chars[0].clone(), chars[1].clone(), chars[2].clone()])) + } +} + +impl std::fmt::Display for ServerId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // We could just call our implementation of Into, but as we can return an error + // here, this seems a better option + if let Some(string) = String::from_utf8( + self.to_vec() + .iter() + .map(|x| x.clone() as u8) + .collect::>(), + ) + .ok() + { + f.write_str(&string)?; + } else { + return Err(std::fmt::Error); + } + + Ok(()) + } +} diff --git a/src/user.rs b/src/user.rs index 583b7cb..c5e3f80 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,11 +1,24 @@ #![allow(dead_code)] +use std::{ + net::{IpAddr, Ipv4Addr}, + time::SystemTime, +}; + +use crate::{ts6::structs::UserId, usermodes::Usermodes}; + #[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct User { pub nickname: Option, pub username: Option, pub realname: Option, pub identified: bool, + pub hopcount: Option, + pub user_id: Option, + pub usermodes: Usermodes, + pub timestamp: Option, + pub ip: Option, + // pub hostname: Option, } #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -14,9 +27,19 @@ pub struct UserUnwrapped { pub username: String, pub realname: String, pub identified: bool, + pub hopcount: u16, + pub user_id: UserId, + pub usermodes: Usermodes, + pub timestamp: SystemTime, + pub ip: IpAddr, + // pub hostname: Option, } impl User { + pub fn is_populated_without_uid(&self) -> bool { + self.realname.is_some() && self.username.is_some() && self.nickname.is_some() + } + pub fn is_populated(&self) -> bool { self.realname.is_some() && self.username.is_some() && self.nickname.is_some() } @@ -27,6 +50,11 @@ impl User { username: self.username.clone().unwrap(), realname: self.realname.clone().unwrap(), identified: self.identified, + hopcount: self.hopcount.clone().unwrap(), + user_id: self.user_id.clone().unwrap(), + usermodes: self.usermodes.clone(), + timestamp: self.timestamp.clone().unwrap(), + ip: self.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), } } @@ -36,6 +64,11 @@ impl User { username: None, realname: None, identified: false, + hopcount: Some(0), + user_id: None, + usermodes: Usermodes::default(), + timestamp: None, + ip: None, } } } diff --git a/src/userid_gen.rs b/src/userid_gen.rs new file mode 100644 index 0000000..160dcfe --- /dev/null +++ b/src/userid_gen.rs @@ -0,0 +1,77 @@ +use once_cell::sync::Lazy; +use tokio::sync::Mutex; + +use thiserror::Error; + +static CURRENT_ID: Lazy>> = + Lazy::new(|| Mutex::new(vec!['A', 'A', 'A', 'A', 'A', 'A'])); +static ZZZZZZ_REACHED: Lazy> = Lazy::new(|| Mutex::new(false)); + +#[derive(Debug, Error)] +pub enum UidIncreaseError { + #[error("cap reached")] + UserCapReached, +} + +pub async fn increase_user_id() -> Result, UidIncreaseError> { + let mut current_id = CURRENT_ID.lock().await; + let mut zzzzzz_reached = ZZZZZZ_REACHED.lock().await; + + let mut idx = 5; + + 'id_increaser: { + if !zzzzzz_reached.clone() { + loop { + if current_id[idx] != 'Z' { + current_id[idx] = (current_id[idx] as u8 + 1) as char; + break 'id_increaser; + } else { + if idx == 0 { + *zzzzzz_reached = true; + break; + } + + current_id[idx] = 'A'; + idx -= 1; + } + } + + // if we get here, our id is ZZZZZZ and we need to start using numbers + idx = 5; + + (*current_id) = vec!['A', '0', '0', '0', '0', '0']; + } + + loop { + if idx != 0 { + if current_id[idx] != '9' { + current_id[idx] = (current_id[idx] as u8 + 1) as char; + break 'id_increaser; + } else { + current_id[idx] = '0'; + idx -= 1; + } + } else { + if current_id[idx] != 'Z' { + current_id[idx] = (current_id[idx] as u8 + 1) as char; + idx = 5; + } else { + return Err(UidIncreaseError::UserCapReached); + } + } + } + } + + Ok(current_id.to_vec()) +} + +// THIS SHOULD BE USED *ONLY* FOR TESTING PURPOSES! DO NOT USE IT IN PRODUCTION CODE! + +#[allow(dead_code)] +pub async fn manually_set_user_id(user_id: Vec) { + assert_eq!(user_id.len(), 6); + + let mut lock = CURRENT_ID.lock().await; + + (*lock) = user_id; +} diff --git a/src/usermodes.rs b/src/usermodes.rs new file mode 100644 index 0000000..b4e8aad --- /dev/null +++ b/src/usermodes.rs @@ -0,0 +1,45 @@ +#[repr(u8)] +#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] +pub enum Usermode { + Invisible = b'i', + HostHiding = b'x', +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] +pub struct Usermodes(Vec); + +impl Into> for Usermodes { + fn into(self) -> Vec { + let mut vector: Vec = vec![]; + + for i in self.0 { + vector.push(Into::::into(i)); + } + + vector + } +} + +impl Into for Usermodes { + fn into(self) -> String { + format!("+{}", Into::>::into(self).join("")) + } +} + +impl Default for Usermodes { + fn default() -> Self { + Self(vec![Usermode::Invisible, Usermode::HostHiding]) + } +} + +impl Into for Usermode { + fn into(self) -> char { + self as u8 as char + } +} + +impl Into for Usermode { + fn into(self) -> String { + Into::::into(self).to_string() + } +}