From 0f1177bd91284a43a82373dc7e1085696c9162f9 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 11 Jun 2023 21:03:41 +0300 Subject: [PATCH] Initial commit --- .gitignore | 11 + Design.txt | 51 + LICENSE.txt | 20 + Makefile | 21 + README.md | 93 + Story.txt | 16 + buny.go | 143 + collection.go | 69 + data/sample_blacklist.json | 15 + data/sample_config.json | 56 + data/sample_whitelist.json | 24 + event_parser.go | 567 + globals.go | 65 + go.mod | 16 + go.sum | 24 + lib.go | 696 ++ main.go | 157 + types.go | 93 + vendor/github.com/davecgh/go-spew/LICENSE | 15 + .../github.com/davecgh/go-spew/spew/bypass.go | 145 + .../davecgh/go-spew/spew/bypasssafe.go | 38 + .../github.com/davecgh/go-spew/spew/common.go | 341 + .../github.com/davecgh/go-spew/spew/config.go | 306 + vendor/github.com/davecgh/go-spew/spew/doc.go | 211 + .../github.com/davecgh/go-spew/spew/dump.go | 509 + .../github.com/davecgh/go-spew/spew/format.go | 419 + .../github.com/davecgh/go-spew/spew/spew.go | 148 + vendor/github.com/eleksir/go-xmpp/LICENSE | 30 + vendor/github.com/eleksir/go-xmpp/README.md | 14 + vendor/github.com/eleksir/go-xmpp/xmpp.go | 1455 +++ .../github.com/eleksir/go-xmpp/xmpp_avatar.go | 125 + .../github.com/eleksir/go-xmpp/xmpp_disco.go | 102 + .../eleksir/go-xmpp/xmpp_information_query.go | 54 + vendor/github.com/eleksir/go-xmpp/xmpp_muc.go | 135 + .../github.com/eleksir/go-xmpp/xmpp_ping.go | 33 + .../github.com/eleksir/go-xmpp/xmpp_pubsub.go | 133 + .../eleksir/go-xmpp/xmpp_subscription.go | 25 + vendor/github.com/hjson/hjson-go/.gitignore | 0 vendor/github.com/hjson/hjson-go/LICENSE | 21 + vendor/github.com/hjson/hjson-go/README.md | 181 + .../hjson/hjson-go/build_release.sh | 58 + vendor/github.com/hjson/hjson-go/decode.go | 480 + vendor/github.com/hjson/hjson-go/encode.go | 492 + .../github.com/hjson/hjson-go/parseNumber.go | 108 + vendor/github.com/sirupsen/logrus/.gitignore | 4 + .../github.com/sirupsen/logrus/.golangci.yml | 40 + vendor/github.com/sirupsen/logrus/.travis.yml | 15 + .../github.com/sirupsen/logrus/CHANGELOG.md | 259 + vendor/github.com/sirupsen/logrus/LICENSE | 21 + vendor/github.com/sirupsen/logrus/README.md | 515 + vendor/github.com/sirupsen/logrus/alt_exit.go | 76 + .../github.com/sirupsen/logrus/appveyor.yml | 14 + .../github.com/sirupsen/logrus/buffer_pool.go | 43 + vendor/github.com/sirupsen/logrus/doc.go | 26 + vendor/github.com/sirupsen/logrus/entry.go | 442 + vendor/github.com/sirupsen/logrus/exported.go | 270 + .../github.com/sirupsen/logrus/formatter.go | 78 + vendor/github.com/sirupsen/logrus/hooks.go | 34 + .../sirupsen/logrus/json_formatter.go | 128 + vendor/github.com/sirupsen/logrus/logger.go | 417 + vendor/github.com/sirupsen/logrus/logrus.go | 186 + .../logrus/terminal_check_appengine.go | 11 + .../sirupsen/logrus/terminal_check_bsd.go | 13 + .../sirupsen/logrus/terminal_check_js.go | 7 + .../logrus/terminal_check_no_terminal.go | 11 + .../logrus/terminal_check_notappengine.go | 17 + .../sirupsen/logrus/terminal_check_solaris.go | 11 + .../sirupsen/logrus/terminal_check_unix.go | 13 + .../sirupsen/logrus/terminal_check_windows.go | 27 + .../sirupsen/logrus/text_formatter.go | 339 + vendor/github.com/sirupsen/logrus/writer.go | 102 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go | 77 + vendor/golang.org/x/exp/LICENSE | 27 + vendor/golang.org/x/exp/PATENTS | 22 + .../x/exp/constraints/constraints.go | 50 + vendor/golang.org/x/exp/slices/slices.go | 282 + vendor/golang.org/x/exp/slices/sort.go | 128 + vendor/golang.org/x/exp/slices/zsortfunc.go | 479 + .../golang.org/x/exp/slices/zsortordered.go | 481 + vendor/golang.org/x/sys/LICENSE | 27 + vendor/golang.org/x/sys/PATENTS | 22 + .../sys/internal/unsafeheader/unsafeheader.go | 30 + vendor/golang.org/x/sys/unix/.gitignore | 2 + vendor/golang.org/x/sys/unix/README.md | 184 + .../golang.org/x/sys/unix/affinity_linux.go | 86 + vendor/golang.org/x/sys/unix/aliases.go | 15 + vendor/golang.org/x/sys/unix/asm_aix_ppc64.s | 18 + vendor/golang.org/x/sys/unix/asm_bsd_386.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_amd64.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm64.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s | 31 + .../golang.org/x/sys/unix/asm_bsd_riscv64.s | 29 + vendor/golang.org/x/sys/unix/asm_linux_386.s | 66 + .../golang.org/x/sys/unix/asm_linux_amd64.s | 58 + vendor/golang.org/x/sys/unix/asm_linux_arm.s | 57 + .../golang.org/x/sys/unix/asm_linux_arm64.s | 53 + .../golang.org/x/sys/unix/asm_linux_loong64.s | 54 + .../golang.org/x/sys/unix/asm_linux_mips64x.s | 57 + .../golang.org/x/sys/unix/asm_linux_mipsx.s | 55 + .../golang.org/x/sys/unix/asm_linux_ppc64x.s | 45 + .../golang.org/x/sys/unix/asm_linux_riscv64.s | 49 + .../golang.org/x/sys/unix/asm_linux_s390x.s | 57 + .../x/sys/unix/asm_openbsd_mips64.s | 30 + .../golang.org/x/sys/unix/asm_solaris_amd64.s | 18 + vendor/golang.org/x/sys/unix/asm_zos_s390x.s | 426 + .../golang.org/x/sys/unix/bluetooth_linux.go | 36 + vendor/golang.org/x/sys/unix/cap_freebsd.go | 196 + vendor/golang.org/x/sys/unix/constants.go | 14 + vendor/golang.org/x/sys/unix/dev_aix_ppc.go | 27 + vendor/golang.org/x/sys/unix/dev_aix_ppc64.go | 29 + vendor/golang.org/x/sys/unix/dev_darwin.go | 24 + vendor/golang.org/x/sys/unix/dev_dragonfly.go | 30 + vendor/golang.org/x/sys/unix/dev_freebsd.go | 30 + vendor/golang.org/x/sys/unix/dev_linux.go | 42 + vendor/golang.org/x/sys/unix/dev_netbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_openbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_zos.go | 29 + vendor/golang.org/x/sys/unix/dirent.go | 103 + vendor/golang.org/x/sys/unix/endian_big.go | 10 + vendor/golang.org/x/sys/unix/endian_little.go | 10 + vendor/golang.org/x/sys/unix/env_unix.go | 32 + vendor/golang.org/x/sys/unix/epoll_zos.go | 221 + vendor/golang.org/x/sys/unix/fcntl.go | 37 + vendor/golang.org/x/sys/unix/fcntl_darwin.go | 24 + .../x/sys/unix/fcntl_linux_32bit.go | 14 + vendor/golang.org/x/sys/unix/fdset.go | 30 + vendor/golang.org/x/sys/unix/fstatfs_zos.go | 164 + vendor/golang.org/x/sys/unix/gccgo.go | 60 + vendor/golang.org/x/sys/unix/gccgo_c.c | 45 + .../x/sys/unix/gccgo_linux_amd64.go | 21 + vendor/golang.org/x/sys/unix/ifreq_linux.go | 142 + vendor/golang.org/x/sys/unix/ioctl_linux.go | 233 + vendor/golang.org/x/sys/unix/ioctl_signed.go | 70 + .../golang.org/x/sys/unix/ioctl_unsigned.go | 70 + vendor/golang.org/x/sys/unix/ioctl_zos.go | 72 + vendor/golang.org/x/sys/unix/mkall.sh | 249 + vendor/golang.org/x/sys/unix/mkerrors.sh | 783 ++ vendor/golang.org/x/sys/unix/mremap.go | 40 + vendor/golang.org/x/sys/unix/pagesize_unix.go | 16 + .../golang.org/x/sys/unix/pledge_openbsd.go | 163 + vendor/golang.org/x/sys/unix/ptrace_darwin.go | 18 + vendor/golang.org/x/sys/unix/ptrace_ios.go | 18 + vendor/golang.org/x/sys/unix/race.go | 31 + vendor/golang.org/x/sys/unix/race0.go | 26 + .../x/sys/unix/readdirent_getdents.go | 13 + .../x/sys/unix/readdirent_getdirentries.go | 20 + .../x/sys/unix/sockcmsg_dragonfly.go | 16 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 85 + vendor/golang.org/x/sys/unix/sockcmsg_unix.go | 107 + .../x/sys/unix/sockcmsg_unix_other.go | 47 + vendor/golang.org/x/sys/unix/syscall.go | 87 + vendor/golang.org/x/sys/unix/syscall_aix.go | 599 ++ .../golang.org/x/sys/unix/syscall_aix_ppc.go | 53 + .../x/sys/unix/syscall_aix_ppc64.go | 84 + vendor/golang.org/x/sys/unix/syscall_bsd.go | 624 ++ .../golang.org/x/sys/unix/syscall_darwin.go | 826 ++ .../x/sys/unix/syscall_darwin_amd64.go | 52 + .../x/sys/unix/syscall_darwin_arm64.go | 52 + .../x/sys/unix/syscall_darwin_libSystem.go | 27 + .../x/sys/unix/syscall_dragonfly.go | 545 + .../x/sys/unix/syscall_dragonfly_amd64.go | 57 + .../golang.org/x/sys/unix/syscall_freebsd.go | 645 ++ .../x/sys/unix/syscall_freebsd_386.go | 65 + .../x/sys/unix/syscall_freebsd_amd64.go | 65 + .../x/sys/unix/syscall_freebsd_arm.go | 61 + .../x/sys/unix/syscall_freebsd_arm64.go | 61 + .../x/sys/unix/syscall_freebsd_riscv64.go | 61 + vendor/golang.org/x/sys/unix/syscall_hurd.go | 30 + .../golang.org/x/sys/unix/syscall_hurd_386.go | 29 + .../golang.org/x/sys/unix/syscall_illumos.go | 79 + vendor/golang.org/x/sys/unix/syscall_linux.go | 2551 +++++ .../x/sys/unix/syscall_linux_386.go | 315 + .../x/sys/unix/syscall_linux_alarm.go | 14 + .../x/sys/unix/syscall_linux_amd64.go | 146 + .../x/sys/unix/syscall_linux_amd64_gc.go | 13 + .../x/sys/unix/syscall_linux_arm.go | 217 + .../x/sys/unix/syscall_linux_arm64.go | 185 + .../golang.org/x/sys/unix/syscall_linux_gc.go | 15 + .../x/sys/unix/syscall_linux_gc_386.go | 17 + .../x/sys/unix/syscall_linux_gc_arm.go | 14 + .../x/sys/unix/syscall_linux_gccgo_386.go | 31 + .../x/sys/unix/syscall_linux_gccgo_arm.go | 21 + .../x/sys/unix/syscall_linux_loong64.go | 217 + .../x/sys/unix/syscall_linux_mips64x.go | 190 + .../x/sys/unix/syscall_linux_mipsx.go | 176 + .../x/sys/unix/syscall_linux_ppc.go | 205 + .../x/sys/unix/syscall_linux_ppc64x.go | 117 + .../x/sys/unix/syscall_linux_riscv64.go | 179 + .../x/sys/unix/syscall_linux_s390x.go | 297 + .../x/sys/unix/syscall_linux_sparc64.go | 113 + .../golang.org/x/sys/unix/syscall_netbsd.go | 621 ++ .../x/sys/unix/syscall_netbsd_386.go | 38 + .../x/sys/unix/syscall_netbsd_amd64.go | 38 + .../x/sys/unix/syscall_netbsd_arm.go | 38 + .../x/sys/unix/syscall_netbsd_arm64.go | 38 + .../golang.org/x/sys/unix/syscall_openbsd.go | 403 + .../x/sys/unix/syscall_openbsd_386.go | 42 + .../x/sys/unix/syscall_openbsd_amd64.go | 42 + .../x/sys/unix/syscall_openbsd_arm.go | 42 + .../x/sys/unix/syscall_openbsd_arm64.go | 42 + .../x/sys/unix/syscall_openbsd_libc.go | 27 + .../x/sys/unix/syscall_openbsd_mips64.go | 39 + .../x/sys/unix/syscall_openbsd_ppc64.go | 42 + .../x/sys/unix/syscall_openbsd_riscv64.go | 42 + .../golang.org/x/sys/unix/syscall_solaris.go | 1135 ++ .../x/sys/unix/syscall_solaris_amd64.go | 28 + vendor/golang.org/x/sys/unix/syscall_unix.go | 596 ++ .../golang.org/x/sys/unix/syscall_unix_gc.go | 16 + .../x/sys/unix/syscall_unix_gc_ppc64x.go | 25 + .../x/sys/unix/syscall_zos_s390x.go | 1994 ++++ vendor/golang.org/x/sys/unix/sysvshm_linux.go | 21 + vendor/golang.org/x/sys/unix/sysvshm_unix.go | 52 + .../x/sys/unix/sysvshm_unix_other.go | 14 + vendor/golang.org/x/sys/unix/timestruct.go | 77 + .../golang.org/x/sys/unix/unveil_openbsd.go | 42 + vendor/golang.org/x/sys/unix/xattr_bsd.go | 281 + .../golang.org/x/sys/unix/zerrors_aix_ppc.go | 1385 +++ .../x/sys/unix/zerrors_aix_ppc64.go | 1386 +++ .../x/sys/unix/zerrors_darwin_amd64.go | 1911 ++++ .../x/sys/unix/zerrors_darwin_arm64.go | 1911 ++++ .../x/sys/unix/zerrors_dragonfly_amd64.go | 1738 +++ .../x/sys/unix/zerrors_freebsd_386.go | 2043 ++++ .../x/sys/unix/zerrors_freebsd_amd64.go | 2040 ++++ .../x/sys/unix/zerrors_freebsd_arm.go | 2034 ++++ .../x/sys/unix/zerrors_freebsd_arm64.go | 2034 ++++ .../x/sys/unix/zerrors_freebsd_riscv64.go | 2148 ++++ vendor/golang.org/x/sys/unix/zerrors_linux.go | 3513 ++++++ .../x/sys/unix/zerrors_linux_386.go | 829 ++ .../x/sys/unix/zerrors_linux_amd64.go | 829 ++ .../x/sys/unix/zerrors_linux_arm.go | 835 ++ .../x/sys/unix/zerrors_linux_arm64.go | 829 ++ .../x/sys/unix/zerrors_linux_loong64.go | 819 ++ .../x/sys/unix/zerrors_linux_mips.go | 836 ++ .../x/sys/unix/zerrors_linux_mips64.go | 836 ++ .../x/sys/unix/zerrors_linux_mips64le.go | 836 ++ .../x/sys/unix/zerrors_linux_mipsle.go | 836 ++ .../x/sys/unix/zerrors_linux_ppc.go | 888 ++ .../x/sys/unix/zerrors_linux_ppc64.go | 892 ++ .../x/sys/unix/zerrors_linux_ppc64le.go | 892 ++ .../x/sys/unix/zerrors_linux_riscv64.go | 816 ++ .../x/sys/unix/zerrors_linux_s390x.go | 891 ++ .../x/sys/unix/zerrors_linux_sparc64.go | 934 ++ .../x/sys/unix/zerrors_netbsd_386.go | 1780 ++++ .../x/sys/unix/zerrors_netbsd_amd64.go | 1770 +++ .../x/sys/unix/zerrors_netbsd_arm.go | 1759 +++ .../x/sys/unix/zerrors_netbsd_arm64.go | 1770 +++ .../x/sys/unix/zerrors_openbsd_386.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_amd64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_arm.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_arm64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_mips64.go | 1906 ++++ .../x/sys/unix/zerrors_openbsd_ppc64.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_riscv64.go | 1904 ++++ .../x/sys/unix/zerrors_solaris_amd64.go | 1557 +++ .../x/sys/unix/zerrors_zos_s390x.go | 860 ++ .../x/sys/unix/zptrace_armnn_linux.go | 42 + .../x/sys/unix/zptrace_linux_arm64.go | 17 + .../x/sys/unix/zptrace_mipsnn_linux.go | 51 + .../x/sys/unix/zptrace_mipsnnle_linux.go | 51 + .../x/sys/unix/zptrace_x86_linux.go | 81 + .../golang.org/x/sys/unix/zsyscall_aix_ppc.go | 1484 +++ .../x/sys/unix/zsyscall_aix_ppc64.go | 1443 +++ .../x/sys/unix/zsyscall_aix_ppc64_gc.go | 1189 +++ .../x/sys/unix/zsyscall_aix_ppc64_gccgo.go | 1070 ++ .../x/sys/unix/zsyscall_darwin_amd64.go | 2572 +++++ .../x/sys/unix/zsyscall_darwin_amd64.s | 903 ++ .../x/sys/unix/zsyscall_darwin_arm64.go | 2572 +++++ .../x/sys/unix/zsyscall_darwin_arm64.s | 903 ++ .../x/sys/unix/zsyscall_dragonfly_amd64.go | 1689 +++ .../x/sys/unix/zsyscall_freebsd_386.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_amd64.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_arm.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_arm64.go | 1909 ++++ .../x/sys/unix/zsyscall_freebsd_riscv64.go | 1909 ++++ .../x/sys/unix/zsyscall_illumos_amd64.go | 102 + .../golang.org/x/sys/unix/zsyscall_linux.go | 2199 ++++ .../x/sys/unix/zsyscall_linux_386.go | 487 + .../x/sys/unix/zsyscall_linux_amd64.go | 654 ++ .../x/sys/unix/zsyscall_linux_arm.go | 602 ++ .../x/sys/unix/zsyscall_linux_arm64.go | 553 + .../x/sys/unix/zsyscall_linux_loong64.go | 487 + .../x/sys/unix/zsyscall_linux_mips.go | 654 ++ .../x/sys/unix/zsyscall_linux_mips64.go | 648 ++ .../x/sys/unix/zsyscall_linux_mips64le.go | 637 ++ .../x/sys/unix/zsyscall_linux_mipsle.go | 654 ++ .../x/sys/unix/zsyscall_linux_ppc.go | 659 ++ .../x/sys/unix/zsyscall_linux_ppc64.go | 705 ++ .../x/sys/unix/zsyscall_linux_ppc64le.go | 705 ++ .../x/sys/unix/zsyscall_linux_riscv64.go | 533 + .../x/sys/unix/zsyscall_linux_s390x.go | 496 + .../x/sys/unix/zsyscall_linux_sparc64.go | 649 ++ .../x/sys/unix/zsyscall_netbsd_386.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_amd64.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_arm.go | 1860 ++++ .../x/sys/unix/zsyscall_netbsd_arm64.go | 1860 ++++ .../x/sys/unix/zsyscall_openbsd_386.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_386.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_amd64.go | 2253 ++++ .../x/sys/unix/zsyscall_openbsd_amd64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_arm.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_arm.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_arm64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_arm64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_mips64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_mips64.s | 674 ++ .../x/sys/unix/zsyscall_openbsd_ppc64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_ppc64.s | 808 ++ .../x/sys/unix/zsyscall_openbsd_riscv64.go | 2251 ++++ .../x/sys/unix/zsyscall_openbsd_riscv64.s | 674 ++ .../x/sys/unix/zsyscall_solaris_amd64.go | 2104 ++++ .../x/sys/unix/zsyscall_zos_s390x.go | 1265 +++ .../x/sys/unix/zsysctl_openbsd_386.go | 281 + .../x/sys/unix/zsysctl_openbsd_amd64.go | 281 + .../x/sys/unix/zsysctl_openbsd_arm.go | 281 + .../x/sys/unix/zsysctl_openbsd_arm64.go | 281 + .../x/sys/unix/zsysctl_openbsd_mips64.go | 281 + .../x/sys/unix/zsysctl_openbsd_ppc64.go | 281 + .../x/sys/unix/zsysctl_openbsd_riscv64.go | 282 + .../x/sys/unix/zsysnum_darwin_amd64.go | 440 + .../x/sys/unix/zsysnum_darwin_arm64.go | 438 + .../x/sys/unix/zsysnum_dragonfly_amd64.go | 317 + .../x/sys/unix/zsysnum_freebsd_386.go | 394 + .../x/sys/unix/zsysnum_freebsd_amd64.go | 394 + .../x/sys/unix/zsysnum_freebsd_arm.go | 394 + .../x/sys/unix/zsysnum_freebsd_arm64.go | 394 + .../x/sys/unix/zsysnum_freebsd_riscv64.go | 394 + .../x/sys/unix/zsysnum_linux_386.go | 450 + .../x/sys/unix/zsysnum_linux_amd64.go | 372 + .../x/sys/unix/zsysnum_linux_arm.go | 414 + .../x/sys/unix/zsysnum_linux_arm64.go | 317 + .../x/sys/unix/zsysnum_linux_loong64.go | 311 + .../x/sys/unix/zsysnum_linux_mips.go | 434 + .../x/sys/unix/zsysnum_linux_mips64.go | 364 + .../x/sys/unix/zsysnum_linux_mips64le.go | 364 + .../x/sys/unix/zsysnum_linux_mipsle.go | 434 + .../x/sys/unix/zsysnum_linux_ppc.go | 441 + .../x/sys/unix/zsysnum_linux_ppc64.go | 413 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 413 + .../x/sys/unix/zsysnum_linux_riscv64.go | 316 + .../x/sys/unix/zsysnum_linux_s390x.go | 379 + .../x/sys/unix/zsysnum_linux_sparc64.go | 392 + .../x/sys/unix/zsysnum_netbsd_386.go | 275 + .../x/sys/unix/zsysnum_netbsd_amd64.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm64.go | 275 + .../x/sys/unix/zsysnum_openbsd_386.go | 220 + .../x/sys/unix/zsysnum_openbsd_amd64.go | 220 + .../x/sys/unix/zsysnum_openbsd_arm.go | 220 + .../x/sys/unix/zsysnum_openbsd_arm64.go | 219 + .../x/sys/unix/zsysnum_openbsd_mips64.go | 222 + .../x/sys/unix/zsysnum_openbsd_ppc64.go | 218 + .../x/sys/unix/zsysnum_openbsd_riscv64.go | 219 + .../x/sys/unix/zsysnum_zos_s390x.go | 2670 +++++ .../golang.org/x/sys/unix/ztypes_aix_ppc.go | 354 + .../golang.org/x/sys/unix/ztypes_aix_ppc64.go | 358 + .../x/sys/unix/ztypes_darwin_amd64.go | 806 ++ .../x/sys/unix/ztypes_darwin_arm64.go | 806 ++ .../x/sys/unix/ztypes_dragonfly_amd64.go | 474 + .../x/sys/unix/ztypes_freebsd_386.go | 651 ++ .../x/sys/unix/ztypes_freebsd_amd64.go | 656 ++ .../x/sys/unix/ztypes_freebsd_arm.go | 642 ++ .../x/sys/unix/ztypes_freebsd_arm64.go | 636 ++ .../x/sys/unix/ztypes_freebsd_riscv64.go | 638 ++ vendor/golang.org/x/sys/unix/ztypes_linux.go | 5865 ++++++++++ .../golang.org/x/sys/unix/ztypes_linux_386.go | 698 ++ .../x/sys/unix/ztypes_linux_amd64.go | 713 ++ .../golang.org/x/sys/unix/ztypes_linux_arm.go | 693 ++ .../x/sys/unix/ztypes_linux_arm64.go | 692 ++ .../x/sys/unix/ztypes_linux_loong64.go | 693 ++ .../x/sys/unix/ztypes_linux_mips.go | 698 ++ .../x/sys/unix/ztypes_linux_mips64.go | 695 ++ .../x/sys/unix/ztypes_linux_mips64le.go | 695 ++ .../x/sys/unix/ztypes_linux_mipsle.go | 698 ++ .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 706 ++ .../x/sys/unix/ztypes_linux_ppc64.go | 701 ++ .../x/sys/unix/ztypes_linux_ppc64le.go | 701 ++ .../x/sys/unix/ztypes_linux_riscv64.go | 720 ++ .../x/sys/unix/ztypes_linux_s390x.go | 715 ++ .../x/sys/unix/ztypes_linux_sparc64.go | 696 ++ .../x/sys/unix/ztypes_netbsd_386.go | 586 + .../x/sys/unix/ztypes_netbsd_amd64.go | 594 ++ .../x/sys/unix/ztypes_netbsd_arm.go | 591 + .../x/sys/unix/ztypes_netbsd_arm64.go | 594 ++ .../x/sys/unix/ztypes_openbsd_386.go | 569 + .../x/sys/unix/ztypes_openbsd_amd64.go | 569 + .../x/sys/unix/ztypes_openbsd_arm.go | 576 + .../x/sys/unix/ztypes_openbsd_arm64.go | 569 + .../x/sys/unix/ztypes_openbsd_mips64.go | 569 + .../x/sys/unix/ztypes_openbsd_ppc64.go | 571 + .../x/sys/unix/ztypes_openbsd_riscv64.go | 571 + .../x/sys/unix/ztypes_solaris_amd64.go | 517 + .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 415 + vendor/golang.org/x/sys/windows/aliases.go | 13 + .../golang.org/x/sys/windows/dll_windows.go | 416 + vendor/golang.org/x/sys/windows/empty.s | 9 + .../golang.org/x/sys/windows/env_windows.go | 54 + vendor/golang.org/x/sys/windows/eventlog.go | 21 + .../golang.org/x/sys/windows/exec_windows.go | 183 + .../x/sys/windows/memory_windows.go | 48 + vendor/golang.org/x/sys/windows/mkerrors.bash | 70 + .../x/sys/windows/mkknownfolderids.bash | 27 + vendor/golang.org/x/sys/windows/mksyscall.go | 10 + vendor/golang.org/x/sys/windows/race.go | 31 + vendor/golang.org/x/sys/windows/race0.go | 26 + .../x/sys/windows/security_windows.go | 1444 +++ vendor/golang.org/x/sys/windows/service.go | 258 + .../x/sys/windows/setupapi_windows.go | 1425 +++ vendor/golang.org/x/sys/windows/str.go | 23 + vendor/golang.org/x/sys/windows/syscall.go | 105 + .../x/sys/windows/syscall_windows.go | 1819 ++++ .../golang.org/x/sys/windows/types_windows.go | 3349 ++++++ .../x/sys/windows/types_windows_386.go | 35 + .../x/sys/windows/types_windows_amd64.go | 34 + .../x/sys/windows/types_windows_arm.go | 35 + .../x/sys/windows/types_windows_arm64.go | 34 + .../x/sys/windows/zerrors_windows.go | 9468 +++++++++++++++++ .../x/sys/windows/zknownfolderids_windows.go | 149 + .../x/sys/windows/zsyscall_windows.go | 4341 ++++++++ vendor/modules.txt | 24 + 422 files changed, 226058 insertions(+) create mode 100644 .gitignore create mode 100644 Design.txt create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 Story.txt create mode 100644 buny.go create mode 100644 collection.go create mode 100644 data/sample_blacklist.json create mode 100644 data/sample_config.json create mode 100644 data/sample_whitelist.json create mode 100644 event_parser.go create mode 100644 globals.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib.go create mode 100644 main.go create mode 100644 types.go create mode 100644 vendor/github.com/davecgh/go-spew/LICENSE create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypass.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypasssafe.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/common.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/config.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/doc.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/dump.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/format.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/spew.go create mode 100644 vendor/github.com/eleksir/go-xmpp/LICENSE create mode 100644 vendor/github.com/eleksir/go-xmpp/README.md create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_avatar.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_disco.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_muc.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_ping.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_pubsub.go create mode 100644 vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go create mode 100644 vendor/github.com/hjson/hjson-go/.gitignore create mode 100644 vendor/github.com/hjson/hjson-go/LICENSE create mode 100644 vendor/github.com/hjson/hjson-go/README.md create mode 100644 vendor/github.com/hjson/hjson-go/build_release.sh create mode 100644 vendor/github.com/hjson/hjson-go/decode.go create mode 100644 vendor/github.com/hjson/hjson-go/encode.go create mode 100644 vendor/github.com/hjson/hjson-go/parseNumber.go create mode 100644 vendor/github.com/sirupsen/logrus/.gitignore create mode 100644 vendor/github.com/sirupsen/logrus/.golangci.yml create mode 100644 vendor/github.com/sirupsen/logrus/.travis.yml create mode 100644 vendor/github.com/sirupsen/logrus/CHANGELOG.md create mode 100644 vendor/github.com/sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/sirupsen/logrus/README.md create mode 100644 vendor/github.com/sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/sirupsen/logrus/appveyor.yml create mode 100644 vendor/github.com/sirupsen/logrus/buffer_pool.go create mode 100644 vendor/github.com/sirupsen/logrus/doc.go create mode 100644 vendor/github.com/sirupsen/logrus/entry.go create mode 100644 vendor/github.com/sirupsen/logrus/exported.go create mode 100644 vendor/github.com/sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/logger.go create mode 100644 vendor/github.com/sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_bsd.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_js.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_solaris.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_unix.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_windows.go create mode 100644 vendor/github.com/sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/writer.go create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go create mode 100644 vendor/golang.org/x/sys/LICENSE create mode 100644 vendor/golang.org/x/sys/PATENTS create mode 100644 vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go create mode 100644 vendor/golang.org/x/sys/unix/.gitignore create mode 100644 vendor/golang.org/x/sys/unix/README.md create mode 100644 vendor/golang.org/x/sys/unix/affinity_linux.go create mode 100644 vendor/golang.org/x/sys/unix/aliases.go create mode 100644 vendor/golang.org/x/sys/unix/asm_aix_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_loong64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mips64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mipsx.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_solaris_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/bluetooth_linux.go create mode 100644 vendor/golang.org/x/sys/unix/cap_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/constants.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/dev_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/dev_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/dev_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_linux.go create mode 100644 vendor/golang.org/x/sys/unix/dev_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_zos.go create mode 100644 vendor/golang.org/x/sys/unix/dirent.go create mode 100644 vendor/golang.org/x/sys/unix/endian_big.go create mode 100644 vendor/golang.org/x/sys/unix/endian_little.go create mode 100644 vendor/golang.org/x/sys/unix/env_unix.go create mode 100644 vendor/golang.org/x/sys/unix/epoll_zos.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_linux_32bit.go create mode 100644 vendor/golang.org/x/sys/unix/fdset.go create mode 100644 vendor/golang.org/x/sys/unix/fstatfs_zos.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo_c.c create mode 100644 vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ifreq_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_signed.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_unsigned.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_zos.go create mode 100644 vendor/golang.org/x/sys/unix/mkall.sh create mode 100644 vendor/golang.org/x/sys/unix/mkerrors.sh create mode 100644 vendor/golang.org/x/sys/unix/mremap.go create mode 100644 vendor/golang.org/x/sys/unix/pagesize_unix.go create mode 100644 vendor/golang.org/x/sys/unix/pledge_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_ios.go create mode 100644 vendor/golang.org/x/sys/unix/race.go create mode 100644 vendor/golang.org/x/sys/unix/race0.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdents.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdirentries.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/syscall.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_illumos.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_alarm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_libc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/timestruct.go create mode 100644 vendor/golang.org/x/sys/unix/unveil_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/xattr_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_armnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnnle_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_x86_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/windows/aliases.go create mode 100644 vendor/golang.org/x/sys/windows/dll_windows.go create mode 100644 vendor/golang.org/x/sys/windows/empty.s create mode 100644 vendor/golang.org/x/sys/windows/env_windows.go create mode 100644 vendor/golang.org/x/sys/windows/eventlog.go create mode 100644 vendor/golang.org/x/sys/windows/exec_windows.go create mode 100644 vendor/golang.org/x/sys/windows/memory_windows.go create mode 100644 vendor/golang.org/x/sys/windows/mkerrors.bash create mode 100644 vendor/golang.org/x/sys/windows/mkknownfolderids.bash create mode 100644 vendor/golang.org/x/sys/windows/mksyscall.go create mode 100644 vendor/golang.org/x/sys/windows/race.go create mode 100644 vendor/golang.org/x/sys/windows/race0.go create mode 100644 vendor/golang.org/x/sys/windows/security_windows.go create mode 100644 vendor/golang.org/x/sys/windows/service.go create mode 100644 vendor/golang.org/x/sys/windows/setupapi_windows.go create mode 100644 vendor/golang.org/x/sys/windows/str.go create mode 100644 vendor/golang.org/x/sys/windows/syscall.go create mode 100644 vendor/golang.org/x/sys/windows/syscall_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_386.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_amd64.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm64.go create mode 100644 vendor/golang.org/x/sys/windows/zerrors_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zknownfolderids_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zsyscall_windows.go create mode 100644 vendor/modules.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d6aa15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.idea/** +.vscode/** +.vstags +*.bak +*.tmp +**/*.bak +**/*.tmp +data/config.json +data/whitelist.json +data/blacklist.json +buny-jabber-bot diff --git a/Design.txt b/Design.txt new file mode 100644 index 0000000..55a54fe --- /dev/null +++ b/Design.txt @@ -0,0 +1,51 @@ +Oсобенности реализации клиентской части. + +Общие положения +------------------------------------------------------------------------------------------------------------------------ + +Nick для room-ов един и не может задаваться на per-conference основе. На этом завязаны отсылка ping-ов и определение +pong-ов, как минимум. Хотя формально джойниться можно с любым ником :) но что-то не хочется потом распутывать этот +клубок. + + +XEP-0410 MUC Self-Ping (Schrödinger's Chat) +------------------------------------------------------------------------------------------------------------------------ + +xep-0410 реализован не польностью. Согласно https://xmpp.org/extensions/xep-0410.html, существует минимум 2 варианта +реализации обнаружения, что киент оотвалился от чатика. + +* С двухступенчатым пингом. Когда с jid-а локального пнгуется jid в конференции. Ответ доставляется на один из клиентов. +Который в свою очередь должен ответить на этот ответ (отвечайте на мои ответы!), результат этого ответа передаётся +оригинальному запросившему клиенту. + +* С серверной орптимизацией. (оптимизация анонсируется в ответе на запрос disco#info MUC-а) +В этом случае пинг будет одноступенчатым и нам сразу же должен по идее вернуться result (то бишь pong) + +Двухступенчатая реализация вызывает вопросы. Во-первых, своей двухступенчатастью, а во-вторых, ответ на начальный пинг +доставляется на один из клиентов (не на все!), а если этот клиент испытывает сетевые проблемы, то получается, страдают +все клиенты. В нашем случае предполагается, что если клиентов больше одного, то он не должен своим состоянием аффектить +других клиентов. + +И... тут есть другой момент - двухступенчатый пинг гораздо сложнее в реализации на клиентской стороне, т.к. надо +обрабатывать значительно бОльшее количество кейсов сочетаний ошибок, которые могут возникнуть в ходе пинга. + +Вобщем, поддерживаем только второй вариант пинга, который с серверной оптимизацией. + +N.B. Но на IQ get ping с xmlns="urn:xmpp:ping" из первого шага мы всё-таки будем отвечать обычным result-ом (понгом, то + бишь) + +N.B. Каждый пинг комнаты происходит в отдельной goroutne-ке, так что заходить в огромное количеств комнат (>1k) + не рекомендуется + + +xep-0199 XMPP Ping +------------------------------------------------------------------------------------------------------------------------ + +Cуществует несколько видов пинга: +* Server-To-Client Pings +* Client-To-Server Pings +* Server-To-Server Pings +* Client-to-Client Pings +* Component-to-Client Pings + +Явным образом я реализовываю с2s. Плюс частично Component-to-Client в рамках N.B. по xep-0410. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..13255ce --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2022 Sergei Fedosoff + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..175118e --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +#!/usr/bin/env gmake -f + +GOOPTS=CGO_ENABLED=0 +BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath + +all: clean build + +build: + ${GOOPTS} go build ${BUILDOPTS} -o "buny-jabber-bot" collection.go types.go globals.go lib.go event_parser.go \ + buny.go main.go + +clean: + go clean + +upgrade: + rm -rf vendor + go get -d -u -t ./... + go mod tidy + go mod vendor + +# vim: set ft=make noet ai ts=4 sw=4 sts=4: diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff9d897 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Buny Jabber Bot + +Простой бот, работающий по протоколу xmpp. Его задача - банить абьюзеров чата. + +## Что он может? + +Имеет возможность соединяться с сервером по протоколу xmpp как с шифрованием, так и без. Шифрование работает по +классическим механикам (шифрованный канал связи, без возможности коммуникации в незашифрованном виде), без поддержки +механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает. + +Имеет возможность заносить пользователей в бан-лист согласно заданным в конфиге правилам. + +## Что он не может? + +* Не может заходить в комнаты, защищённые паролем. +* Не умеет разгадывать капчу. + +## Как заставить его работать? + +Этот бот автоматизирует уборку человеческой грязи. Самый важный вопрос, кторый следует себе задать еред его +использоованием - а не забаню ли я кого-то не того? + +Технически, бот собирается гошкой 1.20 поод платформу linux. Работоспособность на других платформах не тестировалась. + +Чтобы получить готовые бинарники достаточно выполнить команду: + +```bash +make +``` + +в итоге получится бинарник **buny-jabber-bot**, которому нужен конфиг data/config.json для работы. + +А также понадобятся **data/blacklist.json** и **data/whitelist.json** в которых списки правил, согласно которым *точно* +надо банить злодеев и списки пользователей, которых *точно* не надо банить. + +### Замечание + +Очевидным образом в комнате банить может только админ. Поэтому боту придётся выдать админские права. А комнату сделать +не анонимной (то есть, чтобы как минимум админы могли видеть реальный jid, потому что в бан отправляется именно он). + +## Как работает бан? + +Никакой магии, всё по стандарту, согласно [xep-0045](https://xmpp.org/extensions/xep-0045.html#ban). + +Серверу направляется команда изменения участия (affiliation) зашедшего jid-а и задаётся значение outcast, на что сервер +помещает этот jid в список изгнанных из комнаты (бан-лист конкретной комнаты, в которой присутствует бот) и выгоняет +этого jid-а. + +### В чём тонкость? + +Ровно в том, что для исполнения действия, боту надо заметить presence участника, а presence посылается +при изменении состояния клиента. То есть, если jid уже был в комнате на момент захода бота в неё, то его не забанят +(во всяком случае моментально). Самый простой способ поместить таких персонажей в бан - просто кикнуть их. Ихний +affiliation сменится на none, бот заметит presence и, если jid подпадает под правила чёрного списка, то отправит этот +jid в бан. + +Но, простите, ведь "по правилам", при заходе клиента в комнату ему присылается presence всех участников, чтобы он мог +построить локальный ростер комнаты и начать отслеживать изменения состояния участников конференции. Почему бы в этот +момент не банить злодеев? + +Да, так делать можно, однако пока этого нет. Дело в том, что presence самого себя участнику приходит последним (после +presence-ов тех, кто находится в комнате на момент появления в ней бота), и, согласно этому событию, можно считать, что +клиент зашёл в комнату успешно и формально *только после этого*, клиент может что-то пытаться делать в этой комнате. +Соответственно, чтобы кого-то банить исходя из списка presense-ов, который прилетает на входе, необходимо реализовать +"одноразовую" очередь тех, кого имеет смысл забанить и разгрести её "когда будет можно". Эта задача факультативная, +возможно, она будет решена потом. + +## Как работает белый список? + +Белый список - это список jid-ов, которых банить нельзя ни при каких обстоятельствах. В нём всегда неявно присутсвует +сам бот. + +### Можно ли почистить ban-list? + +Да, бывают ситуации, когда по ошибке бот может забанить кого-то не того или, если не настроен белый список вообще ни в +каком виде, то будет банить всех входящих. И если вдруг произошло так, что в бан попали все админы комнаты, то разбанить +их может либо овнер (никто же не даст овнера боту), либо тот, кто зайдёт в комнату из-под аккаунта бота и разбанить всех +от его имени. + +В далёких планах есть идея релизовать очистку бан-листа при запуске бота с настройкой `"wipe_bans": true`` в +whitelist.json для канала; кроме того, есть планы такое реализовать и в качестве команды, когда у бота появятся команды. + +## Дисклеймер + +Весь дисклеймер в файле LICENSE.txt :) + +## Благодарности + +Во-первых `сообществу`, без него не было бы чятиков. + +Во-вторых `batman46(new)` из конференций на `jabber.ru`, за наблюдения и за некоторые идеи, воплощённыые в этом боте. + +В-третьих `U2` из конференции `radio@conference.jabber.ru` за мысли, идеи и за позитивный настрой. diff --git a/Story.txt b/Story.txt new file mode 100644 index 0000000..535c803 --- /dev/null +++ b/Story.txt @@ -0,0 +1,16 @@ +Конечно, очень печально, что приходится развивать свои навыки программизмов на программах с такой задачей. Но жизнь не +всегда радует своими проявлениями. + +История бота довольно банальна: к нам в чятик стал набергать один товарищ и нести чушь про политоту. Если его банить +классическими средствами, он просто создавал новый аккаунт и снова набегал гадить. + +И таким образом родилась идея как-то автоматизировать его бан. + +Поскольку сама идея достойна мема, то боту было дано имя buny. Как отсылка к комикс-стрипу buni, к персонажу +banhammer-тян и как отсылка к персонажу по имени Бани из сериала про Сейлор Мун (если верить Википедии, то Бани на самом +деле это Usagi Tsukino, но у русских переводчиков своё видение мира). + +Jid-ом бота стал buny@jabber.ru, а девизом - надпись из школьного туалета (исковерканный девиз из Сейлор Мун): +"Облегчающая сила Луны, помоги мне!". + +С портретом, традиционно, заминка. diff --git a/buny.go b/buny.go new file mode 100644 index 0000000..2ca627d --- /dev/null +++ b/buny.go @@ -0,0 +1,143 @@ +package main + +import ( + "os" + "regexp" + "strings" + + "github.com/davecgh/go-spew/spew" + "github.com/eleksir/go-xmpp" + log "github.com/sirupsen/logrus" +) + +func buny(v xmpp.Presence) error { + var err error + + // Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном + // списке, заносим пидора в список outcast-ов + if v.JID != "" { + // На всякий случай: себя никогда не баним, явным образом + if v.JID == talk.JID() { + return err + } + + room := strings.SplitN(v.From, "/", 2)[0] + + if room == "" { + log.Infof("We got empty room in repsence event, which kinda strange: %s", spew.Sdump(v)) + + return err + } + + goodJid := strings.SplitN(v.JID, "/", 2)[0] + + // Обрабатываем правила белого списка + for _, good := range whiteList.Whitelist { + // Глобальный белый список + if good.RoomName == "" { + for _, person := range good.Jid { + if person == goodJid { + return err + } + } + } + + // Белый список для конкретных каналов + if good.RoomName == room { + for _, person := range good.Jid { + if person == goodJid { + return err + } + } + } + } + + evilJid := strings.SplitN(v.JID, "/", 2)[0] + + // Обрабатываем правила чёрного списка + for _, cRoom := range roomsConnected { + if cRoom == room { + for _, bEntry := range blackList.Blacklist { + + // Обработаем правила гобального чёрного списка + if bEntry.RoomName == "" { + for _, jid_regexp := range bEntry.JidRe { + re, err := regexp.Compile(jid_regexp) + + if err != nil { + log.Errorf("Incorrect regexp in blacklist: %s, skipping", jid_regexp) + + continue + } + + if re.Match([]byte(evilJid)) { + log.Infof("Hammer falls on %s", v.JID) + // https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением + ban := "" + ban += "" + ban += "" + + if id, err := talk.RawInformationQuery( + talk.JID(), + room, + "ban1", + xmpp.IQTypeSet, + "http://jabber.org/protocol/muc#admin", + ban, + ); err != nil { + log.Errorf("Unable to ban user: id=%s, err=%s", id, err) + os.Exit(1) + } + + return err + } + + } + + continue + } + + // Обработаем правила конкретного канала, комнаты конференции + if bEntry.RoomName == room { + for _, jid_regexp := range bEntry.JidRe { + re, err := regexp.Compile(jid_regexp) + + if err != nil { + log.Errorf("Incorrect regexp in blacklist: %s, skipping", jid_regexp) + + continue + } + + if re.Match([]byte(evilJid)) { + log.Infof("Hammer falls on %s", v.JID) + // https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением + ban := "" + ban += "" + ban += "" + + if id, err := talk.RawInformationQuery( + talk.JID(), + room, + "ban1", + xmpp.IQTypeSet, + "http://jabber.org/protocol/muc#admin", + ban, + ); err != nil { + log.Errorf("Unable to ban user: id=%s, err=%s", id, err) + os.Exit(1) + } + + return err + } + + } + + continue + } + } + } + } + } + + return err +} diff --git a/collection.go b/collection.go new file mode 100644 index 0000000..9805c97 --- /dev/null +++ b/collection.go @@ -0,0 +1,69 @@ +package main + +import ( + "sync" +) + +// Collection это sync.Map-ка, хранящая в себе произвольные итемы +type Collection struct { + items sync.Map + close chan struct{} +} + +// Item представляет собой интерфейс для хранения произвольных данных +type item struct { + data interface{} +} + +// NewCollection создаёт инстанс коллекции +func NewCollection() *Collection { + c := &Collection{ + close: make(chan struct{}), + } + + return c +} + +// Get достаёт данные по заданному ключу из коллекции +func (collection *Collection) Get(key interface{}) (interface{}, bool) { + obj, exists := collection.items.Load(key) + + if !exists { + return nil, false + } + + item := obj.(item) + + return item.data, true +} + +// Set сохраняет данные с заданным ключом в коллекцию +func (collection *Collection) Set(key interface{}, value interface{}) { + collection.items.Store(key, item{ + data: value, + }) +} + +// Range применяет функцию f ко всем ключам в коллекции +func (collection *Collection) Range(f func(key, value interface{}) bool) { + fn := func(key, value interface{}) bool { + item := value.(item) + + return f(key, item.data) + } + + collection.items.Range(fn) +} + +// Delete удаляет ключ и значение из коллекции данных +func (collection *Collection) Delete(key interface{}) { + collection.items.Delete(key) +} + +// Close очищает и высвобождает ресурсы, занятые коллекцией +func (collection *Collection) Close() { + collection.close <- struct{}{} + collection.items = sync.Map{} +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/data/sample_blacklist.json b/data/sample_blacklist.json new file mode 100644 index 0000000..c43eb4a --- /dev/null +++ b/data/sample_blacklist.json @@ -0,0 +1,15 @@ +{ + "blacklist": [ + { + # Пустой список означет глобальные баны, которые применяются ко всем комнатам, где присутствует бот + "room_name": "", + # Список регулярок JID-ов, которых надо банить + "jid_re": [ + "^[Mm]ary@server.tld/resource1$", + "^[cC]hris(topher)?@", + "^(his|her)_cat@sweet.home/.*$", + "^(her|his)_dog@.*\\.street/.*" + ] + } + ] +} \ No newline at end of file diff --git a/data/sample_config.json b/data/sample_config.json new file mode 100644 index 0000000..90048cb --- /dev/null +++ b/data/sample_config.json @@ -0,0 +1,56 @@ +{ + # Фронт-энд бота + "jabber": { + # Если не задан, будет localhost + "server": "jabber.tld", + # Если не задано - 5222 для нешифрованного соединения или для starttls и 5223 для голого (старорежимного) ssl + "port": 5222, + # Включить шифрование + "ssl": true, + # Это более современный протокол и как раз на нём работет большинство jabber-серверов, порт 5222, + # jabber.ru по этому протоколу как-то странно работает, если его включить, я получаю + # tls: first record does not look like a TLS handshake + "starttls" : true, + # Не проверяем сертификаты по-умолчанию. + # Вам правда хочется всё это проверять? Там по идее могут быть всякие Lets Encrypt, которые хоть и работают, но + # создают кучу проблем + "ssl_verify": false, + # Позволять авторизацию логин-паролем по незашифрованному каналу связи + "insecureallowunencryptedauth": false, + # Таймаут установления соединения с сервером, в секундах. 10 секунд, если не задано. + "connection_timeout": 10, + # Задержка перед пере-коннекчиванием. 3 секунды, если не задано. + "reconnect_delay": 3, + # Промежуток между пингами сервера, для определения его живости. По-умолчанию 60 секунд. + # Пинги посылаются, если в течение этого времени не было активности со стороны сервера. + "server_ping_delay": 60, + # Промежуток между пингами muc-ов. По-умолчанию 900 секунд. (Согласно рекомендации в xep-0410.) + # Пинги посылаются, если в течение этого времени не было активности со стороны muc-а. + "muc_ping_delay": 900, + # Сколько ждать перед переджоиванием к комнате. По-умолчанию, 3 секунды. + "muc_rejoin_delay": 3, + # Ping-to-ping splay delay. Варьируемая задержка между пингами, чтобы не набирать высокий message rate. + # По-умолчанию, 3 секунды. Под капотом преобразуются в миллисекунды. Важна для пингов MUC-ов, особенно, + # если их МНОГО. + "ping_splay_delay": 3, + # Ник бота, с которым он будет заходить в комнаты + "nick": "buny", + "resource" : "buny bot", + # Имя пользователя для авторизации на сервере + "user": "buny@jabber.tld", + # Пароль для авторизации + "password": "secret", + # Jabber-каналы, к которым бот попробует присоединиться при старте + "channels": [ + "my_channel@conference.jabber.tld", + "another_channel@conference.jabber.tld" + ] + }, + + # Многословность логов. Если не задано, то info. Debug - ДЕЙСТВИТЕЛЬНО вербозный уровень логгирования. + # Есть ещё trace :) + "loglevel": "debug", + + # Если не задан, логи идут в STDOUT + "log": "/var/log/echo-jabber-bot/echo-jabber-bot.log", +} diff --git a/data/sample_whitelist.json b/data/sample_whitelist.json new file mode 100644 index 0000000..a8cfd08 --- /dev/null +++ b/data/sample_whitelist.json @@ -0,0 +1,24 @@ +# Пример белого списка +{ + "whitlist": [ + { + # Если room_name пустой или его нет, это список jid-ов, которых мы не баним ни на одном канале, нигде и никогда + "room_name": "", + # Собственно, сам список + "jid": [ + "me@jabber.tld", + "myself@xmpp.tld", + "iren@example.org" + ] + }, + { + # А этот список относится к конференции earth@conference.jabber.tld + "room_name": "earth@conference.jabber.tld", + "jid": [ + "meow@street.net", + "bzzz@hive.org", + "god@heaven.org" + ] + } + ] +} diff --git a/event_parser.go b/event_parser.go new file mode 100644 index 0000000..0544928 --- /dev/null +++ b/event_parser.go @@ -0,0 +1,567 @@ +package main + +import ( + "encoding/xml" + "fmt" + "os" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/eleksir/go-xmpp" + log "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" +) + +func parseEvent(e interface{}) { + lastServerActivity = time.Now().Unix() + + switch v := e.(type) { + // Сообщение в чяти + case xmpp.Chat: + log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s", + v.Type, v.Remote, v.Subject, v.Text) + + if config.NoEcho { + return + } + + // Топик чятика присылается в виде сообщения с subject, но без text + // В то же время сообщения от людей приходят с пустым subject, но с заполненным text + if v.Text != "" { + // Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение + switch v.Type { //nolint:whitespace + + // Групповой чятик + case "groupchat": + log.Debugf("Message from public chat: %s", v.Text) + // dest := strings.SplitN(v.Remote, "/", 2)[0] + nick := strings.SplitN(v.Remote, "/", 2)[1] + + if nick == config.Jabber.Nick { + log.Debug("Skipping message from myself") + + return + } + + lastActivity = lastServerActivity + + if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" { + lastMucActivity.Set(muc, lastServerActivity) + } + + // Приватный чятик + case "chat": + // Здесь у нас может быть 2 вида From: + // slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика + // eleksir@jabber.ru/array.lan - это если мы работаем через ростер + log.Debugf("Private message: %s", v.Text) + + lastActivity = lastServerActivity + + // Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это + // не реализовано, поэтому мы получаем ошибки в форме отдельного чятика + case "error": + // Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят + // асинхронно, связать их с конкретными нашими действиями довольно сложно. + log.Warn(spew.Sdump(e)) + } + } + + // IQ-сообщение - пинг, понг, что-то ещё... + case xmpp.IQ: + // По правилам IQ обязательно должна содержать ID, причём не пустой + if v.ID == "" { + log.Info("Got an IQ stanza with empty id, discarding") + + return + } + + switch v.Type { + case xmpp.IQTypeGet: + log.Debug("Looks like IQ get query") + + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + var iqStruct jabberSimpleIqGetQuery + var parseError bool + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { //nolint:whitespace + + // Запрос номера версии приложения + case "jabber:iq:version": + log.Infof("Got IQ get request for version from %s", v.From) + answer := "" + answer += "buny-jabber-bot" + answer += "1.с_чем-то" + answer += "линупс" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send verion info to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + // Нам прислали попингуй + case "urn:xmpp:ping": + log.Infof("Got IQ get request for pong from %s", v.From) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + "", + ); err != nil { + log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + // У нас запросили время последней активности. В нашем случае это либо время запуска клиента, либо время + // последней фразы в чяти. + case "jabber:iq:last": + log.Infof("Got IQ get last activity time request from %s", v.From) + + answer := fmt.Sprintf( + "", + time.Now().Unix()-lastActivity, + ) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send last activity time to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + // Запрос на список поддерживаемых фич - версия, локальное время, итд + case xmpp.XMPPNS_DISCO_INFO: + log.Infof("Got IQ get disco#info request from %s", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send disco#info to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + // Спрашивают список команд, чтобы поуправлять этим клиентом + case xmpp.XMPPNS_DISCO_ITEMS: + log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf("Unable to send disco#items to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + // Запрашивают что-то, о чём мы не имеем представления + default: + log.Info("Got an unknown IQ get request, discarding") + log.Info(spew.Sdump(e)) + + return + } + } else { + parseError = true + } + + // Попробуем распарсить входящий запрос как urn:xmpp:time + if parseError { + var iqStruct jabberTimeIqGetQuery + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { + case "urn:xmpp:time": + log.Infof("Got IQ get time request from %s", v.From) + + answer := fmt.Sprintf( + "", + time.Now().UTC().Format(time.RFC3339), + ) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + answer, + ); err != nil { + log.Errorf("Unable to send urn:xmpp:time to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + + parseError = false + + default: + log.Info("Got bogus IQ get time request, discarding") + log.Info(spew.Sdump(e)) + + return + } + } else { + parseError = true + } + } + + // Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub + if parseError { + var iqStruct jabberPubsubIQGetQuery + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + switch iqStruct.Xmlns { + case xmpp.XMPPNS_PUBSUB: + log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From) + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf( + "Unable to send pubsub feature unimplemented to jabber server: id=%s, err=%s", + id, + err, + ) + + os.Exit(1) + } + + parseError = false + + default: + log.Info("Got unknown IQ get pubsub something request, discarding") + log.Info(spew.Sdump(e)) + parseError = true + } + } else { + parseError = true + } + } + + // Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat) + // Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации. + // Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно. + if parseError { + var iqStruct jabberIqPing + + if err := xml.Unmarshal(v.Query, &iqStruct); err == nil { + log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From) + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeResult, + "", + ); err != nil { + log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err) + os.Exit(1) + } + } + + parseError = false + } + + // Не знаю, как парсить... залоггируем это дело и пойдём дальше + if parseError { + log.Infof("Does not look like parsable via iqStruct") + log.Info(spew.Sdump(e)) + } + + case xmpp.IQTypeResult: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + switch { + // Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это) + case v.From == config.Jabber.Server && v.To == talk.JID() && string(v.Query) == "": + log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To) + serverPingTimestampRx = time.Now().Unix() + + // Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован + case v.To == talk.JID() && string(v.Query) == "": + mucNameMatch := slices.Contains(roomsConnected, v.From) + + if mucNameMatch { + log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To) + } else { + mucNickMatch := false + + for _, room := range roomsConnected { + mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick) + + if v.From == mucNick { + mucNickMatch = true + + break + } + } + + if mucNickMatch { + log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To) + // Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ + } else { + log.Infof("Got unknown pong from %s to %s", v.From, v.To) + log.Debug(spew.Sdump(e)) + } + } + default: + log.Info("Got an IQ result. Dunno how deal with it, discarding") + log.Debug(spew.Sdump(e)) + } + + // Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем + case xmpp.IQTypeSet: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + log.Info("Got an IQ request for set something. Answer not implemented") + + answer := "" + answer += "" + answer += "" + answer += "" + + if id, err := talk.RawInformation( + v.To, + v.From, + v.ID, + xmpp.IQTypeError, + answer, + ); err != nil { + log.Errorf("Unable to send set feature unimplemented to jabber server: id=%s, err=%s", + id, + err, + ) + + os.Exit(1) + } + + log.Debug(spew.Sdump(e)) + + // Нам прилетело сообщение об ошибке + case xmpp.IQTypeError: + // Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги, + // хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец! + var iqPingStruct jabberIqPing + var parseError = true + + if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil { + if iqPingStruct.Xmlns == "urn:xmpp:ping" { + if v.From == config.Jabber.Server { + msg := "Server announced that it can answer c2s ping, but gives us an error to such query, " + msg += "fallback to keepalive whitespace pings" + log.Error(msg) + + serverCapsList.Set("urn:xmpp:ping", false) + } else { + log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To) + } + } else { + log.Error("Iq parsed as ping, but does not belong to xmlns urn:xmpp:ping") + log.Error(spew.Sdump(e)) + } + + parseError = false + } + + // Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться + if parseError { + var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAccepatble + + if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil { + if v.To == talk.JID() { + nick := strings.SplitN(v.From, "/", 2)[1] + + if slices.Contains(roomsConnected, iqErrorCancelNotAcceptable.By) && nick == config.Jabber.Nick { + log.Errorf( + "Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore", + v.From, v.To, + ) + + time.Sleep(time.Duration(config.Jabber.MucRejoinDelay) * time.Second) + + if _, err := talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil { + log.Errorf("Looks like connection to server also lost") + + os.Exit(1) + } + } else { + log.Error( + "looks like message parsed as jabberIqErrorCancelNotAcceptable but we're not in given room", + ) + + log.Error(spew.Sdump(e)) + } + } else { + log.Error( + "Looks like message parsed as jabberIqErrorCancelNotAcceptable but addressed not to us", + ) + + log.Error(spew.Sdump(e)) + } + + parseError = false + } + } + + if parseError { + log.Error("Unhandled IQ Error message") + log.Error(spew.Sdump(e)) + } + // Нам прилетело что-то неизвестное из семейства IQ stanza + default: + log.Info("Got an unknown IQ request. Dunno how deal with it, discarding") + log.Info(spew.Sdump(e)) + } + + // Смена статуса участника + case xmpp.Presence: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + if v.Type == xmpp.IQTypeError { + // Это событие происходит, когда из чятика выходит другой инстанс клиента. + // Такое бывает, когда 2 инстанса с одинаковым ресурсом, например, начинают "драться" за возможность + // остаться на сервере. Ситуация, которую допускать нельзя, на самом деле, потому что рано или поздно такого + // клиента забанят из-за спама "вошёл-вышел". + // Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать. + if v.Type == "unavailable" { + log.Error("Presence notification - looks like another instance of client leaves room") + + if slices.Contains(roomsConnected, v.From) { + go joinMuc(v.From) + } + } else { + log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s", + v.Type, v.From, v.Show, v.Status) + log.Errorf(spew.Sdump(v)) + } + } else { + log.Infof("Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s, Affilation: %s, Role: %s, JID: %s", + v.Type, v.From, v.To, v.Show, v.Status, v.Affilation, v.Role, v.JID) + + // Это наш собственный Presence + if v.Show == "" && v.Status == "" { + if from := strings.Split(v.From, "/"); from[1] == config.Jabber.Nick { + roomsConnected = append(roomsConnected, from[0]) + } + } + + if err := buny(v); err != nil { + os.Exit(1) + } + } + + // Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info" + case xmpp.DiscoResult: + if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" { + if slices.Contains(roomsConnected, muc) { + lastMucActivity.Set(muc, lastServerActivity) + } + } + + // Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity + // TODO: проверять адресата v.To + for _, ident := range v.Identities { + switch ident.Category { + case "server": + // Конкретно сейчас нас интересует только поддержка c2s ping + for _, feature := range v.Features { + log.Debugf("Server %s announced that it supports feature: %s", v.From, feature) + serverCapsList.Set(feature, true) + } + + serverCapsQueried = true + + case "conference": + mucCaps := make(map[string]bool) + + for _, feature := range v.Features { + log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature) + mucCaps[feature] = true + } + + mucCapsList.Set(v.From, mucCaps) + + case "pubsub": + log.Debugf("PubSub component %s reply to disco#info, skipping", v.From) + + default: + log.Debug("Got unknown reply to disco#info") + log.Debug(spew.Sdump(e)) + } + } + + // Это что-то неизвестное, подампим событие в лог + default: + log.Info(spew.Sdump(e)) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/globals.go b/globals.go new file mode 100644 index 0000000..b5d5012 --- /dev/null +++ b/globals.go @@ -0,0 +1,65 @@ +package main + +import ( + "os" + + "github.com/eleksir/go-xmpp" +) + +// Config - это у нас глобальная штука :) +var config myConfig + +// Списки тех, кого мы не баним никогда +var whiteList myWhiteList + +// Списки злодеев, которых мы банм +var blackList myBlackList + +// Ставится в true, если мы получили сигнал на выключение +var shutdown = false + +// Чтобы не организовывать драку за установку коннекта +var connecting = false + +// Глобальное состояние соединения +var isConnected = false + +// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов +var sigChan = make(chan os.Signal, 1) + +// Основной инстанс xmpp-клиента +var talk *xmpp.Client + +// Опции подключения к xmpp-серверу +var options *xmpp.Options + +// Список комнат, в которых находится бот +var roomsConnected []string + +// Время последней активности, нужно для jabber:iq:last +var lastActivity int64 + +// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с +// сервера +var lastServerActivity int64 + +// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из +// muc-ов +var lastMucActivity = NewCollection() + +// Получен ли ответ на запрос disco#info к серверу +var serverCapsQueried bool + +// sync.Map-ка с капабилити сервера +var serverCapsList = NewCollection() + +// sync.Map-ка с комнатами и их capability +var mucCapsList = NewCollection() + +// Время, когда был отправлен c2s ping +var serverPingTimestampTx int64 + +// Время, когда был принят s2c pong +var serverPingTimestampRx int64 + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b2e1d2b --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module buny-jabber-bot + +go 1.20 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/eleksir/go-xmpp v1.3.0 + github.com/hjson/hjson-go v3.3.0+incompatible + github.com/sirupsen/logrus v1.9.3 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 +) + +require ( + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/sys v0.10.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1cd2df2 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eleksir/go-xmpp v1.3.0 h1:A4vonXmHJ+3W4LW+BDM24GkAT0zRMhCAMZhAqTRarLE= +github.com/eleksir/go-xmpp v1.3.0/go.mod h1:KnvbXvUpttEVFl8l5eDO2Q7J97ZxtTMvOsDWULD2Rn8= +github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI= +github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib.go b/lib.go new file mode 100644 index 0000000..28a8a7b --- /dev/null +++ b/lib.go @@ -0,0 +1,696 @@ +package main + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "path/filepath" + "syscall" + "time" + + "github.com/eleksir/go-xmpp" + "github.com/hjson/hjson-go" + log "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" +) + +// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения +func sigHandler() { + log.Debug("Installing signal handler") + + for s := range sigChan { + switch s { + case syscall.SIGINT: + log.Infoln("Got SIGINT, quitting") + case syscall.SIGTERM: + log.Infoln("Got SIGTERM, quitting") + case syscall.SIGQUIT: + log.Infoln("Got SIGQUIT, quitting") + + // Заходим на новую итерацию, если у нас "неинтересный" сигнал + default: + continue + } + + // Чтобы не срать в логи ошибками, проставим shutdown state приложения в true + shutdown = true + + if isConnected && !shutdown { + log.Debug("Try to set our presence to Unavailable and status to Offline") + + // Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline + // Пока за неимением лучшего сообщим об этом самим себе + for _, room := range roomsConnected { + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + To: room, + Status: "Offline", + Type: "unavailable", + }, + ); err != nil { + log.Infof("Unable to send presence to jabber server: %s", err) + } + } + + // И закрываем соединение + log.Debugf("Closing connection to jabber server") + + if err := talk.Close(); err != nil { + log.Infof("Unable to close connection to jabber server: %s", err) + } + } + + os.Exit(0) + } +} + +// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет +func readConfig() { + configLoaded := false + executablePath, err := os.Executable() + + if err != nil { + log.Errorf("Unable to get current executable path: %s", err) + } + + configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) + + locations := []string{ + "~/.buny-jabber-bot.json", + "~/buny-jabber-bot.json", + "/etc/buny-jabber-bot.json", + configJSONPath, + } + + for _, location := range locations { + fileInfo, err := os.Stat(location) + + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + if err != nil { + continue + } + + // Конфиг-файл длинноват для конфига, попробуем следующего кандидата + if fileInfo.Size() > 65535 { + log.Warnf("Config file %s is too long for config, skipping", location) + + continue + } + + buf, err := os.ReadFile(location) + + // Не удалось прочитать, попробуем следующего кандидата + if err != nil { + log.Warnf("Skip reading config file %s: %s", location, err) + + continue + } + + // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. + // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку + // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. + var sampleConfig myConfig + var tmp map[string]interface{} + err = hjson.Unmarshal(buf, &tmp) + + // Не удалось распарсить - попробуем следующего кандидата + if err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + tmpjson, err := json.Marshal(tmp) + + // Не удалось преобразовать map-ку в json + if err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { + log.Warnf("Skip parsing config file %s: %s", location, err) + + continue + } + + // Значения для Jabber-клиента + if sampleConfig.Jabber.Server == "" { + sampleConfig.Jabber.Server = "localhost" + log.Errorf("Jabber server is not defined in config, using localhost") + } + + if sampleConfig.Jabber.Port == 0 { + sampleConfig.Jabber.Port = 5552 + + if sampleConfig.Jabber.Ssl { + if !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.Port = 5553 + } + } + + log.Infof("Jabber port is not defined in config, using 6667") + } + + if !sampleConfig.Jabber.Ssl { + sampleConfig.Jabber.StartTLS = false + } + + if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS { + sampleConfig.Jabber.SslVerify = false + } + + // sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан + + if sampleConfig.Jabber.ConnectionTimeout == 0 { + sampleConfig.Jabber.ConnectionTimeout = 10 + } + + if sampleConfig.Jabber.ReconnectDelay == 0 { + sampleConfig.Jabber.ReconnectDelay = 3 + } + + if sampleConfig.Jabber.ServerPingDelay == 0 { + sampleConfig.Jabber.ServerPingDelay = 60 + } + + if sampleConfig.Jabber.MucPingDelay == 0 { + sampleConfig.Jabber.MucPingDelay = 900 + } + + if sampleConfig.Jabber.MucRejoinDelay == 0 { + sampleConfig.Jabber.MucRejoinDelay = 3 + } + + if sampleConfig.Jabber.PingSplayDelay == 0 { + sampleConfig.Jabber.PingSplayDelay = 3 + } + + if sampleConfig.Jabber.Nick == "" { + log.Errorf("Jabber nick is not defined in config, quitting") + os.Exit(1) + } + + if sampleConfig.Jabber.Resource == "" { + sampleConfig.Jabber.Resource = "buny bot" + } + + if sampleConfig.Jabber.User == "" { + sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server) + } + + // Если sampleConfig.Jabber.Password не задан, то авторизации не будет + // Если sampleConfig.Jabber.Sasl не задан, то авторизация происходит через NickServ + + // Нам бот нужен в каких-то чат-румах, а не "просто так" + if len(sampleConfig.Jabber.Channels) < 1 { + log.Errorf("No jabber channels/rooms defined in config, quitting") + os.Exit(1) + } + + if sampleConfig.Loglevel == "" { + sampleConfig.Loglevel = "info" + } + + // sampleConfig.Log = "" if not set + + config = sampleConfig + configLoaded = true + log.Infof("Using %s as config file", location) + + break + } + + if !configLoaded { + log.Error("Config was not loaded! Refusing to start.") + os.Exit(1) + } +} + +// Читает и валидирует белые списки пользователей +func readWhitelist() { + whitelistLoaded := false + executablePath, err := os.Executable() + + if err != nil { + log.Errorf("Unable to get current executable path: %s", err) + } + + whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath)) + + locations := []string{ + "~/.buny-jabber-bot-whitelist.json", + "~/buny-jabber-bot-whitelist.json", + "/etc/buny-jabber-bot-whitelist.json", + whitelistJSONPath, + } + + for _, location := range locations { + fileInfo, err := os.Stat(location) + + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + if err != nil { + continue + } + + // Файл белого списка длинноват для белого списка, попробуем следующего кандидата + if fileInfo.Size() > 2097152 { + log.Warnf("Whitelist file %s is too long for whitelist, skipping", location) + + continue + } + + buf, err := os.ReadFile(location) + + // Не удалось прочитать, попробуем следующего кандидата + if err != nil { + log.Warnf("Skip reading whitelist file %s: %s", location, err) + + continue + } + + // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. + // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку + // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. + var sampleWhitelist myWhiteList + var tmp map[string]interface{} + err = hjson.Unmarshal(buf, &tmp) + + // Не удалось распарсить - попробуем следующего кандидата + if err != nil { + log.Warnf("Skip parsing whitelist file %s: %s", location, err) + + continue + } + + tmpjson, err := json.Marshal(tmp) + + // Не удалось преобразовать map-ку в json + if err != nil { + log.Warnf("Skip parsing whitelist file %s: %s", location, err) + + continue + } + + if err := json.Unmarshal(tmpjson, &sampleWhitelist); err != nil { + log.Warnf("Skip parsing whitelist file %s: %s", location, err) + + continue + } + + whiteList = sampleWhitelist + whitelistLoaded = true + log.Infof("Using %s as whiteList file", location) + + break + } + + if !whitelistLoaded { + log.Error("Whitelist was not loaded! Refusing to start.") + os.Exit(1) + } +} + +// Читает и валидирует чёрные списки пользователей +func readBlacklist() { + blacklistLoaded := false + executablePath, err := os.Executable() + + if err != nil { + log.Errorf("Unable to get current executable path: %s", err) + } + + whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath)) + + locations := []string{ + "~/.buny-jabber-bot-blacklist.json", + "~/buny-jabber-bot-blacklist.json", + "/etc/buny-jabber-bot-blacklist.json", + whitelistJSONPath, + } + + for _, location := range locations { + fileInfo, err := os.Stat(location) + + // Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём + if err != nil { + continue + } + + // Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата + if fileInfo.Size() > 16777216 { + log.Warnf("Blacklist file %s is too long for blacklist, skipping", location) + + continue + } + + buf, err := os.ReadFile(location) + + // Не удалось прочитать, попробуем следующего кандидата + if err != nil { + log.Warnf("Skip reading blacklist file %s: %s", location, err) + + continue + } + + // Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку. + // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку + // сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется. + var sampleBlacklist myBlackList + var tmp map[string]interface{} + err = hjson.Unmarshal(buf, &tmp) + + // Не удалось распарсить - попробуем следующего кандидата + if err != nil { + log.Warnf("Skip parsing blacklist file %s: %s", location, err) + + continue + } + + tmpjson, err := json.Marshal(tmp) + + // Не удалось преобразовать map-ку в json + if err != nil { + log.Warnf("Skip parsing blacklist file %s: %s", location, err) + + continue + } + + if err := json.Unmarshal(tmpjson, &sampleBlacklist); err != nil { + log.Warnf("Skip parsing whitelist file %s: %s", location, err) + + continue + } + + blackList = sampleBlacklist + blacklistLoaded = true + log.Infof("Using %s as blacklist file", location) + + break + } + + if !blacklistLoaded { + log.Error("Blacklist was not loaded! Refusing to start.") + os.Exit(1) + } +} + +func establishConnection() { + var err error + + if connecting && !isConnected { + return + } + + // проставляем глобальные переменные + connecting = true + isConnected = false + roomsConnected = make([]string, 0) + + for { + talk, err = options.NewClient() + + if err != nil { + log.Error(err) + + os.Exit(1) + } else { + break + } + } + + // По идее keepalive должен же проходить только, если мы уже на сервере, так? + for { + if _, err := talk.SendKeepAlive(); err != nil { + log.Errorf("Try to send initial KeepAlive, got error: %s", err) + time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second) + + // Понятно, что мы уже не в процессе установления соединения, а произошла ошибка + connecting = false + os.Exit(1) + } else { + log.Info("Connected") + + break + } + } + + // Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили + for _, room := range config.Jabber.Channels { + go joinMuc(room) + } + + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + Status: "Ready to chat", + }, + ); err != nil { + log.Infof("Unable to send presence to jabber server: %s", err) + + os.Exit(1) + } + + lastActivity = time.Now().Unix() + connecting = false + isConnected = true + + log.Debugf("Sending disco#info to %s", config.Jabber.Server) + + _, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server) + + if err != nil { + log.Infof("Unable to send disco#info to jabber server: %s", err) + + os.Exit(1) + } +} + +func reEstablishConnection() { + // Серверные capabilities нам *уже* неизвестны + serverCapsQueried = false + serverCapsList = NewCollection() + mucCapsList = NewCollection() + lastMucActivity = NewCollection() + + // Пока соединение не установлено, мы про пинги ничего не знаем + serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен + serverPingTimestampTx = 0 + time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second) + connecting = false + establishConnection() +} + +// Джойнится к конференциям/каналам/комнатам в джаббере +func joinMuc(room string) { + log.Debugf("Sending disco#info from %s to %s", talk.JID(), room) + + if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil { + log.Infof("Unable to send disco#info to MUC %s: %s", room, err) + + os.Exit(1) + } + + // Ждём, пока muc нам вернёт список фичей + for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + var myRoom interface{} + var supported bool + var exist bool + + time.Sleep(50 * time.Millisecond) + + if myRoom, exist = mucCapsList.Get(room); !exist { + // Пока не задискаверилась + continue + } + + if supported, exist = myRoom.(map[string]bool)["muc_unsecured"]; exist { + if supported { + break + } else { + log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)") + + return + } + } + } + + if _, err := talk.JoinMUCNoHistory(room, config.Jabber.Nick); err != nil { + log.Errorf("Unable to join to MUC: %s", room) + + os.Exit(1) + } else { + log.Infof("Joining to MUC: %s", room) + } + + // Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли + for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ { + time.Sleep(50 * time.Millisecond) + + if slices.Contains(roomsConnected, room) { + break + } + } + + // Вот теперь точно можно слать статус + log.Infof("Joined to MUC: %s", room) + + if _, err := talk.SendPresence( + xmpp.Presence{ //nolint:exhaustruct + To: room, + Status: "Ready to chat", + }, + ); err != nil { + log.Infof("Unable to send presence to MUC %s: %s", room, err) + os.Exit(1) + } +} + +// Проверяет живость соединения с сервером +func probeServerLiveness() { + for { + if shutdown { + return + } + + sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond + sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + if !isConnected { + continue + } + + // Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay, + // в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay + // if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + // continue + // } + + if serverCapsQueried { // Сервер ответил на disco#info + var value interface{} + var exist bool + + value, exist = serverCapsList.Get("urn:xmpp:ping") + + switch { + // Сервер анонсировал, что умеет в c2s пинги + case exist && value.(bool): + // Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим + // максимальную корректировку разброса. + txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout + txTimeout += config.Jabber.PingSplayDelay + rxTimeout := txTimeout + + rxTimeAgo := time.Now().Unix() - serverPingTimestampRx + + if serverPingTimestampTx > 0 { // Первая пуля от нас ушла... + switch { + // Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно? + case rxTimeAgo > (rxTimeout * 2): + log.Debugf( + "Stall connection detected. No c2s pong for %d seconds", + rxTimeAgo, + ) + + os.Exit(1) + + // По-умолчанию, мы отправляем c2s пинг + default: + log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + + if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + os.Exit(1) + } else { + serverPingTimestampTx = time.Now().Unix() + } + } + } else { // Первая пуля пока не вылетела, отправляем + log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server) + + if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil { + os.Exit(1) + } else { + serverPingTimestampTx = time.Now().Unix() + } + } + + // Сервер не анонсировал, что умеет в c2s пинги + default: + log.Debug("Sending keepalive whitespace ping") + + if _, err := talk.SendKeepAlive(); err != nil { + os.Exit(1) + + continue + } + } + } else { // Сервер не ответил на disco#info + log.Debug("Sending keepalive whitespace ping") + + if _, err := talk.SendKeepAlive(); err != nil { + os.Exit(1) + + continue + } + } + } +} + +// Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е +func probeMUCLiveness() { + for { + for _, room := range roomsConnected { + var exist bool + var lastActivityTs interface{} + + // Если записи про комнату нету, то пинговать её бессмысленно. + if lastActivityTs, exist = lastMucActivity.Get(room); !exist { + continue + } + + // Если время последней активности в чятике не превысило + // config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем + if (time.Now().Unix() - lastActivityTs.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) { + continue + } + + /* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно. + go func(room string) { + // Небольшая рандомная задержка перед пингом комнаты + sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + if err := talk.PingS2S(talk.JID(), room+"/"+config.Jabber.Nick); err != nil { + reEstablishConnection() + } + }(room) + */ + + var roomMap interface{} + + roomMap, exist = mucCapsList.Get(room) + + // Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая + // Server Optimization + if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] { + go func(room string) { + // Небольшая рандомная задержка перед пингом комнаты + sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec + time.Sleep(sleepTime) + + log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room) + + if err := talk.PingS2S(talk.JID(), room); err != nil { + os.Exit(1) + } + }(room) + } + } + + time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/main.go b/main.go new file mode 100644 index 0000000..00bc56b --- /dev/null +++ b/main.go @@ -0,0 +1,157 @@ +package main + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "os" + "os/signal" + "time" + + "github.com/eleksir/go-xmpp" + log "github.com/sirupsen/logrus" +) + +func init() { + log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct + DisableQuote: true, + DisableLevelTruncation: false, + DisableColors: true, + FullTimestamp: true, + TimestampFormat: "2006-01-02 15:04:05", + }) + + readConfig() + + // no panic + switch config.Loglevel { + case "fatal": + log.SetLevel(log.FatalLevel) + case "error": + log.SetLevel(log.ErrorLevel) + case "warn": + log.SetLevel(log.WarnLevel) + case "info": + log.SetLevel(log.InfoLevel) + case "debug": + log.SetLevel(log.DebugLevel) + case "trace": + log.SetLevel(log.TraceLevel) + default: + log.SetLevel(log.InfoLevel) + } + + // Пока про capabilities мы ничего не знаем + serverCapsQueried = false + + // Мы пока что не пинговали сервер. + serverPingTimestampRx = 0 + serverPingTimestampTx = 0 +} + +func main() { + // Откроем лог и скормим его логгеру + if config.Log != "" { + logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + + if err != nil { + log.Fatalf("Unable to open log file %s: %s", config.Log, err) + } + + log.SetOutput(logfile) + } + + myLogLevel := log.GetLevel() + log.Warnf("Loglevel set to %v", myLogLevel) + + verboseClient := false + + if myLogLevel == log.TraceLevel { + verboseClient = true + } + + // Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог. + readWhitelist() + readBlacklist() + + // github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace + xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel) + + options = &xmpp.Options{ //nolint:exhaustruct + Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port), + User: config.Jabber.User, + Password: config.Jabber.Password, + Resource: config.Jabber.Resource, + NoTLS: !config.Jabber.Ssl, + StartTLS: config.Jabber.StartTLS, + TLSConfig: &tls.Config{ //nolint:exhaustruct + ServerName: config.Jabber.Server, + InsecureSkipVerify: !config.Jabber.SslVerify, //nolint:gosec + }, + InsecureAllowUnencryptedAuth: config.Jabber.InsecureAllowUnencryptedAuth, + Debug: verboseClient, + Session: false, + Status: "xa", + StatusMessage: "echo bot is starting up", + DialTimeout: 10 * time.Second, + } + + go sigHandler() + signal.Notify(sigChan, os.Interrupt) + // Установим коннект + establishConnection() + + serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен + + // Тыкаем сервер палочкой, проверяем, что коннект жив и переустанавливаем его, если он не жив + go probeServerLiveness() + + // Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали + go probeMUCLiveness() + + // Гребём сообщения... + for { + // Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например) + // грести не надо + if shutdown { + break + } + + if !isConnected { + continue + } + + chat, err := talk.Recv() + + if err != nil { + log.Errorf("Unable to get events from server: %s", err) + + switch { + // Стрим не читается, он закрылся с той стороны во время чтения + case errors.Is(err, io.EOF): + os.Exit(1) + + // Пытаемся читать закрытый сокет + case errors.Is(err, net.ErrClosed): + os.Exit(1) + + // Не смогли записать в сокет + case errors.Is(err, net.ErrWriteToConnected): + os.Exit(1) + + // Не сетевая проблема + default: + // Это уже что-то странное. + // Вероятно, ошибка парсинга xml. Собственно, баг сервера, тут мы ничего поделать не можем + + os.Exit(1) + } + } + + parseEvent(chat) + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/types.go b/types.go new file mode 100644 index 0000000..a2092d9 --- /dev/null +++ b/types.go @@ -0,0 +1,93 @@ +package main + +import ( + "encoding/xml" +) + +// Конфиг +type myConfig struct { + Jabber struct { + Server string `json:"server,omitempty"` + Port int `json:"port,omitempty"` + Ssl bool `json:"ssl,omitempty"` + StartTLS bool `json:"starttls,omitempty"` + SslVerify bool `json:"ssl_verify,omitempty"` + InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"` + ConnectionTimeout int64 `json:"connection_timeout,omitempty"` + ReconnectDelay int64 `json:"reconnect_delay,omitempty"` + ServerPingDelay int64 `json:"server_ping_delay,omitempty"` + MucPingDelay int64 `json:"muc_ping_delay,omitempty"` + MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"` + PingSplayDelay int64 `json:"ping_splay_delay,omitempty"` + Nick string `json:"nick,omitempty"` + Resource string `json:"resource,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + Channels []string `json:"channels"` + OutcastDomains []string `json:"outcast_domains,omitempty"` + } + + Loglevel string `json:"loglevel,omitempty"` + Log string `json:"log,omitempty"` + NoEcho bool `json:"noecho,omitempty"` +} + +// Белый список +type myWhiteList struct { + Whitelist []struct { + RoomName string `json:"room_name,omitempty"` + Jid []string `json:"jid,omitempty"` + WipeBans bool `json:"wipe_bans,omitempty"` + } `json:"whitelist,omitampty"` +} + +type myBlackList struct { + Blacklist []struct { + RoomName string `json:"room_name,omitempty"` + JidRe []string `json:"jid_re,omitempty"` + } `json:"blacklist,omitempty"` +} + +type jabberSimpleIqGetQuery struct { + XMLName xml.Name `xml:"query"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items" +} + +type jabberPubsubIQGetQuery struct { + XMLName xml.Name `xml:"pubsub"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Items struct { + Text string `xml:",chardata"` + Node string `xml:"node,attr"` + MaxItems string `xml:"max_items,attr"` + } `xml:"items"` +} + +type jabberTimeIqGetQuery struct { + //