From 77e7d58ba66fe7265154d73b2673297db4137c0b Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Wed, 1 Apr 2020 00:42:31 +0200 Subject: [PATCH 01/25] Added sequence_pastebin.png --- sequence_pastebin.png | Bin 0 -> 34641 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sequence_pastebin.png diff --git a/sequence_pastebin.png b/sequence_pastebin.png new file mode 100644 index 0000000000000000000000000000000000000000..80956cd419c68cc04c9afd0c07cf81d0b2c7a666 GIT binary patch literal 34641 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 literal 0 HcmV?d00001 From 69d9bdddd84166c67aa7705e334c27f2edbf6646 Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Wed, 1 Apr 2020 01:03:09 +0200 Subject: [PATCH 02/25] Added sequence_pastebin.png --- sequence_pastebin.png | Bin 34641 -> 34033 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sequence_pastebin.png b/sequence_pastebin.png index 80956cd419c68cc04c9afd0c07cf81d0b2c7a666..8abbf64e5b8210328bc899cd7e0cd359db932efe 100644 GIT binary patch literal 34033 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 From a52a57eba297b7b78c587f7c786b45a21882003e Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Wed, 1 Apr 2020 01:05:26 +0200 Subject: [PATCH 03/25] squash me --- tmp/sequence_pastebin.png | Bin 0 -> 34641 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tmp/sequence_pastebin.png diff --git a/tmp/sequence_pastebin.png b/tmp/sequence_pastebin.png new file mode 100644 index 0000000000000000000000000000000000000000..80956cd419c68cc04c9afd0c07cf81d0b2c7a666 GIT binary patch literal 34641 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 literal 0 HcmV?d00001 From 4cc3deac1790f54f6692f351ecd0ba0f12774865 Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Wed, 1 Apr 2020 15:48:51 +0200 Subject: [PATCH 04/25] [WIP] privatebin plugin for bitbot --- progress.md | 92 ++++++++++++++++++ ...uence_pastebin.png => sd_create_paste.png} | Bin 2 files changed, 92 insertions(+) create mode 100644 progress.md rename tmp/{sequence_pastebin.png => sd_create_paste.png} (100%) diff --git a/progress.md b/progress.md new file mode 100644 index 0000000..61bfb4f --- /dev/null +++ b/progress.md @@ -0,0 +1,92 @@ +## usage and scenario +create a new paste +`/msg bitbot !paste ` + +delete a paste +`/msg bitbot !paste --delete ` + +see [sample](https://github.com/PrivateBin/PrivateBin/blob/master/cfg/conf.sample.php) for sample server-side configuration file for privatebin (and for more ideas, like url-shortner) + +the following sequence diagram represents the creation of a new paste: + +![sd_create_paste](tmp/sd_create_paste.png) + +## encryption format +from [here](https://github.com/PrivateBin/PrivateBin/wiki/Encryption-format#encryption); the following was request was sent with key `7MqznSyqHVx9VwNWfHRi6PLsr322eBY4Fnkx45tFV1gR` + +```json +{ + "adata": [ // auth data + [ + "Yu5ECsseuJ07M2QBfdf3bA==", // base64(cipher_iv); getRandomBytes(16) default + "kvDZJC6IahU=", // base64(kdf_salt); getRandomBytes(8) default + 100000, // pbkdf_iterations; default + 256, // pbkdf_keysize; default + 128, // cipher_tag_size (wtf ?); default + "aes", // cipher_algo; default + "gcm", // cipher_mode; default + "zlib" // compression_type; default + ], + "plaintext", // format of the paste - "plaintext" or "syntaxhighlighting" or "markdown" + + 0, // open-discussion flag + 0 // burn-after-reading flag (0 or 1) + ], + "meta": { + "expire": "1week" + }, + "v": 2, // schema version + "ct": "aVKpZWtKmTJKis5S6nYEL1rdxyPbHYFclHV3E6Kq99Tb" // cipher text +} +``` +prompt for password on wrong encryption keys + +### Process data + +If paste_password is an empty string: + +``` +paste_passphrase = random(32) # 32 bytes +``` + +if a paste_password has been specified: + +``` +paste_passphrase = random(32) + paste_password +``` + +Processing of the paste_data, if compression is enabled (the default): + +``` +paste_blob = zlib.compress(paste_data) +``` + +### Key derivation (PBKDF2) + +Since passwords and keys are usually too short to be usable for encryption, it is common practice to use salted key derivation to turn such low entropy input into the actual key to use during en/decryption. + +```python +kdf_salt = random(8) # 8 bytes +kdf_iterations = 100000 # was 10000 before PrivateBin version 1.3 +kdf_keysize = 256 # bits of resulting kdf_key + +kdf_key = PBKDF2_HMAC_SHA256(kdf_keysize, kdf_salt, paste_password) +``` + +The encrypted text is then: + +```python +cipher_algo = "aes" +cipher_mode = "gcm" # was "ccm" before PrivateBin version 1.0 +cipher_iv = random(16) # 128 bit +cipher_tag_size = 128 + +cipher_text = cipher(AES(kdf_key), GCM(iv, paste_meta), paste_blob) +``` + +## todo + +* switch to a db instead of "file-less" configuration +* enable ability to delete +* enable paste cloning + diff --git a/tmp/sequence_pastebin.png b/tmp/sd_create_paste.png similarity index 100% rename from tmp/sequence_pastebin.png rename to tmp/sd_create_paste.png From 95ef33344416fa853a7c4a06df91fc5836e55f8a Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Sun, 5 Apr 2020 14:10:09 +0200 Subject: [PATCH 05/25] types and routines for encrypting locally --- bitbot/paste.go | 137 ++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 1 + go.mod | 4 +- go.sum | 25 ++++++++ tmp/sd_create_paste.png | Bin 34641 -> 0 bytes 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 bitbot/paste.go delete mode 100644 tmp/sd_create_paste.png diff --git a/bitbot/paste.go b/bitbot/paste.go new file mode 100644 index 0000000..bc9746b --- /dev/null +++ b/bitbot/paste.go @@ -0,0 +1,137 @@ +package bitbot + +import ( + "io" + "fmt" + "strings" + "crypto/cipher" + "crypto/aes" + "crypto/rand" + "encoding/base64" + // "encoding/json" + // "net/http" + + "github.com/whyrusleeping/hellabot" + "github.com/btcsuite/btcutil/base58" +) + +const ( + nonceSize = 16 // privatebin uses a nonce of 16 bytes by default + aesKeySize = 32 // using aes-256-gcm; for reference only + gcmTagSize = 16 // for reference +) + +type AuthData struct { //todo: should be type "json" ? + // todo: the following fields (till EOF) should be a (json ?) array + nonce string // base64(cipher_iv); getRandomBytes(16) default + kdfsalt string // base64(kdf_salt); getRandomBytes(8) default + pbkdf_iterations int // pbkdf_iterations; default + pbkdf_keysize int // pbkdf_keysize; default + cipher_tag_size int // cipher_tag_size (wtf ?); default + cipher_algo string // cipher_algo; default + cipher_mode string // cipher_mode; default + compression_type string // compression_type; default + // EOF + + format string // format of the paste + open_discussion bool // open-discussion flag (todo: not sure if bool works) + burn_after_reading bool // burn-after-reading flag (todo: not sure if bool works) + +} + +type PasteMeta struct { // todo: should be type "json" ? + expire string `json:"expire"` +} + +type PasteRequest struct { // todo: should be type "json" ? + adata AuthData + meta PasteMeta + version int `json:"v"` + ciphertext string `json:"ct"` // todo: type should be "base64" ? +} + +type PasteResponse struct { + status bool `json:"status"` // todo: not sure if bool works + id string `json:"id"` + url string `json:"url"` + deletetoken string `json:"deletetoken"` +} + +var PasteTrigger = NamedTrigger { + ID: "paste", + Help: "returns a pastebin link for a PRIVMSG to bitbot" + + "\nUsage: !paste ", + Condition: func(irc *hbot.Bot, m *hbot.Message) bool { + return m.Command == "PRIVMSG" && strings.HasPrefix(m.Content, "!paste ") + }, + Action: func(irc *hbot.Bot, m *hbot.Message) bool { + + // uncomment below + // plaintext := []byte(m.Content) // todo: only fetch the paste content + // key, nonce, ciphertext, kdfsalt := encrypt(plaintext) + /* + todo: + 1. send request to privatebin + 2. parse the response + 3. check status, fail if not okay + 4. return a valid URL (https://$website?$paste_id#generated_key_in_base58) + + */ + return false + }, +} + +func recvPaste(req *PasteRequest) (PasteResponse) { + var resp PasteResponse + + return resp +} + +func encrypt(plaintext []byte) (string, string, string, string){ + // encrypts the message with a random key, then return it back + // todo: should generate the key instead of it being a random + // todo: should return a struct (first array of AuthData) + + /* public function create($pasteid, $paste) + { + $pasteid = substr(hash('md5', $paste['data']), 0, 16); + + $paste['data'] // text + $paste['meta']['postdate'] // int UNIX timestamp + $paste['meta']['expire_date'] // int UNIX timestamp + $paste['meta']['opendiscussion'] // true (if false it is unset) + $paste['meta']['burnafterreading'] // true (if false it is unset; if true, then opendiscussion is unset) + $paste['meta']['formatter'] // string + $paste['meta']['attachment'] // text + $paste['meta']['attachmentname'] // text + } + */ + + // since we'll be using a different random key for each paste, + // a fixed nonce should be OK (but we won't do it anyway) + keyWithNonce := make([]byte, aesKeySize + nonceSize) + if _, err := io.ReadFull(rand.Reader, keyWithNonce); err != nil { + panic(err.Error()) + } + + key, nonce := keyWithNonce[:aesKeySize], keyWithNonce[aesKeySize:] + block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size + if err != nil { + panic(err.Error()) + } + + aesgcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) + if err != nil { + panic(err.Error()) + } + + // encrypt, encode and return + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + encodedKey := base58.Encode(key) + encodedNonce := base64.StdEncoding.EncodeToString(nonce) + encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) + encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it + + fmt.Printf("pt: %s\n key: %s\n, ct: %s\n", plaintext, encodedKey, encodedCipherText) + return encodedKey, encodedNonce, encodedCipherText, encodedKDFsalt +} \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 1401891..2717b3b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,6 +72,7 @@ var pluginMap = map[string]bitbot.NamedTrigger{ "markovTrainer": bitbot.MarkovTrainerTrigger, "epeen": bitbot.EpeenTrigger, "ipinfo": bitbot.IPinfoTrigger, + "paste": bitbot.PasteTrigger, } // rootCmd represents the base command when called without any subcommands diff --git a/go.mod b/go.mod index 39cd26f..d8a3a2a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/bbriggs/quotes v0.0.0-20190225011706-ef48dc0c0ec7 - github.com/go-stack/stack v1.8.0 // indirect + github.com/btcsuite/btcutil v1.0.1 github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec // indirect github.com/jinzhu/gorm v1.9.12 github.com/justinian/dice v0.0.0-20170728002755-6a18b51d929c @@ -12,8 +12,6 @@ require ( github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/mudler/sendfd v0.0.0-20150620134918-f0fc74c13877 // indirect github.com/prometheus/client_golang v1.2.1 github.com/spf13/cobra v0.0.5 diff --git a/go.sum b/go.sum index 2a9a0fe..334bf54 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -13,6 +14,16 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.1 h1:GKOz8BnRjYrb/JTKgaOk+zh26NWNdSNvdvv0xoAZMSA= +github.com/btcsuite/btcutil v1.0.1/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= @@ -25,6 +36,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -62,16 +74,19 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec h1:CGkYB1Q7DSsH/ku+to+foV4agt2F2miquaLUgF6L178= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -79,6 +94,7 @@ github.com/justinian/dice v0.0.0-20170728002755-6a18b51d929c h1:nuF3PF5z48DaqyVL github.com/justinian/dice v0.0.0-20170728002755-6a18b51d929c/go.mod h1:AyqU0eD51oSe8WchcqHezWZKoQpXZkT4MWqvaW7AnW8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -116,6 +132,9 @@ github.com/mudler/sendfd v0.0.0-20150620134918-f0fc74c13877 h1:Xp2ntqpIKo5TC32/F github.com/mudler/sendfd v0.0.0-20150620134918-f0fc74c13877/go.mod h1:j4hnSAX0+AZQpILVkkVRwfQTeAFMH0crQMGe5apT9Yo= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= @@ -201,15 +220,18 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -227,6 +249,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -259,11 +282,13 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/sorcix/irc.v1 v1.1.4 h1:ejxu1vDyqn1d+I5WXzh93pMDq5NqdwBl3V/eN5c2we8= gopkg.in/sorcix/irc.v1 v1.1.4/go.mod h1:CHwY3DGuZpB6/OvF+fj4Jlvnj6QeS56EUTDoQkuAePM= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/tmp/sd_create_paste.png b/tmp/sd_create_paste.png deleted file mode 100644 index 80956cd419c68cc04c9afd0c07cf81d0b2c7a666..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34641 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 From 9d48ec92dad7592386e7a1d198cfe74750d3d97f Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Mon, 6 Apr 2020 17:37:01 +0200 Subject: [PATCH 06/25] added routine for sending/receiving paste changed some types in earlier commit to suit json; still unclear about some no tests were made whatsoever --- bitbot/paste.go | 160 +++++++++++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 56 deletions(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index bc9746b..796e66f 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -1,66 +1,70 @@ package bitbot import ( - "io" - "fmt" - "strings" - "crypto/cipher" "crypto/aes" + "crypto/cipher" "crypto/rand" - "encoding/base64" - // "encoding/json" - // "net/http" + // "encoding/base64" + "fmt" + "io" + "bytes" + "strings" + "encoding/json" + "net/http" - "github.com/whyrusleeping/hellabot" "github.com/btcsuite/btcutil/base58" + "github.com/whyrusleeping/hellabot" ) const ( - nonceSize = 16 // privatebin uses a nonce of 16 bytes by default - aesKeySize = 32 // using aes-256-gcm; for reference only - gcmTagSize = 16 // for reference + nonceSize = 16 // privatebin uses a nonce of 16 bytes by default + aesKeySize = 32 // using aes-256-gcm; for reference only + gcmTagSize = 16 // for reference ) -type AuthData struct { //todo: should be type "json" ? +type Array1 struct { // todo: the following fields (till EOF) should be a (json ?) array - nonce string // base64(cipher_iv); getRandomBytes(16) default - kdfsalt string // base64(kdf_salt); getRandomBytes(8) default - pbkdf_iterations int // pbkdf_iterations; default - pbkdf_keysize int // pbkdf_keysize; default - cipher_tag_size int // cipher_tag_size (wtf ?); default - cipher_algo string // cipher_algo; default - cipher_mode string // cipher_mode; default - compression_type string // compression_type; default + Nonce []byte // base64(cipher_iv); getRandomBytes(16) default + Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default + Pbkdf_iterations int // pbkdf_iterations; default + Pbkdf_keysize int // pbkdf_keysize; default + Cipher_tag_size int // cipher_tag_size (wtf ?); default + Cipher_algo string // cipher_algo; default + Cipher_mode string // cipher_mode; default + Compression_type string // compression_type; default // EOF +} +type AuthData struct { //todo: should be type "json" ? - format string // format of the paste - open_discussion bool // open-discussion flag (todo: not sure if bool works) - burn_after_reading bool // burn-after-reading flag (todo: not sure if bool works) + EncryptionDetails Array1 + Format string // format of the paste + Open_discussion bool // open-discussion flag (todo: not sure if bool works) + Burn_after_reading bool // burn-after-reading flag (todo: not sure if bool works) } -type PasteMeta struct { // todo: should be type "json" ? - expire string `json:"expire"` +type PasteMeta struct { // todo: should be type "json" ? + Expire string `json:"expire"` } -type PasteRequest struct { // todo: should be type "json" ? - adata AuthData - meta PasteMeta - version int `json:"v"` - ciphertext string `json:"ct"` // todo: type should be "base64" ? +type PasteRequest struct { // todo: should be type "json" ? + Adata AuthData `json:"adata"` + Meta PasteMeta `json:"meta"` // todo: meta is another json + Version int `json:"v"` + Ciphertext []byte `json:"ct"` // todo: type should be "base64" ? } type PasteResponse struct { - status bool `json:"status"` // todo: not sure if bool works - id string `json:"id"` - url string `json:"url"` - deletetoken string `json:"deletetoken"` + Status int `json:"status"` // todo: not sure if bool works + Id string `json:"id"` + Url string `json:"url"` + Deletetoken string `json:"deletetoken"` } -var PasteTrigger = NamedTrigger { - ID: "paste", - Help: "returns a pastebin link for a PRIVMSG to bitbot" + - "\nUsage: !paste ", +var PasteTrigger = NamedTrigger{ + ID: "paste", + Help: "returns a pastebin link for a PRIVMSG to bitbot" + + "\nUsage: !paste ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { return m.Command == "PRIVMSG" && strings.HasPrefix(m.Content, "!paste ") }, @@ -70,27 +74,68 @@ var PasteTrigger = NamedTrigger { // plaintext := []byte(m.Content) // todo: only fetch the paste content // key, nonce, ciphertext, kdfsalt := encrypt(plaintext) /* - todo: - 1. send request to privatebin - 2. parse the response - 3. check status, fail if not okay - 4. return a valid URL (https://$website?$paste_id#generated_key_in_base58) + todo: + 1. send request to privatebin + 2. parse the response + 3. check status, fail if not okay + 4. return a valid URL (https://$website?$paste_id#generated_key_in_base58) */ return false }, } -func recvPaste(req *PasteRequest) (PasteResponse) { - var resp PasteResponse +func recvPaste(pasteReq *PasteRequest) PasteResponse { + var ( + resp PasteResponse + jsonForm []byte + r, req *http.Request + err error + ) + + if jsonForm, err := json.Marshal(pasteReq); err != nil { // Marshal, not NewEncoder + fmt.Println(err) + } + httpClient := &http.Client{} + if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // todo: don't hardcode url + fmt.Println(err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("X-Requested-With", "JSONHttpRequest") + + // resp, err := http.Post(, "application/json", &jsonForm) + if r, err := httpClient.Do(req); err != nil { + fmt.Println(err) + } + // fmt.Println(r.Body) + defer r.Body.Close() + // body, err := ioutil.ReadAll(r.Body) + // if err != nil { + // print(err) + // } + // err = json.Unmarshal(body, &m) + if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { + fmt.Println(err) + } return resp } -func encrypt(plaintext []byte) (string, string, string, string){ +// func test() { +// var resp struct { +// Completed bool `json:"completed"` +// Id int `json:"id"` +// Title string `json:"title"` +// UserId int `json:"userId"` +// } + +// return resp +// } + +func encrypt(plaintext []byte) (string, []byte, []byte, []byte) { // encrypts the message with a random key, then return it back // todo: should generate the key instead of it being a random - // todo: should return a struct (first array of AuthData) + // todo: should return a struct (first array of AuthData) /* public function create($pasteid, $paste) { @@ -109,13 +154,13 @@ func encrypt(plaintext []byte) (string, string, string, string){ // since we'll be using a different random key for each paste, // a fixed nonce should be OK (but we won't do it anyway) - keyWithNonce := make([]byte, aesKeySize + nonceSize) + keyWithNonce := make([]byte, aesKeySize+nonceSize) if _, err := io.ReadFull(rand.Reader, keyWithNonce); err != nil { panic(err.Error()) } key, nonce := keyWithNonce[:aesKeySize], keyWithNonce[aesKeySize:] - block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size + block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size if err != nil { panic(err.Error()) } @@ -128,10 +173,13 @@ func encrypt(plaintext []byte) (string, string, string, string){ // encrypt, encode and return ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) encodedKey := base58.Encode(key) - encodedNonce := base64.StdEncoding.EncodeToString(nonce) - encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) - encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it - - fmt.Printf("pt: %s\n key: %s\n, ct: %s\n", plaintext, encodedKey, encodedCipherText) - return encodedKey, encodedNonce, encodedCipherText, encodedKDFsalt -} \ No newline at end of file +// encodedNonce := base64.StdEncoding.EncodeToString(nonce) +// encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) + dummyKDFsalt := make([]byte, 8) + if _, err := io.ReadFull(rand.Reader, dummyKDFsalt); err != nil { + fmt.Println("warning, dummy kdfsalt is not initialized properly") + // encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it +} + fmt.Printf("pt: %s\n key: %s\n, ct: %s\n", plaintext, encodedKey, ciphertext) + return encodedKey, nonce, ciphertext, dummyKDFsalt +} From b6d60504e98f5e4fe561767d9a452cf97d516513 Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Tue, 7 Apr 2020 20:11:16 +0200 Subject: [PATCH 07/25] adapting Go structrures to suit the JSON the JSON structure sent does not map well to Go types, but it should be ok now to do JSON marshalling --- bitbot/paste.go | 62 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index 796e66f..9cabb4e 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -22,40 +22,42 @@ const ( gcmTagSize = 16 // for reference ) -type Array1 struct { - // todo: the following fields (till EOF) should be a (json ?) array - Nonce []byte // base64(cipher_iv); getRandomBytes(16) default - Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default - Pbkdf_iterations int // pbkdf_iterations; default - Pbkdf_keysize int // pbkdf_keysize; default - Cipher_tag_size int // cipher_tag_size (wtf ?); default - Cipher_algo string // cipher_algo; default - Cipher_mode string // cipher_mode; default - Compression_type string // compression_type; default - // EOF -} -type AuthData struct { //todo: should be type "json" ? - EncryptionDetails Array1 - Format string // format of the paste - Open_discussion bool // open-discussion flag (todo: not sure if bool works) - Burn_after_reading bool // burn-after-reading flag (todo: not sure if bool works) +// type Array1 []interface { // TODO: more descriptive name +// // TODO: the following fields (till EOF) should be a (json ?) array +// Nonce []byte // base64(cipher_iv); getRandomBytes(16) default +// Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default +// Pbkdf_iterations int // pbkdf_iterations; default +// Pbkdf_keysize int // pbkdf_keysize; default +// Cipher_tag_size int // cipher_tag_size (wtf ?); default +// Cipher_algo string // cipher_algo; default +// Cipher_mode string // cipher_mode; default +// Compression_type string // compression_type; default +// // EOF +// } -} +// type AuthData struct { //TODO: should be type "json" ? +// +// EncryptionDetails Array1 // TODO: more descriptive name +// Format string // format of the paste +// Open_discussion int // open-discussion flag (TODO: not sure if bool works) +// Burn_after_reading int // burn-after-reading flag (TODO: not sure if bool works) +// +// } -type PasteMeta struct { // todo: should be type "json" ? +type PasteMeta struct { // TODO: should be type "json" ? Expire string `json:"expire"` } -type PasteRequest struct { // todo: should be type "json" ? - Adata AuthData `json:"adata"` - Meta PasteMeta `json:"meta"` // todo: meta is another json - Version int `json:"v"` - Ciphertext []byte `json:"ct"` // todo: type should be "base64" ? +type PasteRequest struct { // TODO: should be type "json" ? + AuthData [4]interface{} `json:"adata"` + Meta PasteMeta `json:"meta"` // TODO: meta is another json + Version int `json:"v"` + Ciphertext []byte `json:"ct"` // TODO: type should be "base64" ? } type PasteResponse struct { - Status int `json:"status"` // todo: not sure if bool works + Status int `json:"status"` // TODO: not sure if bool works Id string `json:"id"` Url string `json:"url"` Deletetoken string `json:"deletetoken"` @@ -71,10 +73,10 @@ var PasteTrigger = NamedTrigger{ Action: func(irc *hbot.Bot, m *hbot.Message) bool { // uncomment below - // plaintext := []byte(m.Content) // todo: only fetch the paste content + // plaintext := []byte(m.Content) // TODO: only fetch the paste content // key, nonce, ciphertext, kdfsalt := encrypt(plaintext) /* - todo: + TODO: 1. send request to privatebin 2. parse the response 3. check status, fail if not okay @@ -97,7 +99,7 @@ func recvPaste(pasteReq *PasteRequest) PasteResponse { fmt.Println(err) } httpClient := &http.Client{} - if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // todo: don't hardcode url + if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url fmt.Println(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -134,8 +136,8 @@ func recvPaste(pasteReq *PasteRequest) PasteResponse { func encrypt(plaintext []byte) (string, []byte, []byte, []byte) { // encrypts the message with a random key, then return it back - // todo: should generate the key instead of it being a random - // todo: should return a struct (first array of AuthData) + // TODO: should generate the key instead of it being a random + // TODO: should return a struct (first array of AuthData) /* public function create($pasteid, $paste) { From 1b8f859da575f69be6beab3336b0a33b4cebb14e Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Wed, 8 Apr 2020 17:54:51 +0200 Subject: [PATCH 08/25] manually created the paste request final commit before testing --- bitbot/paste.go | 145 ++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index 9cabb4e..6a515d9 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -5,12 +5,12 @@ import ( "crypto/cipher" "crypto/rand" // "encoding/base64" + "bytes" + "encoding/json" "fmt" "io" - "bytes" - "strings" - "encoding/json" "net/http" + "strings" "github.com/btcsuite/btcutil/base58" "github.com/whyrusleeping/hellabot" @@ -22,28 +22,60 @@ const ( gcmTagSize = 16 // for reference ) +// not used directly in the paste request +type array1 struct { // TODO: more descriptive name + // TODO: the following fields (till EOF) should be a (json ?) array + Nonce []byte // base64(cipher_iv); getRandomBytes(16) default + Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default + KdfIterations int // pbkdf_iterations; default + KdfKeySize int // pbkdf_keysize; default + CipherTagSize int // cipher_tag_size (wtf ?); default + CipherAlgo string // cipher_algo; default + CipherMode string // cipher_mode; default + CompressionType string // compression_type; default + // EOF +} + +type authData struct { //TODO: should be type "json" ? + // + EncryptionDetails *array1 // TODO: more descriptive name + Format string // format of the paste + OpenDiscussion int // open-discussion flag (TODO: not sure if bool works) + BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) + // +} + +func (req PasteRequest) NewRequest(iv []byte, cipherText []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int, expiryDate string) *PasteRequest { -// type Array1 []interface { // TODO: more descriptive name -// // TODO: the following fields (till EOF) should be a (json ?) array -// Nonce []byte // base64(cipher_iv); getRandomBytes(16) default -// Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default -// Pbkdf_iterations int // pbkdf_iterations; default -// Pbkdf_keysize int // pbkdf_keysize; default -// Cipher_tag_size int // cipher_tag_size (wtf ?); default -// Cipher_algo string // cipher_algo; default -// Cipher_mode string // cipher_mode; default -// Compression_type string // compression_type; default -// // EOF -// } - -// type AuthData struct { //TODO: should be type "json" ? -// -// EncryptionDetails Array1 // TODO: more descriptive name -// Format string // format of the paste -// Open_discussion int // open-discussion flag (TODO: not sure if bool works) -// Burn_after_reading int // burn-after-reading flag (TODO: not sure if bool works) -// -// } + + encryptionInfo := array1{iv,dummyKDFsalt,10000,265,128,"aes","gcm","zlib"} + + // TODO: this is really messy! + aData := authData{EncryptionDetails: encryptionInfo, Format: format, OpenDiscussion: openDiscussion, BurnAfterReading: burnAfterReading} + // aData.EncryptionDetails = encryptionInfo + // aData.Format = format + // aData.OpenDiscussion = openDiscussion + // aData.BurnAfterReading = burnAfterReading + var aDataInterface [4]interface{} //https://golang.org/doc/faq#convert_slice_of_interface + // for i, v := range aData { + // aDataInterface[i] = v + // } + aDataInterface[0] = aData.EncryptionDetails + aDataInterface[1] = aData.Format + aDataInterface[2] = aData.OpenDiscussion + aDataInterface[3] = aData.BurnAfterReading + + meta := PasteMeta{expiryDate} + + req.AuthData = aDataInterface + req.Meta = meta + req.Version = 2 + req.CipherText = cipherText + + return &req +} + +// ============================================================================================================================================================================================================================ type PasteMeta struct { // TODO: should be type "json" ? Expire string `json:"expire"` @@ -51,9 +83,9 @@ type PasteMeta struct { // TODO: should be type "json" ? type PasteRequest struct { // TODO: should be type "json" ? AuthData [4]interface{} `json:"adata"` - Meta PasteMeta `json:"meta"` // TODO: meta is another json + Meta *PasteMeta `json:"meta"` // TODO: meta is another json Version int `json:"v"` - Ciphertext []byte `json:"ct"` // TODO: type should be "base64" ? + CipherText []byte `json:"ct"` // TODO: type should be "base64" ? } type PasteResponse struct { @@ -73,8 +105,14 @@ var PasteTrigger = NamedTrigger{ Action: func(irc *hbot.Bot, m *hbot.Message) bool { // uncomment below - // plaintext := []byte(m.Content) // TODO: only fetch the paste content - // key, nonce, ciphertext, kdfsalt := encrypt(plaintext) + plaintext := []byte(m.Content) // TODO: only fetch paste content and options + key, nonce, ciphertext, kdfsalt := encrypt(plaintext) + pasteReq := PasteRequest.NewRequest(nonce, ciphertext, kdfsalt, "plaintext", 0, 0, "1week") + if pasteResp, err := recvPaste(pasteReq); err != nil { + fmt.Println("could not receive paste") + return false + } + fmt.Println("%v %v", key, pasteResp) /* TODO: 1. send request to privatebin @@ -83,23 +121,21 @@ var PasteTrigger = NamedTrigger{ 4. return a valid URL (https://$website?$paste_id#generated_key_in_base58) */ - return false + return true }, } -func recvPaste(pasteReq *PasteRequest) PasteResponse { +func recvPaste(pasteReq *PasteRequest) (resp PasteResponse, err error) { var ( - resp PasteResponse - jsonForm []byte - r, req *http.Request - err error - ) + jsonForm []byte + r, req *http.Request + ) if jsonForm, err := json.Marshal(pasteReq); err != nil { // Marshal, not NewEncoder fmt.Println(err) } httpClient := &http.Client{} - if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url + if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url fmt.Println(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -107,7 +143,7 @@ func recvPaste(pasteReq *PasteRequest) PasteResponse { // resp, err := http.Post(, "application/json", &jsonForm) if r, err := httpClient.Do(req); err != nil { - fmt.Println(err) + fmt.Println(err) // TODO: use log } // fmt.Println(r.Body) defer r.Body.Close() @@ -117,24 +153,13 @@ func recvPaste(pasteReq *PasteRequest) PasteResponse { // } // err = json.Unmarshal(body, &m) if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { - fmt.Println(err) + fmt.Println(err) // TODO: use logging } - return resp + return resp, err } -// func test() { -// var resp struct { -// Completed bool `json:"completed"` -// Id int `json:"id"` -// Title string `json:"title"` -// UserId int `json:"userId"` -// } - -// return resp -// } - -func encrypt(plaintext []byte) (string, []byte, []byte, []byte) { +func encrypt(plaintext []byte) (encodedKey string, nonce []byte, ciphertext []byte, dummyKDFsalt []byte) { // encrypts the message with a random key, then return it back // TODO: should generate the key instead of it being a random // TODO: should return a struct (first array of AuthData) @@ -173,15 +198,15 @@ func encrypt(plaintext []byte) (string, []byte, []byte, []byte) { } // encrypt, encode and return - ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) - encodedKey := base58.Encode(key) -// encodedNonce := base64.StdEncoding.EncodeToString(nonce) -// encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) - dummyKDFsalt := make([]byte, 8) - if _, err := io.ReadFull(rand.Reader, dummyKDFsalt); err != nil { - fmt.Println("warning, dummy kdfsalt is not initialized properly") - // encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it -} + ciphertext = aesgcm.Seal(nil, nonce, plaintext, nil) + encodedKey = base58.Encode(key) + // encodedNonce := base64.StdEncoding.EncodeToString(nonce) + // encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) + dummyKDFsalt = make([]byte, 8) // TODO: remove magic number + if _, err := io.ReadFull(rand.Reader, dummyKDFsalt); err != nil { + fmt.Println("warning, dummy kdfsalt is not initialized properly") + // encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it + } fmt.Printf("pt: %s\n key: %s\n, ct: %s\n", plaintext, encodedKey, ciphertext) return encodedKey, nonce, ciphertext, dummyKDFsalt } From 856c78b4d8a2bf2187bb3bc6a559f1a4ceb3c21a Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Sun, 12 Apr 2020 20:11:18 +0200 Subject: [PATCH 09/25] reworked the struct for pasteReq one more error to go! --- README.md | 5 ++ bitbot/paste.go | 156 +++++++++++++++++++++++++++++------------------- 2 files changed, 100 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index f0674f7..ed3d938 100644 --- a/README.md +++ b/README.md @@ -73,4 +73,9 @@ You will need to have a postgresSQL database running, you can launch one that wi docker-compose up -d db ``` +If you are running the database locally, then create a new user/database for bitbot: +``` +psql -c "CREATE USER bitbot WITH PASSWORD 'bitbot'; CREATE DATABASE bitbot;" +``` + Remember that if you're running in Docker, your Prometheus bind address must be 0.0.0.0 + some port and you must publish that port using `-p` diff --git a/bitbot/paste.go b/bitbot/paste.go index 6a515d9..5b2b06b 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -4,11 +4,14 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/tls" // "encoding/base64" + "os" "bytes" "encoding/json" "fmt" "io" + "io/ioutil" "net/http" "strings" @@ -23,7 +26,7 @@ const ( ) // not used directly in the paste request -type array1 struct { // TODO: more descriptive name +type Array1 struct { // TODO: more descriptive name // TODO: the following fields (till EOF) should be a (json ?) array Nonce []byte // base64(cipher_iv); getRandomBytes(16) default Kdfsalt []byte // base64(kdf_salt); getRandomBytes(8) default @@ -36,63 +39,71 @@ type array1 struct { // TODO: more descriptive name // EOF } -type authData struct { //TODO: should be type "json" ? +type AuthData struct { //TODO: should be type "json" ? // - EncryptionDetails *array1 // TODO: more descriptive name - Format string // format of the paste - OpenDiscussion int // open-discussion flag (TODO: not sure if bool works) - BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) + EncryptionDetails []interface{} // TODO: more descriptive name (type was: Array1) + Format string // format of the paste + OpenDiscussion int // open-discussion flag (TODO: not sure if bool works) + BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) // } -func (req PasteRequest) NewRequest(iv []byte, cipherText []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int, expiryDate string) *PasteRequest { +// ============================================================================================================================================================================================================================ + +type PasteMeta struct { // TODO: should be type "json" ? + Expire string `json:"expire"` +} + +type PasteResponse struct { + Status int `json:"status"` // TODO: not sure if bool works + Id string `json:"id"` + Url string `json:"url"` + Deletetoken string `json:"deletetoken"` +} + +type PasteRequest struct { // TODO: should be type "json" ? + AuthData []interface{} `json:"adata"` + Meta PasteMeta `json:"meta"` // TODO: meta is another json + Version int `json:"v"` + CipherText []byte `json:"ct"` // TODO: type should be "base64" ? +} + +func NewRequest(iv []byte, cipherText []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int, expiryDate string) (*PasteRequest){ + // encryptionInfo := Array1{iv, dummyKDFsalt, 10000, 265, 128, "aes", "gcm", "zlib"} + // encryptionInfo := make([]interface{}, 0) + var ( + req PasteRequest + encryptionInfo []interface{} + aData []interface{} // or aData := make([]interface{}, 0); then append + ) + encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "zlib") - encryptionInfo := array1{iv,dummyKDFsalt,10000,265,128,"aes","gcm","zlib"} // TODO: this is really messy! - aData := authData{EncryptionDetails: encryptionInfo, Format: format, OpenDiscussion: openDiscussion, BurnAfterReading: burnAfterReading} + // aData := AuthData{EncryptionDetails: encryptionInfo, Format: format, OpenDiscussion: openDiscussion, BurnAfterReading: burnAfterReading} // aData.EncryptionDetails = encryptionInfo // aData.Format = format // aData.OpenDiscussion = openDiscussion // aData.BurnAfterReading = burnAfterReading - var aDataInterface [4]interface{} //https://golang.org/doc/faq#convert_slice_of_interface + // var aDataInterface []interface{} //https://golang.org/doc/faq#convert_slice_of_interface // for i, v := range aData { // aDataInterface[i] = v // } - aDataInterface[0] = aData.EncryptionDetails - aDataInterface[1] = aData.Format - aDataInterface[2] = aData.OpenDiscussion - aDataInterface[3] = aData.BurnAfterReading + aData = append(aData, encryptionInfo, format, openDiscussion, burnAfterReading) + // aDataInterface[0] = aData.EncryptionDetails + // aDataInterface[1] = aData.Format + // aDataInterface[2] = aData.OpenDiscussion + // aDataInterface[3] = aData.BurnAfterReading meta := PasteMeta{expiryDate} - - req.AuthData = aDataInterface + + req.AuthData = aData req.Meta = meta req.Version = 2 req.CipherText = cipherText + return &req - return &req -} - -// ============================================================================================================================================================================================================================ - -type PasteMeta struct { // TODO: should be type "json" ? - Expire string `json:"expire"` -} - -type PasteRequest struct { // TODO: should be type "json" ? - AuthData [4]interface{} `json:"adata"` - Meta *PasteMeta `json:"meta"` // TODO: meta is another json - Version int `json:"v"` - CipherText []byte `json:"ct"` // TODO: type should be "base64" ? -} - -type PasteResponse struct { - Status int `json:"status"` // TODO: not sure if bool works - Id string `json:"id"` - Url string `json:"url"` - Deletetoken string `json:"deletetoken"` } var PasteTrigger = NamedTrigger{ @@ -104,15 +115,21 @@ var PasteTrigger = NamedTrigger{ }, Action: func(irc *hbot.Bot, m *hbot.Message) bool { + var ( + pasteResp PasteResponse + pasteReq *PasteRequest + err error + ) // uncomment below - plaintext := []byte(m.Content) // TODO: only fetch paste content and options + plaintext := []byte(m.Content) // TODO: only fetch paste content and options key, nonce, ciphertext, kdfsalt := encrypt(plaintext) - pasteReq := PasteRequest.NewRequest(nonce, ciphertext, kdfsalt, "plaintext", 0, 0, "1week") - if pasteResp, err := recvPaste(pasteReq); err != nil { - fmt.Println("could not receive paste") - return false - } - fmt.Println("%v %v", key, pasteResp) + pasteReq = NewRequest(nonce, ciphertext, kdfsalt, "plaintext", 0, 0, "1week") + if pasteResp, err = recvPaste(pasteReq); err != nil { + fmt.Println("could not receive paste") + return false + } + // TODO: check pasteresponse.status + fmt.Println(key, pasteResp) /* TODO: 1. send request to privatebin @@ -128,33 +145,50 @@ var PasteTrigger = NamedTrigger{ func recvPaste(pasteReq *PasteRequest) (resp PasteResponse, err error) { var ( jsonForm []byte - r, req *http.Request + req *http.Request + r *http.Response ) - if jsonForm, err := json.Marshal(pasteReq); err != nil { // Marshal, not NewEncoder + if jsonForm, err = json.Marshal(pasteReq); err != nil { // Marshal, not NewEncoder fmt.Println(err) } - httpClient := &http.Client{} - if req, err := http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url + fmt.Printf("marhsalled json: %s\n", jsonForm) + // TODO: self-signed certificates workaround (https://groups.google.com/d/msg/golang-nuts/v5ShM8R7Tdc/I2wyTy1o118J) + transportOptions := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + ForceAttemptHTTP2: true, + Proxy: http.ProxyFromEnvironment, + } + // TODO: to debug w/ burp, install its cert here + httpClient := &http.Client{Transport: transportOptions} + if req, err = http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url fmt.Println(err) } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Add("X-Requested-With", "JSONHttpRequest") - + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + req.Header.Add("X-Requested-With", "JSONHttpRequest") // reason we used http.NewRequest w/ Client.Do() + req.Header.Add("Origin", "https://bin.fraq.io") + + // fmt.Println("debugging body: ") + // if b, err := req.GetBody(); err == nil{ + // fmt.Println(ioutil.ReadAll(b)) + // fmt.Println(ioutil.ReadAll(req.Body)) + // } // resp, err := http.Post(, "application/json", &jsonForm) - if r, err := httpClient.Do(req); err != nil { - fmt.Println(err) // TODO: use log + url, err := http.ProxyFromEnvironment(req) + fmt.Printf("proxy: %s %s\n", url, err) + req.WriteProxy(os.Stdout) + if r, err = httpClient.Do(req); err != nil { + fmt.Printf("post to paste site error: %s\n", err) // TODO: use log + return resp, err } // fmt.Println(r.Body) defer r.Body.Close() - // body, err := ioutil.ReadAll(r.Body) - // if err != nil { - // print(err) + body, err := ioutil.ReadAll(r.Body) + fmt.Printf("received body: %s, err %s", body, err) + err = json.Unmarshal(body, &resp) + // if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { + // fmt.Printf("json decoding error: %s\n", err) // TODO: use logging // } - // err = json.Unmarshal(body, &m) - if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { - fmt.Println(err) // TODO: use logging - } return resp, err } @@ -202,7 +236,7 @@ func encrypt(plaintext []byte) (encodedKey string, nonce []byte, ciphertext []by encodedKey = base58.Encode(key) // encodedNonce := base64.StdEncoding.EncodeToString(nonce) // encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) - dummyKDFsalt = make([]byte, 8) // TODO: remove magic number + dummyKDFsalt = make([]byte, 8) // TODO: remove magic number if _, err := io.ReadFull(rand.Reader, dummyKDFsalt); err != nil { fmt.Println("warning, dummy kdfsalt is not initialized properly") // encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it From 5aee7a801166aa7395d1f099418f57fb752802df Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Thu, 16 Apr 2020 12:29:11 +0200 Subject: [PATCH 10/25] removed the misleading req.WriteReq() it turns out that req.WriteReq() consumes the req, hence subsequent calls find an empty request, which generates all sorts of error encoded paste data with zlib (will be removed later) authenticated aData; re-added docker-compose file --- bitbot/paste.go | 257 +++++++++++++++++++++++++-------------------- docker-compose.yml | 52 +++++++++ 2 files changed, 198 insertions(+), 111 deletions(-) create mode 100644 docker-compose.yml diff --git a/bitbot/paste.go b/bitbot/paste.go index 5b2b06b..e16905e 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -1,18 +1,22 @@ package bitbot import ( + "bytes" + "compress/zlib" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/tls" + "crypto/x509" // "encoding/base64" - "os" - "bytes" "encoding/json" "fmt" "io" - "io/ioutil" + "io/ioutil" + // "net" + // "time" "net/http" + // "os" "strings" "github.com/btcsuite/btcutil/base58" @@ -23,6 +27,7 @@ const ( nonceSize = 16 // privatebin uses a nonce of 16 bytes by default aesKeySize = 32 // using aes-256-gcm; for reference only gcmTagSize = 16 // for reference + kdfSaltSize = 8 // for reference ) // not used directly in the paste request @@ -41,60 +46,47 @@ type Array1 struct { // TODO: more descriptive name type AuthData struct { //TODO: should be type "json" ? // - EncryptionDetails []interface{} // TODO: more descriptive name (type was: Array1) - Format string // format of the paste - OpenDiscussion int // open-discussion flag (TODO: not sure if bool works) - BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) + EncryptionDetails []interface{} // TODO: more descriptive name (type was: Array1) + Format string // format of the paste + OpenDiscussion int // open-discussion flag (TODO: not sure if bool works) + BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) // } +type PasteData struct { // !shrug (see https://github.com/PrivateBin/PrivateBin/wiki/Encryption-format#data-passed-in) + Paste string `json:"paste"` // ciphertext (encrypted zlib'd plaintext) + Attachment string `json:"attachment"` + AttachementName string `json:"attachment_name"` + Children []interface{} `json:"children"` +} // ============================================================================================================================================================================================================================ -type PasteMeta struct { // TODO: should be type "json" ? - Expire string `json:"expire"` +// https://raw.githubusercontent.com/PrivateBin/PrivateBin/master/js/types.jsonld +type PasteMeta struct { + Expire string `json:"expire"` // ["5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never"] } type PasteResponse struct { - Status int `json:"status"` // TODO: not sure if bool works + Status int `json:"status"` Id string `json:"id"` Url string `json:"url"` Deletetoken string `json:"deletetoken"` } -type PasteRequest struct { // TODO: should be type "json" ? +type PasteRequest struct { AuthData []interface{} `json:"adata"` - Meta PasteMeta `json:"meta"` // TODO: meta is another json - Version int `json:"v"` - CipherText []byte `json:"ct"` // TODO: type should be "base64" ? + Meta PasteMeta `json:"meta"` + Version int `json:"v"` + CipherText []byte `json:"ct"` } -func NewRequest(iv []byte, cipherText []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int, expiryDate string) (*PasteRequest){ - // encryptionInfo := Array1{iv, dummyKDFsalt, 10000, 265, 128, "aes", "gcm", "zlib"} - // encryptionInfo := make([]interface{}, 0) - var ( - req PasteRequest - encryptionInfo []interface{} - aData []interface{} // or aData := make([]interface{}, 0); then append - ) - encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "zlib") - - - // TODO: this is really messy! - // aData := AuthData{EncryptionDetails: encryptionInfo, Format: format, OpenDiscussion: openDiscussion, BurnAfterReading: burnAfterReading} - // aData.EncryptionDetails = encryptionInfo - // aData.Format = format - // aData.OpenDiscussion = openDiscussion - // aData.BurnAfterReading = burnAfterReading - // var aDataInterface []interface{} //https://golang.org/doc/faq#convert_slice_of_interface - // for i, v := range aData { - // aDataInterface[i] = v - // } - aData = append(aData, encryptionInfo, format, openDiscussion, burnAfterReading) - // aDataInterface[0] = aData.EncryptionDetails - // aDataInterface[1] = aData.Format - // aDataInterface[2] = aData.OpenDiscussion - // aDataInterface[3] = aData.BurnAfterReading + +func NewRequest(aData []interface{}, cipherText []byte, expiryDate string) *PasteRequest { + + var ( + req PasteRequest + ) meta := PasteMeta{expiryDate} @@ -102,7 +94,7 @@ func NewRequest(iv []byte, cipherText []byte, dummyKDFsalt []byte, format strin req.Meta = meta req.Version = 2 req.CipherText = cipherText - return &req + return &req } @@ -120,28 +112,59 @@ var PasteTrigger = NamedTrigger{ pasteReq *PasteRequest err error ) - // uncomment below plaintext := []byte(m.Content) // TODO: only fetch paste content and options - key, nonce, ciphertext, kdfsalt := encrypt(plaintext) - pasteReq = NewRequest(nonce, ciphertext, kdfsalt, "plaintext", 0, 0, "1week") + key, nonce, kdfsalt := generateEncryptionParameters() + adata := generateAuthenticationData(nonce, kdfsalt, "plaintext", 0, 0) + ciphertext := encrypt(plaintext, key, nonce, adata) // auth tag is appended to ciphertext + pasteReq = NewRequest(adata, ciphertext, "1week") if pasteResp, err = recvPaste(pasteReq); err != nil { fmt.Println("could not receive paste") return false } - // TODO: check pasteresponse.status - fmt.Println(key, pasteResp) - /* - TODO: - 1. send request to privatebin - 2. parse the response - 3. check status, fail if not okay - 4. return a valid URL (https://$website?$paste_id#generated_key_in_base58) - - */ + + if pasteResp.Status != 0 { + irc.Reply(m, "This is impossible to see unless the same key AND message were used") + return false + } + url := fmt.Sprintf("https://bin.fraq.io%s#%s", pasteResp.Url, base58.Encode(key)) + deleteURL := fmt.Sprintf("https://bin.fraq.io/?pasteid=%s&deletetoken=%s", pasteResp.Id, pasteResp.Deletetoken) + fmt.Printf("response: %v\n", pasteResp) + fmt.Printf("url: %s\n delete url: %s\n", url, deleteURL) + return true }, } +func generateAuthenticationData(iv []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int) []interface{} { + // encryptionInfo := Array1{iv, dummyKDFsalt, 10000, 265, 128, "aes", "gcm", "zlib"} + // encryptionInfo := make([]interface{}, 0) + var ( + encryptionInfo []interface{} + aData []interface{} // or aData := make([]interface{}, 0); then append + ) + encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "zlib") + aData = append(aData, encryptionInfo, format, openDiscussion, burnAfterReading) + return aData +} +func generateEncryptionParameters() (key, iv, kdfSalt []byte){ + + + // since we'll be using a different random key for each paste, + // a fixed nonce should be OK (but we won't do it anyway) + totalSize := aesKeySize+nonceSize+kdfSaltSize + keyWithNonceAndKdfSalt := make([]byte, totalSize) + if _, err := io.ReadFull(rand.Reader, keyWithNonceAndKdfSalt); err != nil { + panic(err.Error()) + } + + key = keyWithNonceAndKdfSalt[:aesKeySize] + iv = keyWithNonceAndKdfSalt[aesKeySize:aesKeySize+nonceSize] + kdfSalt = keyWithNonceAndKdfSalt[totalSize-kdfSaltSize:]// dummy value for PBKDF as we don't use it + + return key, iv, kdfSalt + +} + func recvPaste(pasteReq *PasteRequest) (resp PasteResponse, err error) { var ( jsonForm []byte @@ -153,94 +176,106 @@ func recvPaste(pasteReq *PasteRequest) (resp PasteResponse, err error) { fmt.Println(err) } fmt.Printf("marhsalled json: %s\n", jsonForm) + // ==== cert ==== // // TODO: self-signed certificates workaround (https://groups.google.com/d/msg/golang-nuts/v5ShM8R7Tdc/I2wyTy1o118J) + cert, err := ioutil.ReadFile("/tmp/burp.pem") // TODO: to debug w/ burp, install its cert here + if err != nil { + fmt.Println("error in importing cert: ", err) + } + caCertPool := x509.NewCertPool() + if ok := caCertPool.AppendCertsFromPEM(cert); ok != true { + fmt.Println("error in appending cert") + } transportOptions := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - ForceAttemptHTTP2: true, - Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + RootCAs: caCertPool, + }, + Proxy: http.ProxyFromEnvironment, } - // TODO: to debug w/ burp, install its cert here + httpClient := &http.Client{Transport: transportOptions} if req, err = http.NewRequest("POST", "https://bin.fraq.io", bytes.NewReader(jsonForm)); err != nil { // TODO: don't hardcode url fmt.Println(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - req.Header.Add("X-Requested-With", "JSONHttpRequest") // reason we used http.NewRequest w/ Client.Do() - req.Header.Add("Origin", "https://bin.fraq.io") - - // fmt.Println("debugging body: ") - // if b, err := req.GetBody(); err == nil{ - // fmt.Println(ioutil.ReadAll(b)) - // fmt.Println(ioutil.ReadAll(req.Body)) - // } + req.Header.Add("X-Requested-With", "JSONHttpRequest") // reason we used http.NewRequest w/ Client.Do() + // req.Header.Add("Origin", "https://bin.fraq.io") + + // fmt.Println("debugging body: ") + // if b, err := req.GetBody(); err == nil{ + // fmt.Println(ioutil.ReadAll(b)) + // fmt.Println(ioutil.ReadAll(req.Body)) + // } // resp, err := http.Post(, "application/json", &jsonForm) url, err := http.ProxyFromEnvironment(req) fmt.Printf("proxy: %s %s\n", url, err) - req.WriteProxy(os.Stdout) + // req.WriteProxy(os.Stdout) + if r, err = httpClient.Do(req); err != nil { fmt.Printf("post to paste site error: %s\n", err) // TODO: use log - return resp, err + return resp, err } // fmt.Println(r.Body) defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - fmt.Printf("received body: %s, err %s", body, err) - err = json.Unmarshal(body, &resp) - // if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { - // fmt.Printf("json decoding error: %s\n", err) // TODO: use logging - // } + // body, err := ioutil.ReadAll(r.Body) + // fmt.Printf("received body: %s, err %s\n", body, err) + // err = json.Unmarshal(body, &resp) + if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { + fmt.Printf("json decoding error: %s\n", err) // TODO: use logging + } return resp, err } -func encrypt(plaintext []byte) (encodedKey string, nonce []byte, ciphertext []byte, dummyKDFsalt []byte) { - // encrypts the message with a random key, then return it back - // TODO: should generate the key instead of it being a random - // TODO: should return a struct (first array of AuthData) - - /* public function create($pasteid, $paste) - { - $pasteid = substr(hash('md5', $paste['data']), 0, 16); - - $paste['data'] // text - $paste['meta']['postdate'] // int UNIX timestamp - $paste['meta']['expire_date'] // int UNIX timestamp - $paste['meta']['opendiscussion'] // true (if false it is unset) - $paste['meta']['burnafterreading'] // true (if false it is unset; if true, then opendiscussion is unset) - $paste['meta']['formatter'] // string - $paste['meta']['attachment'] // text - $paste['meta']['attachmentname'] // text - } - */ +func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphertext []byte){ + // compresses the message with zlib and encrypts it with a random key - // since we'll be using a different random key for each paste, - // a fixed nonce should be OK (but we won't do it anyway) - keyWithNonce := make([]byte, aesKeySize+nonceSize) - if _, err := io.ReadFull(rand.Reader, keyWithNonce); err != nil { + block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size + if err != nil { panic(err.Error()) } - key, nonce := keyWithNonce[:aesKeySize], keyWithNonce[aesKeySize:] - block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size + aesgcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) //TODO: should instruct privatebin to use standard nonce size instead if err != nil { panic(err.Error()) } - aesgcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) - if err != nil { + // compress and encrypt message, then encode key and return + var ( + compressedCiphertext bytes.Buffer + // pasteData PasteData + // encodedCompressedPlaintext bytes.Buffer + cipherJson, authenticatedDataJson []byte + ) + // pasteData = PasteData{Paste: string(plaintext)} // TODO: support file attachement and paste linking + pasteData := struct { + Paste string `json:"paste"` + }{ + string(plaintext), + } + if cipherJson, err = json.Marshal(pasteData); err != nil { // Marshal, not NewEncoder + panic(err.Error()) + } + if authenticatedDataJson, err = json.Marshal(authenticationData); err != nil { // Marshal, not NewEncoder panic(err.Error()) } + fmt.Printf("marshalled cipher: %s\n", cipherJson) + fmt.Printf("marshalled adata: %s\n", authenticatedDataJson) + + // TODO: add check for compression support (for now, assuming defaults) + compressedCiphertextWriter := zlib.NewWriter(&compressedCiphertext) + compressedCiphertextWriter.Write(cipherJson) + compressedCiphertextWriter.Close() + // encoder := base64.NewEncoder(base64.StdEncoding, &encodedCompressedPlaintext) + // encoder.Write(compressedCiphertext.Bytes()) + // encoder.Close() - // encrypt, encode and return - ciphertext = aesgcm.Seal(nil, nonce, plaintext, nil) - encodedKey = base58.Encode(key) + // authData is authenticated as well(https://github.com/r4sas/PBinCLI/blob/682b47fbd3e24a8a53c3b484ba896a5dbc85cda2/pbincli/format.py#L122) + // kudos to filo for hinting about the tag location (https://github.com/golang/go/issues/32742) + ciphertext = aesgcm.Seal(nil, iv, compressedCiphertext.Bytes(), authenticatedDataJson) // encodedNonce := base64.StdEncoding.EncodeToString(nonce) // encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) - dummyKDFsalt = make([]byte, 8) // TODO: remove magic number - if _, err := io.ReadFull(rand.Reader, dummyKDFsalt); err != nil { - fmt.Println("warning, dummy kdfsalt is not initialized properly") - // encodedKDFsalt := "kvDZJC6IahU=" // dummy value for PBKDF as we don't use it - } - fmt.Printf("pt: %s\n key: %s\n, ct: %s\n", plaintext, encodedKey, ciphertext) - return encodedKey, nonce, ciphertext, dummyKDFsalt + fmt.Printf("pt: %s\n key: %s\n", plaintext, base58.Encode(key)) + return ciphertext } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..135d200 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +version: "3.7" +services: + bitbot: + # image: bbriggs/bitbot:latest + build: . + environment: + SERVER: "irc.example.com:6697" + CHANNELS: "#bots" + NICK: "bitbot-test" + SSL: "true" + PROM: "true" + PROMADDR: "0.0.0.0:8080" + DBUSER: "bitbot" + DBPASS: "bitbot" + DBHOST: "db" + DBPORT: "5432" + DBSSLMODE: "disable" + prometheus: + image: prom/prometheus + command: + - '--config.file=/tmp/prometheus.yml' + volumes: + - ./prometheus.yml:/tmp/prometheus.yml + - prometheus:/prometheus + ports: + - "127.0.0.1:9090:9090" + grafana: + image: grafana/grafana + ports: + - "127.0.0.1:3000:3000" + environment: + GF_SECURITY_ADMIN_PASSWORD: "grafana" + volumes: + - grafana:/var/lib/grafana + db: + image: postgres:12.1 + ports: + - "127.0.0.1:5432:5432" + restart: unless-stopped + volumes: + - db-data:/var/lib/postgresql/data + environment: + PGDATA: /var/lib/postgresql/data/pgdata + POSTGRES_USER: bitbot + POSTGRES_PASSWORD: bitbot + POSTGRES_DB: bitbot + +volumes: + grafana: + prometheus: + bolt: + db-data: From 809b30ffc058047e989881ffbfb5e9f27c328e0d Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Sun, 19 Apr 2020 09:01:50 +0200 Subject: [PATCH 11/25] use a KDF to derive aes key; working version! as it turned out, the key used in decryption is derived from the base58 encoded key and paste password; Initially, I thought the key used in AES is the random-looking key Interestingly enough, the KDF package is released 4 days ago by the Go crypto team, with version number v0.0.0 --- bitbot/paste.go | 26 ++++++++++++++++++++------ go.mod | 1 + go.sum | 1 + 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index e16905e..499e2e9 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -1,7 +1,17 @@ package bitbot +// welp; it turned out the key (the base58 encoded one) is used in a key-derivation function; that is, the key used in AES-GCM is the derived one; consequently, the salt we send is equally important; here're the specs +// key derivation function: pbkdf2 +// key: password of paste + our random key +// salt: the one we send +// hash algorithm: sha-256 (used at the end of derivation) +// iterations: 10,000 (can we instruct the server to lower it ?) +// ==================================================== +// will be using this: https://pkg.go.dev/golang.org/x/crypto/pbkdf2?tab=doc import ( "bytes" + "golang.org/x/crypto/pbkdf2" + "crypto/sha256" "compress/zlib" "crypto/aes" "crypto/cipher" @@ -115,7 +125,8 @@ var PasteTrigger = NamedTrigger{ plaintext := []byte(m.Content) // TODO: only fetch paste content and options key, nonce, kdfsalt := generateEncryptionParameters() adata := generateAuthenticationData(nonce, kdfsalt, "plaintext", 0, 0) - ciphertext := encrypt(plaintext, key, nonce, adata) // auth tag is appended to ciphertext + aesKey := pbkdf2.Key(key, kdfsalt, 100000, aesKeySize, sha256.New) + ciphertext := encrypt(plaintext, aesKey, nonce, adata) // auth tag is appended to ciphertext pasteReq = NewRequest(adata, ciphertext, "1week") if pasteResp, err = recvPaste(pasteReq); err != nil { fmt.Println("could not receive paste") @@ -128,8 +139,9 @@ var PasteTrigger = NamedTrigger{ } url := fmt.Sprintf("https://bin.fraq.io%s#%s", pasteResp.Url, base58.Encode(key)) deleteURL := fmt.Sprintf("https://bin.fraq.io/?pasteid=%s&deletetoken=%s", pasteResp.Id, pasteResp.Deletetoken) - fmt.Printf("response: %v\n", pasteResp) - fmt.Printf("url: %s\n delete url: %s\n", url, deleteURL) + fmt.Printf("response: %v\n", pasteResp) // TODO: use logging + fmt.Printf("url: %s\n delete url: %s\n", url, deleteURL) // TODO: use logging + irc.Reply(m, fmt.Sprintf("Link: %s | Delete paste: %s", url, deleteURL)) return true }, @@ -142,7 +154,7 @@ func generateAuthenticationData(iv []byte, dummyKDFsalt []byte, format string, o encryptionInfo []interface{} aData []interface{} // or aData := make([]interface{}, 0); then append ) - encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "zlib") + encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "none") // TODO: rget back zlib support aData = append(aData, encryptionInfo, format, openDiscussion, burnAfterReading) return aData } @@ -263,7 +275,7 @@ func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphe fmt.Printf("marshalled cipher: %s\n", cipherJson) fmt.Printf("marshalled adata: %s\n", authenticatedDataJson) - // TODO: add check for compression support (for now, assuming defaults) + // TODO: add check for compression support (for now, assuming defaults); get back zlib compressedCiphertextWriter := zlib.NewWriter(&compressedCiphertext) compressedCiphertextWriter.Write(cipherJson) compressedCiphertextWriter.Close() @@ -273,7 +285,9 @@ func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphe // authData is authenticated as well(https://github.com/r4sas/PBinCLI/blob/682b47fbd3e24a8a53c3b484ba896a5dbc85cda2/pbincli/format.py#L122) // kudos to filo for hinting about the tag location (https://github.com/golang/go/issues/32742) - ciphertext = aesgcm.Seal(nil, iv, compressedCiphertext.Bytes(), authenticatedDataJson) + // look for function " decryptOrPromptPassword" in privatebin.js; start debugging there + // TODO: fully support the API (https://github.com/PrivateBin/PrivateBin/wiki/API) + ciphertext = aesgcm.Seal(nil, iv, cipherJson, authenticatedDataJson) // TODO: zzzzlib // encodedNonce := base64.StdEncoding.EncodeToString(nonce) // encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) fmt.Printf("pt: %s\n key: %s\n", plaintext, base58.Encode(key)) diff --git a/go.mod b/go.mod index d8a3a2a..2dfd9e6 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/spf13/viper v1.4.0 github.com/whyrusleeping/hellabot v0.0.0-20191113145436-fd8fa1922281 go.etcd.io/bbolt v1.3.3 + golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec gopkg.in/sorcix/irc.v1 v1.1.4 diff --git a/go.sum b/go.sum index 334bf54..ab396ec 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= From 3dcc8a50f1c1d2221abe8fd8267a1714f2a91338 Mon Sep 17 00:00:00 2001 From: Bren Briggs Date: Tue, 31 Mar 2020 14:34:56 -0400 Subject: [PATCH 12/25] Uppercase arguments to covid19 stats function --- bitbot/covid19.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 4051226..4b3945d 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -79,7 +79,7 @@ func parseCovid19Trigger(args []string, data *Covid19Data) string { case 1: resp = fmt.Sprintf("Total confirmed: %d | Total deaths: %d", data.Confirmed.Latest, data.Deaths.Latest) default: - country, confirmed, dead := covid19StatsByCountryCode(args[1], data) + country, confirmed, dead := covid19StatsByCountryCode(strings.ToUpper(args[1]), data) resp = fmt.Sprintf("Stats for %s || Confirmed: %d | Deaths %d", country, confirmed, dead) } return resp From fd733e760935d28c52016a4499bd000591843fb3 Mon Sep 17 00:00:00 2001 From: Bren Briggs Date: Wed, 1 Apr 2020 09:08:00 -0400 Subject: [PATCH 13/25] Get total for country by summing all provinces (#152) Instead of looking for a "NAN" field which may or may not be there (are you sorry, Canada?) we should run a collector value that adds the totals for each province in a given country code. --- bitbot/covid19.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 4b3945d..274b254 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -93,17 +93,16 @@ func covid19StatsByCountryCode(cc string, data *Covid19Data) (string, int, int) ) for _, v := range data.Confirmed.Locations { - log.Println(v.CountryCode) - if v.CountryCode == cc && v.Province == "nan" { - country = v.Country - confirmed = v.Latest + if v.CountryCode == cc { + country = v.Country // Yes I am aware this write the country var every time. Sue me. + confirmed += v.Latest // Sum all the provinces because a "nan" field isn't guaranteed break } } for _, v := range data.Deaths.Locations { - if v.CountryCode == cc && v.Province == "nan" { - deaths = v.Latest + if v.CountryCode == cc { + deaths += v.Latest break } } From d898ade035f20a394df693c354d397bfad375674 Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 1 Apr 2020 16:45:27 +0200 Subject: [PATCH 14/25] Basic Province trigger patch Signed-off-by: Olivier Moreau m242 --- bitbot/channels.go | 2 +- bitbot/covid19.go | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/bitbot/channels.go b/bitbot/channels.go index 284c247..1beb387 100644 --- a/bitbot/channels.go +++ b/bitbot/channels.go @@ -23,7 +23,7 @@ var PartTrigger = NamedTrigger{ ID: "part", Help: "Command the bot to leave the channel. Usage: [bot nick] part [channel]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { - return m.Command == "PRIVMSG" && strings.HasPrefix(m.Content, irc.Nick + " part") + return m.Command == "PRIVMSG" && strings.HasPrefix(m.Content, irc.Nick+" part") }, Action: func(irc *hbot.Bot, m *hbot.Message) bool { splitMsg := strings.Split(m.Content, " ") diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 274b254..00f7964 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -78,13 +78,43 @@ func parseCovid19Trigger(args []string, data *Covid19Data) string { resp = "Error: empty request" case 1: resp = fmt.Sprintf("Total confirmed: %d | Total deaths: %d", data.Confirmed.Latest, data.Deaths.Latest) - default: + case 2: country, confirmed, dead := covid19StatsByCountryCode(strings.ToUpper(args[1]), data) resp = fmt.Sprintf("Stats for %s || Confirmed: %d | Deaths %d", country, confirmed, dead) + default: + province, country, confirmed, dead := covid19StatsOfProvince(strings.ToUpper(args[1]), args[2:], data) + resp = fmt.Sprintf("Stats for %s, %s || Confirmed: %d | Deaths: %d", province, country, confirmed, dead) } return resp } +func covid19StatsOfProvince(cc string, prov []string, data *Covid19Data) (string, string, int, int) { + var ( + confirmed int + deaths int + country string + province string + ) + province = strings.Title(strings.Join(prov[:], " ")) + + for _, v := range data.Confirmed.Locations { + if v.Province == province { + country = v.Country + confirmed = v.Latest + break + } + } + + for _, v := range data.Deaths.Locations { + if v.Province == province { + deaths = v.Latest + break + } + } + + return province, country, confirmed, deaths +} + func covid19StatsByCountryCode(cc string, data *Covid19Data) (string, int, int) { var ( confirmed int From 3b4a7c51870404085a253dd154ef6dfaaac4b0e8 Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 1 Apr 2020 16:46:57 +0200 Subject: [PATCH 15/25] Fixed total trigger addition loops Signed-off-by: Olivier Moreau m242 --- bitbot/covid19.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 00f7964..cf08ce5 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -126,14 +126,12 @@ func covid19StatsByCountryCode(cc string, data *Covid19Data) (string, int, int) if v.CountryCode == cc { country = v.Country // Yes I am aware this write the country var every time. Sue me. confirmed += v.Latest // Sum all the provinces because a "nan" field isn't guaranteed - break } } for _, v := range data.Deaths.Locations { if v.CountryCode == cc { deaths += v.Latest - break } } return country, confirmed, deaths From 9057ade0aed2ba5cfac63ed193b7d6df3eef0668 Mon Sep 17 00:00:00 2001 From: staticnotdynamic <45046614+staticnotdynamic@users.noreply.github.com> Date: Mon, 6 Apr 2020 14:58:16 +0200 Subject: [PATCH 16/25] removing code anti-patterns (#158) * Add .deepsource.toml * Remove unnecessary blank (_) identifier * Fix Yoda conditions (testing deepsource) Co-authored-by: DeepSource Bot Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com> --- .deepsource.toml | 8 ++++++++ bitbot/flipTable.go | 4 ++-- bitbot/util.go | 2 +- cmd/root.go | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..5c42f8c --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,8 @@ +version = 1 + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_path = "github.com/staticnotdynamic/bitbot" diff --git a/bitbot/flipTable.go b/bitbot/flipTable.go index 61feb94..d69f716 100644 --- a/bitbot/flipTable.go +++ b/bitbot/flipTable.go @@ -8,7 +8,7 @@ var TableFlipTrigger = NamedTrigger{ ID: "tableflip", Help: "Flip a table. Usage: !tableflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { - match := ("!tableflip" == m.Content) + match := (m.Content == "!tableflip") return m.Command == "PRIVMSG" && match }, Action: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -20,7 +20,7 @@ var TableFlipTrigger = NamedTrigger{ var TableUnflipTrigger = NamedTrigger{ ID: "unflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { - match := ("!unflip" == m.Content) + match := (m.Content == "!unflip") return m.Command == "PRIVMSG" && match }, Action: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/util.go b/bitbot/util.go index c0af199..bf6e77b 100644 --- a/bitbot/util.go +++ b/bitbot/util.go @@ -66,7 +66,7 @@ func (b *Bot) ListTriggers() []string { b.triggerMutex.RLock() defer b.triggerMutex.RUnlock() - for k, _ := range b.triggers { + for k := range b.triggers { triggers = append(triggers, k) } return triggers diff --git a/cmd/root.go b/cmd/root.go index 2717b3b..cd82e08 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -165,7 +165,7 @@ func init() { // All plugins enabled by default var defaultPlugins []string - for plugin, _ := range pluginMap { + for plugin := range pluginMap { defaultPlugins = append(defaultPlugins, plugin) } viper.SetDefault("nick", "bitbot") From 71dcbd0bab72251ecec17a739a0b3a7b4c4ab0a1 Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Sat, 25 Apr 2020 19:44:10 +0200 Subject: [PATCH 17/25] test rebasing --- bitbot/urbandict.go | 91 +++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 5 +++ 2 files changed, 96 insertions(+) create mode 100644 bitbot/urbandict.go diff --git a/bitbot/urbandict.go b/bitbot/urbandict.go new file mode 100644 index 0000000..9c30b4e --- /dev/null +++ b/bitbot/urbandict.go @@ -0,0 +1,91 @@ +package bitbot + +// partly stolen from https://github.com/dpatrie/urbandictionary +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + + "github.com/whyrusleeping/hellabot" +) + +var UrbanDictionaryTrigger = NamedTrigger{ + ID: "urbandict", + Help: "Get an urban dictionary issued definition. Usage: !urbd [term]", + Condition: func(irc *hbot.Bot, m *hbot.Message) bool { + return m.Command == "PRIVMSG" && strings.HasPrefix(m.Trailing, "!ud") + }, + Action: func(irc *hbot.Bot, m *hbot.Message) bool { + resp := urbanDefinition(m.Content) + irc.Reply(m, resp) + return true + }, +} + +func urbanDefinition(message string) string { + term := strings.SplitAfterN(message, " ", 2)[1] // Strip trigger word + res, err := urbanDictQuery(term) + if err != nil { + log.Println(err) + return "The search failed" + } + + if len(res.Results) > 0 { + return fmt.Sprintf("%s: %s", term, cleanDef(res.Results[0].Definition)) + } + return "No definition for that word" +} + +func cleanDef(def string) string { + def = strings.ReplaceAll(def, "[", "") + def = strings.ReplaceAll(def, "]", "") + + return def +} + +type searchResult struct { + Type string `json:"result_type"` + Tags []string + Results []result `json:"list"` + Sounds []string +} + +type result struct { + Author string + Word string + Definition string + Example string + Permalink string + Upvote int `json:"thumbs_up"` + Downvote int `json:"thumbs_down"` +} + +func urbanDictQuery(searchTerm string) (*searchResult, error) { + const baseURL = "http://api.urbandictionary.com/v0/define?term=" + resp, err := http.Get(baseURL + url.QueryEscape(searchTerm)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP Response was not a 200: %d", resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + res := &searchResult{} + err = json.Unmarshal(body, res) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/cmd/root.go b/cmd/root.go index cd82e08..33917ac 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,7 +72,12 @@ var pluginMap = map[string]bitbot.NamedTrigger{ "markovTrainer": bitbot.MarkovTrainerTrigger, "epeen": bitbot.EpeenTrigger, "ipinfo": bitbot.IPinfoTrigger, +<<<<<<< HEAD "paste": bitbot.PasteTrigger, +||||||| parent of 7289891... Added urbd trigger, closes #14 (#156) +======= + "urbd": bitbot.UrbanDictionaryTrigger, +>>>>>>> 7289891... Added urbd trigger, closes #14 (#156) } // rootCmd represents the base command when called without any subcommands From d9a8936a031be8056232642b6bd85df7a8f95e2a Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 10:05:49 +0200 Subject: [PATCH 18/25] Added golang-ci "//nolint" to avoid global variables false positives on named triggers. used ```shell cd bitbot grep -R "NamedTrigger" | cut -d":" -f 1 | xargs -I f sed -i "s,NamedTrigger{,& //nolint:gochecknoglobals," f ``` Signed-off-by: Olivier Moreau m242 --- bitbot/8ball.go | 2 +- bitbot/abyss.go | 2 +- bitbot/beefy.go | 2 +- bitbot/channelPopGauge.go | 6 +++--- bitbot/channels.go | 4 ++-- bitbot/covid19.go | 2 +- bitbot/decisions.go | 2 +- bitbot/dice.go | 2 +- bitbot/epeen.go | 2 +- bitbot/flipTable.go | 4 ++-- bitbot/help.go | 2 +- bitbot/info.go | 2 +- bitbot/ipinfo.go | 2 +- bitbot/links.go | 2 +- bitbot/loadtest.go | 2 +- bitbot/markov.go | 6 +++--- bitbot/messageCounter.go | 2 +- bitbot/nick.go | 2 +- bitbot/quotes.go | 2 +- bitbot/shrug.go | 2 +- bitbot/skip.go | 2 +- bitbot/tarot.go | 2 +- bitbot/triggerConditions_test.go | 2 +- bitbot/trollLauncher.go | 2 +- bitbot/urbandict.go | 2 +- 25 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bitbot/8ball.go b/bitbot/8ball.go index f905ee1..d3ee15c 100644 --- a/bitbot/8ball.go +++ b/bitbot/8ball.go @@ -6,7 +6,7 @@ import ( "strings" ) -var Magic8BallTrigger = NamedTrigger{ +var Magic8BallTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "8ball", Help: "Beseech the magic 8ball. Usage: !8ball [question]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/abyss.go b/bitbot/abyss.go index 2e22f45..a7e5e13 100644 --- a/bitbot/abyss.go +++ b/bitbot/abyss.go @@ -5,7 +5,7 @@ import ( "math/rand" ) -var AbyssTrigger = NamedTrigger{ +var AbyssTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "abyss", Help: "State of the art advanced Abyss simulator. Non-interactive.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/beefy.go b/bitbot/beefy.go index e0556bb..6e132a6 100644 --- a/bitbot/beefy.go +++ b/bitbot/beefy.go @@ -5,7 +5,7 @@ import ( "regexp" ) -var BeefyTrigger = NamedTrigger{ +var BeefyTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "beefy", Help: "It's big. It's beefy. It triggers any time someone says \"beefy\" in a message.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/channelPopGauge.go b/bitbot/channelPopGauge.go index 3eed1b9..5e735e7 100644 --- a/bitbot/channelPopGauge.go +++ b/bitbot/channelPopGauge.go @@ -9,7 +9,7 @@ import ( "gopkg.in/sorcix/irc.v1" ) -var ChannelPopGaugeTrigger = NamedTrigger{ +var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "channelPopGauge", Help: "Updates a Prometheus gauge with the value of a channel's population", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -32,7 +32,7 @@ var ChannelPopGaugeTrigger = NamedTrigger{ }, } -var HandleListReplyTrigger = NamedTrigger{ +var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "handleListReply", Help: "Sets the gauge for a channel pop when an RPL_LIST command is detected", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -50,7 +50,7 @@ var HandleListReplyTrigger = NamedTrigger{ }, } -var SetChanPopGaugeTrigger = NamedTrigger{ +var SetChanPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "setChannelPopGauge", Help: "Sets the gauge for a channel's population when the bot joins", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/channels.go b/bitbot/channels.go index 1beb387..9dbe5bc 100644 --- a/bitbot/channels.go +++ b/bitbot/channels.go @@ -7,7 +7,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var InviteTrigger = NamedTrigger{ +var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "invite", Help: "Follow invites to other channels. Usage: /invite [bot nick]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -19,7 +19,7 @@ var InviteTrigger = NamedTrigger{ }, } -var PartTrigger = NamedTrigger{ +var PartTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "part", Help: "Command the bot to leave the channel. Usage: [bot nick] part [channel]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/covid19.go b/bitbot/covid19.go index cf08ce5..93c6e7d 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -52,7 +52,7 @@ type Recovered struct { Locations []Locations `json:"locations"` } -var Covid19Trigger = NamedTrigger{ +var Covid19Trigger = NamedTrigger{ //nolint:gochecknoglobals ID: "covid19", Help: "Fetch stats on Coronavirus by region", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/decisions.go b/bitbot/decisions.go index 65d792e..395661c 100644 --- a/bitbot/decisions.go +++ b/bitbot/decisions.go @@ -8,7 +8,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var DecisionsTrigger = NamedTrigger{ +var DecisionsTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "decisions", Help: "Let the bot decide something for you. Usage: ${bot_name} choose option option [option...]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/dice.go b/bitbot/dice.go index b84f727..4c8897c 100644 --- a/bitbot/dice.go +++ b/bitbot/dice.go @@ -11,7 +11,7 @@ import ( const DICE_USAGE = "Usage: [num dice]d[sides](+/-num) (opt: if fudging)" -var RollTrigger = NamedTrigger{ +var RollTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "roll", Help: "!roll " + DICE_USAGE, Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/epeen.go b/bitbot/epeen.go index 3fdd0ba..096ae98 100644 --- a/bitbot/epeen.go +++ b/bitbot/epeen.go @@ -6,7 +6,7 @@ import ( "strings" ) -var EpeenTrigger = NamedTrigger{ +var EpeenTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "epeen", Help: "epeen returns the length of the requesters epeen. Usage: !epeen", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/flipTable.go b/bitbot/flipTable.go index d69f716..885fc6e 100644 --- a/bitbot/flipTable.go +++ b/bitbot/flipTable.go @@ -4,7 +4,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var TableFlipTrigger = NamedTrigger{ +var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "tableflip", Help: "Flip a table. Usage: !tableflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -17,7 +17,7 @@ var TableFlipTrigger = NamedTrigger{ }, } -var TableUnflipTrigger = NamedTrigger{ +var TableUnflipTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "unflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { match := (m.Content == "!unflip") diff --git a/bitbot/help.go b/bitbot/help.go index 53941d2..ca0c138 100644 --- a/bitbot/help.go +++ b/bitbot/help.go @@ -5,7 +5,7 @@ import ( "strings" ) -var HelpTrigger = NamedTrigger{ +var HelpTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "help", Help: "Usage: !help [trigger name]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/info.go b/bitbot/info.go index fe800d3..f7d3711 100644 --- a/bitbot/info.go +++ b/bitbot/info.go @@ -6,7 +6,7 @@ import ( "strings" ) -var InfoTrigger = NamedTrigger{ +var InfoTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "info", Help: "Get version and repo information about this bot. Usage: !info", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/ipinfo.go b/bitbot/ipinfo.go index b8316f6..c1862d3 100644 --- a/bitbot/ipinfo.go +++ b/bitbot/ipinfo.go @@ -24,7 +24,7 @@ type GeoData struct { Readme string } -var IPinfoTrigger = NamedTrigger{ +var IPinfoTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "ipinfo", Help: "!ipinfo ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/links.go b/bitbot/links.go index 76f97b9..ea80674 100644 --- a/bitbot/links.go +++ b/bitbot/links.go @@ -13,7 +13,7 @@ import ( "strings" ) -var URLReaderTrigger = NamedTrigger{ +var URLReaderTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "urls", Help: "Looks up URLs in chat and returns the page title as a message.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/loadtest.go b/bitbot/loadtest.go index 3623a58..81ca464 100644 --- a/bitbot/loadtest.go +++ b/bitbot/loadtest.go @@ -53,7 +53,7 @@ var unloadTrigger = hbot.Trigger{ }, } -var listTriggers = NamedTrigger{ +var listTriggers = NamedTrigger{ //nolint:gochecknoglobals ID: "listTriggers", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { return m.Command == "PRIVMSG" && strings.TrimSpace(m.Content) == "!triggers" && b.isAdmin(m) diff --git a/bitbot/markov.go b/bitbot/markov.go index e8265b0..fac3d46 100644 --- a/bitbot/markov.go +++ b/bitbot/markov.go @@ -11,7 +11,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var MarkovTrainerTrigger = NamedTrigger{ +var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "markovTrainer", Help: "Incrementally trains bitbot's markov model on every new privmsg", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -27,7 +27,7 @@ var MarkovTrainerTrigger = NamedTrigger{ }, } -var MarkovResponseTrigger = NamedTrigger{ +var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "markovResponse", Help: "Returns a randomly generated markov string. Usage: !babble", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -39,7 +39,7 @@ var MarkovResponseTrigger = NamedTrigger{ }, } -var MarkovInitTrigger = NamedTrigger{ +var MarkovInitTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals ID: "markovInit", Help: "Resets markov chain to a fresh chain, or bootstraps it with sample texts. Usage: !markov reset, !markov init", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/messageCounter.go b/bitbot/messageCounter.go index 1a74221..42ae91d 100644 --- a/bitbot/messageCounter.go +++ b/bitbot/messageCounter.go @@ -4,7 +4,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var MessageCounterTrigger = NamedTrigger{ +var MessageCounterTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "messageCounter", Help: "Increments a counter for every message it sees in chat.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/nick.go b/bitbot/nick.go index 5202a02..9d87623 100644 --- a/bitbot/nick.go +++ b/bitbot/nick.go @@ -6,7 +6,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var NickTakenTrigger = NamedTrigger{ +var NickTakenTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "nick", Help: "Avoids nick collisions by renaming the bot if the nick is already taken.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/quotes.go b/bitbot/quotes.go index feb5857..db6b65f 100644 --- a/bitbot/quotes.go +++ b/bitbot/quotes.go @@ -10,7 +10,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var RaiderQuoteTrigger = NamedTrigger{ +var RaiderQuoteTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "raider", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { return m.Command == "PRIVMSG" && b.Random.Intn(1000) == 1 diff --git a/bitbot/shrug.go b/bitbot/shrug.go index 71163fa..bc5090c 100644 --- a/bitbot/shrug.go +++ b/bitbot/shrug.go @@ -5,7 +5,7 @@ import ( "strings" ) -var ShrugTrigger = NamedTrigger{ +var ShrugTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "shrug", Help: "Usage: !shrug", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/skip.go b/bitbot/skip.go index a81290f..00f5fb4 100644 --- a/bitbot/skip.go +++ b/bitbot/skip.go @@ -7,7 +7,7 @@ import ( // SkipTrigger sets a message prefix that instructs bitbot not to process the message // Should be set before any "skippable" triggers and after any triggers that run on all messages (unskippable) -var SkipTrigger = NamedTrigger{ +var SkipTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "skip", Help: "Prevents a message from being processed by other triggers. Usage: !skip ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/tarot.go b/bitbot/tarot.go index 5d87797..e6df014 100644 --- a/bitbot/tarot.go +++ b/bitbot/tarot.go @@ -7,7 +7,7 @@ import ( "strings" ) -var TarotTrigger = NamedTrigger{ +var TarotTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "Tarot", Help: "Request tarot cards, default 1. Usage !tarot [num cards].", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/triggerConditions_test.go b/bitbot/triggerConditions_test.go index b9c70d9..68f7bac 100644 --- a/bitbot/triggerConditions_test.go +++ b/bitbot/triggerConditions_test.go @@ -6,7 +6,7 @@ import ( // These should test basic passing cases func TestBasicNamedTriggers(t *testing.T) { - triggerTests := map[string]NamedTrigger{ + triggerTests := map[string]NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals "!shrug": ShrugTrigger, "!skip": SkipTrigger, "!info": InfoTrigger, diff --git a/bitbot/trollLauncher.go b/bitbot/trollLauncher.go index e7b3d12..1a86a70 100644 --- a/bitbot/trollLauncher.go +++ b/bitbot/trollLauncher.go @@ -7,7 +7,7 @@ import ( "strings" ) -var TrollLauncherTrigger = NamedTrigger{ +var TrollLauncherTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "troll", Help: "Launches a random number of trolls for a random amount of damage. Usage: !troll ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/urbandict.go b/bitbot/urbandict.go index 9c30b4e..5499510 100644 --- a/bitbot/urbandict.go +++ b/bitbot/urbandict.go @@ -13,7 +13,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var UrbanDictionaryTrigger = NamedTrigger{ +var UrbanDictionaryTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "urbandict", Help: "Get an urban dictionary issued definition. Usage: !urbd [term]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { From 29d56602b7b612dc89ed36919152e9328a8cd3a2 Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 10:12:34 +0200 Subject: [PATCH 19/25] Do not remind us that bitbot global vars are global. Signed-off-by: Olivier Moreau m242 --- cmd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/root.go b/cmd/root.go index 33917ac..51a92c2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -32,6 +32,7 @@ import ( const VERSION = "" +// nolint:gochecknoglobals var ( cfgFile string server string From dd543e588b7506960b9049f5151525f25f239814 Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 10:21:06 +0200 Subject: [PATCH 20/25] Ignored False Positives on the Body.Close() method, golangci-lint wants it to have an error return which it doesn't have. Signed-off-by: Olivier Moreau m242 --- bitbot/covid19.go | 2 +- bitbot/ipinfo.go | 2 +- bitbot/links.go | 2 +- bitbot/markov.go | 2 +- bitbot/quotes.go | 2 +- bitbot/urbandict.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 93c6e7d..6cb2a8f 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -145,7 +145,7 @@ func getCovid19Data() (Covid19Data, bool) { log.Println(err.Error()) return resp, false } - defer r.Body.Close() + defer r.Body.Close() //nolint:errcheck,gosec err = json.NewDecoder(r.Body).Decode(&resp) if err != nil { log.Println(err) diff --git a/bitbot/ipinfo.go b/bitbot/ipinfo.go index c1862d3..55b9dd8 100644 --- a/bitbot/ipinfo.go +++ b/bitbot/ipinfo.go @@ -83,6 +83,6 @@ func query(ip string) string { log.Fatal(err) } - res.Body.Close() + res.Body.Close() //nolint:errcheck,gosec return decodeJSON(jsonData) } diff --git a/bitbot/links.go b/bitbot/links.go index ea80674..1e4ba42 100644 --- a/bitbot/links.go +++ b/bitbot/links.go @@ -63,7 +63,7 @@ func lookupPageTitle(message string) string { if err != nil { return "" } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck,gosec log.Println("Unable to lookup page") if title, ok := GetHtmlTitle(resp.Body); ok { return (title) diff --git a/bitbot/markov.go b/bitbot/markov.go index fac3d46..22c40b0 100644 --- a/bitbot/markov.go +++ b/bitbot/markov.go @@ -94,7 +94,7 @@ func markovInit(chain *gomarkov.Chain) bool { log.Println(err) return false } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck,gosec body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Println(err) diff --git a/bitbot/quotes.go b/bitbot/quotes.go index db6b65f..98da1f3 100644 --- a/bitbot/quotes.go +++ b/bitbot/quotes.go @@ -31,7 +31,7 @@ func getQuote(endpoint string) (model.Response, bool) { log.Println(err.Error()) return resp, false } - defer r.Body.Close() + defer r.Body.Close() //nolint:errcheck,gosec err = json.NewDecoder(r.Body).Decode(&resp) if err != nil { log.Println(err) diff --git a/bitbot/urbandict.go b/bitbot/urbandict.go index 5499510..f8a74eb 100644 --- a/bitbot/urbandict.go +++ b/bitbot/urbandict.go @@ -70,7 +70,7 @@ func urbanDictQuery(searchTerm string) (*searchResult, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck,gosec if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP Response was not a 200: %d", resp.StatusCode) From a4f413c62e0d127358e30415ffc97589184fccfd Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 10:49:34 +0200 Subject: [PATCH 21/25] Corrected mistakes Signed-off-by: Olivier Moreau m242 --- bitbot/channelPopGauge.go | 6 +++--- bitbot/channels.go | 4 ++-- bitbot/flipTable.go | 4 ++-- bitbot/markov.go | 6 +++--- bitbot/triggerConditions_test.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bitbot/channelPopGauge.go b/bitbot/channelPopGauge.go index 5e735e7..73a3df7 100644 --- a/bitbot/channelPopGauge.go +++ b/bitbot/channelPopGauge.go @@ -9,7 +9,7 @@ import ( "gopkg.in/sorcix/irc.v1" ) -var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "channelPopGauge", Help: "Updates a Prometheus gauge with the value of a channel's population", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -32,7 +32,7 @@ var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:go }, } -var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "handleListReply", Help: "Sets the gauge for a channel pop when an RPL_LIST command is detected", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -50,7 +50,7 @@ var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:go }, } -var SetChanPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var SetChanPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "setChannelPopGauge", Help: "Sets the gauge for a channel's population when the bot joins", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/channels.go b/bitbot/channels.go index 9dbe5bc..3dbc9aa 100644 --- a/bitbot/channels.go +++ b/bitbot/channels.go @@ -7,7 +7,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals +var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "invite", Help: "Follow invites to other channels. Usage: /invite [bot nick]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -19,7 +19,7 @@ var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknogl }, } -var PartTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals +var PartTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "part", Help: "Command the bot to leave the channel. Usage: [bot nick] part [channel]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/flipTable.go b/bitbot/flipTable.go index 885fc6e..c0a5bc7 100644 --- a/bitbot/flipTable.go +++ b/bitbot/flipTable.go @@ -4,7 +4,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals +var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "tableflip", Help: "Flip a table. Usage: !tableflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -17,7 +17,7 @@ var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gocheckn }, } -var TableUnflipTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals +var TableUnflipTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "unflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { match := (m.Content == "!unflip") diff --git a/bitbot/markov.go b/bitbot/markov.go index 22c40b0..992fcd7 100644 --- a/bitbot/markov.go +++ b/bitbot/markov.go @@ -11,7 +11,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "markovTrainer", Help: "Incrementally trains bitbot's markov model on every new privmsg", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -27,7 +27,7 @@ var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:goch }, } -var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "markovResponse", Help: "Returns a randomly generated markov string. Usage: !babble", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -39,7 +39,7 @@ var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:goc }, } -var MarkovInitTrigger = NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals //nolint:gochecknoglobals +var MarkovInitTrigger = NamedTrigger{ //nolint:gochecknoglobals ID: "markovInit", Help: "Resets markov chain to a fresh chain, or bootstraps it with sample texts. Usage: !markov reset, !markov init", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/triggerConditions_test.go b/bitbot/triggerConditions_test.go index 68f7bac..56ca8ba 100644 --- a/bitbot/triggerConditions_test.go +++ b/bitbot/triggerConditions_test.go @@ -6,7 +6,7 @@ import ( // These should test basic passing cases func TestBasicNamedTriggers(t *testing.T) { - triggerTests := map[string]NamedTrigger{ //nolint:gochecknoglobals //nolint:gochecknoglobals + triggerTests := map[string]NamedTrigger{ //nolint:gochecknoglobals "!shrug": ShrugTrigger, "!skip": SkipTrigger, "!info": InfoTrigger, From 340d61454f5a6475e33216f8dcbc7e1fd3a808aa Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 10:51:26 +0200 Subject: [PATCH 22/25] Added a linter to already nolinted false positives Signed-off-by: Olivier Moreau m242 --- bitbot/8ball.go | 2 +- bitbot/abyss.go | 2 +- bitbot/beefy.go | 2 +- bitbot/channelPopGauge.go | 6 +++--- bitbot/channels.go | 4 ++-- bitbot/covid19.go | 2 +- bitbot/decisions.go | 2 +- bitbot/dice.go | 2 +- bitbot/epeen.go | 2 +- bitbot/flipTable.go | 4 ++-- bitbot/help.go | 2 +- bitbot/info.go | 2 +- bitbot/ipinfo.go | 2 +- bitbot/links.go | 2 +- bitbot/loadtest.go | 2 +- bitbot/markov.go | 6 +++--- bitbot/messageCounter.go | 2 +- bitbot/nick.go | 2 +- bitbot/quotes.go | 2 +- bitbot/shrug.go | 2 +- bitbot/skip.go | 2 +- bitbot/tarot.go | 2 +- bitbot/triggerConditions_test.go | 2 +- bitbot/trollLauncher.go | 2 +- bitbot/urbandict.go | 2 +- 25 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bitbot/8ball.go b/bitbot/8ball.go index d3ee15c..024e946 100644 --- a/bitbot/8ball.go +++ b/bitbot/8ball.go @@ -6,7 +6,7 @@ import ( "strings" ) -var Magic8BallTrigger = NamedTrigger{ //nolint:gochecknoglobals +var Magic8BallTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "8ball", Help: "Beseech the magic 8ball. Usage: !8ball [question]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/abyss.go b/bitbot/abyss.go index a7e5e13..2098914 100644 --- a/bitbot/abyss.go +++ b/bitbot/abyss.go @@ -5,7 +5,7 @@ import ( "math/rand" ) -var AbyssTrigger = NamedTrigger{ //nolint:gochecknoglobals +var AbyssTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "abyss", Help: "State of the art advanced Abyss simulator. Non-interactive.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/beefy.go b/bitbot/beefy.go index 6e132a6..231a454 100644 --- a/bitbot/beefy.go +++ b/bitbot/beefy.go @@ -5,7 +5,7 @@ import ( "regexp" ) -var BeefyTrigger = NamedTrigger{ //nolint:gochecknoglobals +var BeefyTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "beefy", Help: "It's big. It's beefy. It triggers any time someone says \"beefy\" in a message.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/channelPopGauge.go b/bitbot/channelPopGauge.go index 73a3df7..4f8e821 100644 --- a/bitbot/channelPopGauge.go +++ b/bitbot/channelPopGauge.go @@ -9,7 +9,7 @@ import ( "gopkg.in/sorcix/irc.v1" ) -var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals +var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "channelPopGauge", Help: "Updates a Prometheus gauge with the value of a channel's population", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -32,7 +32,7 @@ var ChannelPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals +var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "handleListReply", Help: "Sets the gauge for a channel pop when an RPL_LIST command is detected", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { @@ -50,7 +50,7 @@ var HandleListReplyTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var SetChanPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals +var SetChanPopGaugeTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "setChannelPopGauge", Help: "Sets the gauge for a channel's population when the bot joins", Condition: func(bot *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/channels.go b/bitbot/channels.go index 3dbc9aa..8e8af44 100644 --- a/bitbot/channels.go +++ b/bitbot/channels.go @@ -7,7 +7,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals +var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "invite", Help: "Follow invites to other channels. Usage: /invite [bot nick]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -19,7 +19,7 @@ var InviteTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var PartTrigger = NamedTrigger{ //nolint:gochecknoglobals +var PartTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "part", Help: "Command the bot to leave the channel. Usage: [bot nick] part [channel]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/covid19.go b/bitbot/covid19.go index 6cb2a8f..62e0327 100644 --- a/bitbot/covid19.go +++ b/bitbot/covid19.go @@ -52,7 +52,7 @@ type Recovered struct { Locations []Locations `json:"locations"` } -var Covid19Trigger = NamedTrigger{ //nolint:gochecknoglobals +var Covid19Trigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "covid19", Help: "Fetch stats on Coronavirus by region", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/decisions.go b/bitbot/decisions.go index 395661c..10e5cd0 100644 --- a/bitbot/decisions.go +++ b/bitbot/decisions.go @@ -8,7 +8,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var DecisionsTrigger = NamedTrigger{ //nolint:gochecknoglobals +var DecisionsTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "decisions", Help: "Let the bot decide something for you. Usage: ${bot_name} choose option option [option...]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/dice.go b/bitbot/dice.go index 4c8897c..8f164f1 100644 --- a/bitbot/dice.go +++ b/bitbot/dice.go @@ -11,7 +11,7 @@ import ( const DICE_USAGE = "Usage: [num dice]d[sides](+/-num) (opt: if fudging)" -var RollTrigger = NamedTrigger{ //nolint:gochecknoglobals +var RollTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "roll", Help: "!roll " + DICE_USAGE, Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/epeen.go b/bitbot/epeen.go index 096ae98..f53604a 100644 --- a/bitbot/epeen.go +++ b/bitbot/epeen.go @@ -6,7 +6,7 @@ import ( "strings" ) -var EpeenTrigger = NamedTrigger{ //nolint:gochecknoglobals +var EpeenTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "epeen", Help: "epeen returns the length of the requesters epeen. Usage: !epeen", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/flipTable.go b/bitbot/flipTable.go index c0a5bc7..b32c8f0 100644 --- a/bitbot/flipTable.go +++ b/bitbot/flipTable.go @@ -4,7 +4,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals +var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "tableflip", Help: "Flip a table. Usage: !tableflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -17,7 +17,7 @@ var TableFlipTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var TableUnflipTrigger = NamedTrigger{ //nolint:gochecknoglobals +var TableUnflipTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "unflip", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { match := (m.Content == "!unflip") diff --git a/bitbot/help.go b/bitbot/help.go index ca0c138..6ce6bd1 100644 --- a/bitbot/help.go +++ b/bitbot/help.go @@ -5,7 +5,7 @@ import ( "strings" ) -var HelpTrigger = NamedTrigger{ //nolint:gochecknoglobals +var HelpTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "help", Help: "Usage: !help [trigger name]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/info.go b/bitbot/info.go index f7d3711..7c427f1 100644 --- a/bitbot/info.go +++ b/bitbot/info.go @@ -6,7 +6,7 @@ import ( "strings" ) -var InfoTrigger = NamedTrigger{ //nolint:gochecknoglobals +var InfoTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "info", Help: "Get version and repo information about this bot. Usage: !info", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/ipinfo.go b/bitbot/ipinfo.go index 55b9dd8..f89a6f5 100644 --- a/bitbot/ipinfo.go +++ b/bitbot/ipinfo.go @@ -24,7 +24,7 @@ type GeoData struct { Readme string } -var IPinfoTrigger = NamedTrigger{ //nolint:gochecknoglobals +var IPinfoTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "ipinfo", Help: "!ipinfo ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/links.go b/bitbot/links.go index 1e4ba42..6a5248a 100644 --- a/bitbot/links.go +++ b/bitbot/links.go @@ -13,7 +13,7 @@ import ( "strings" ) -var URLReaderTrigger = NamedTrigger{ //nolint:gochecknoglobals +var URLReaderTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "urls", Help: "Looks up URLs in chat and returns the page title as a message.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/loadtest.go b/bitbot/loadtest.go index 81ca464..f39c3a4 100644 --- a/bitbot/loadtest.go +++ b/bitbot/loadtest.go @@ -53,7 +53,7 @@ var unloadTrigger = hbot.Trigger{ }, } -var listTriggers = NamedTrigger{ //nolint:gochecknoglobals +var listTriggers = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "listTriggers", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { return m.Command == "PRIVMSG" && strings.TrimSpace(m.Content) == "!triggers" && b.isAdmin(m) diff --git a/bitbot/markov.go b/bitbot/markov.go index 992fcd7..01488f7 100644 --- a/bitbot/markov.go +++ b/bitbot/markov.go @@ -11,7 +11,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals +var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "markovTrainer", Help: "Incrementally trains bitbot's markov model on every new privmsg", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -27,7 +27,7 @@ var MarkovTrainerTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals +var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "markovResponse", Help: "Returns a randomly generated markov string. Usage: !babble", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { @@ -39,7 +39,7 @@ var MarkovResponseTrigger = NamedTrigger{ //nolint:gochecknoglobals }, } -var MarkovInitTrigger = NamedTrigger{ //nolint:gochecknoglobals +var MarkovInitTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "markovInit", Help: "Resets markov chain to a fresh chain, or bootstraps it with sample texts. Usage: !markov reset, !markov init", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/messageCounter.go b/bitbot/messageCounter.go index 42ae91d..a2d12f8 100644 --- a/bitbot/messageCounter.go +++ b/bitbot/messageCounter.go @@ -4,7 +4,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var MessageCounterTrigger = NamedTrigger{ //nolint:gochecknoglobals +var MessageCounterTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "messageCounter", Help: "Increments a counter for every message it sees in chat.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/nick.go b/bitbot/nick.go index 9d87623..cb4834c 100644 --- a/bitbot/nick.go +++ b/bitbot/nick.go @@ -6,7 +6,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var NickTakenTrigger = NamedTrigger{ //nolint:gochecknoglobals +var NickTakenTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "nick", Help: "Avoids nick collisions by renaming the bot if the nick is already taken.", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/quotes.go b/bitbot/quotes.go index 98da1f3..6ba49e6 100644 --- a/bitbot/quotes.go +++ b/bitbot/quotes.go @@ -10,7 +10,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var RaiderQuoteTrigger = NamedTrigger{ //nolint:gochecknoglobals +var RaiderQuoteTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "raider", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { return m.Command == "PRIVMSG" && b.Random.Intn(1000) == 1 diff --git a/bitbot/shrug.go b/bitbot/shrug.go index bc5090c..11c2ffe 100644 --- a/bitbot/shrug.go +++ b/bitbot/shrug.go @@ -5,7 +5,7 @@ import ( "strings" ) -var ShrugTrigger = NamedTrigger{ //nolint:gochecknoglobals +var ShrugTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "shrug", Help: "Usage: !shrug", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/skip.go b/bitbot/skip.go index 00f5fb4..908498a 100644 --- a/bitbot/skip.go +++ b/bitbot/skip.go @@ -7,7 +7,7 @@ import ( // SkipTrigger sets a message prefix that instructs bitbot not to process the message // Should be set before any "skippable" triggers and after any triggers that run on all messages (unskippable) -var SkipTrigger = NamedTrigger{ //nolint:gochecknoglobals +var SkipTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "skip", Help: "Prevents a message from being processed by other triggers. Usage: !skip ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/tarot.go b/bitbot/tarot.go index e6df014..ef165da 100644 --- a/bitbot/tarot.go +++ b/bitbot/tarot.go @@ -7,7 +7,7 @@ import ( "strings" ) -var TarotTrigger = NamedTrigger{ //nolint:gochecknoglobals +var TarotTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "Tarot", Help: "Request tarot cards, default 1. Usage !tarot [num cards].", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/triggerConditions_test.go b/bitbot/triggerConditions_test.go index 56ca8ba..89c9ded 100644 --- a/bitbot/triggerConditions_test.go +++ b/bitbot/triggerConditions_test.go @@ -6,7 +6,7 @@ import ( // These should test basic passing cases func TestBasicNamedTriggers(t *testing.T) { - triggerTests := map[string]NamedTrigger{ //nolint:gochecknoglobals + triggerTests := map[string]NamedTrigger{ //nolint:gochecknoglobals,golint "!shrug": ShrugTrigger, "!skip": SkipTrigger, "!info": InfoTrigger, diff --git a/bitbot/trollLauncher.go b/bitbot/trollLauncher.go index 1a86a70..0d6536d 100644 --- a/bitbot/trollLauncher.go +++ b/bitbot/trollLauncher.go @@ -7,7 +7,7 @@ import ( "strings" ) -var TrollLauncherTrigger = NamedTrigger{ //nolint:gochecknoglobals +var TrollLauncherTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "troll", Help: "Launches a random number of trolls for a random amount of damage. Usage: !troll ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { diff --git a/bitbot/urbandict.go b/bitbot/urbandict.go index f8a74eb..ef7935d 100644 --- a/bitbot/urbandict.go +++ b/bitbot/urbandict.go @@ -13,7 +13,7 @@ import ( "github.com/whyrusleeping/hellabot" ) -var UrbanDictionaryTrigger = NamedTrigger{ //nolint:gochecknoglobals +var UrbanDictionaryTrigger = NamedTrigger{ //nolint:gochecknoglobals,golint ID: "urbandict", Help: "Get an urban dictionary issued definition. Usage: !urbd [term]", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { From 16f853fd4704d73cf9ab131f1bf06ab3c4392118 Mon Sep 17 00:00:00 2001 From: Olivier Moreau m242 Date: Wed, 8 Apr 2020 11:23:01 +0200 Subject: [PATCH 23/25] Fixed improper definition sanitizing Signed-off-by: Olivier Moreau m242 --- bitbot/urbandict.go | 1 + 1 file changed, 1 insertion(+) diff --git a/bitbot/urbandict.go b/bitbot/urbandict.go index ef7935d..64f0c19 100644 --- a/bitbot/urbandict.go +++ b/bitbot/urbandict.go @@ -43,6 +43,7 @@ func urbanDefinition(message string) string { func cleanDef(def string) string { def = strings.ReplaceAll(def, "[", "") def = strings.ReplaceAll(def, "]", "") + def = strings.ReplaceAll(def, "\r\n", " ") return def } From eb6261cdda4ff3e5959aa51bfd0f1db4a78baa1d Mon Sep 17 00:00:00 2001 From: rfc2119 Date: Sat, 25 Apr 2020 19:56:47 +0200 Subject: [PATCH 24/25] !paste now activates on a private message only --- bitbot/paste.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index 499e2e9..858c861 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -113,7 +113,9 @@ var PasteTrigger = NamedTrigger{ Help: "returns a pastebin link for a PRIVMSG to bitbot" + "\nUsage: !paste ", Condition: func(irc *hbot.Bot, m *hbot.Message) bool { - return m.Command == "PRIVMSG" && strings.HasPrefix(m.Content, "!paste ") + return m.Command == "PRIVMSG" && // This is a message + m.Params[0] == irc.Nick && // Private to the bot + strings.HasPrefix(m.Content, "!paste ") // beginning with !paste }, Action: func(irc *hbot.Bot, m *hbot.Message) bool { From 09a52c8f150edd886faa3d05fc3794306796efd5 Mon Sep 17 00:00:00 2001 From: m242 Date: Sat, 9 May 2020 23:28:53 +0200 Subject: [PATCH 25/25] Formatted and other small changes that help with the linting but don't touch the code. --- bitbot/paste.go | 106 +++++++++++++++++++++++++----------------------- cmd/root.go | 2 +- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/bitbot/paste.go b/bitbot/paste.go index 858c861..7e0e934 100644 --- a/bitbot/paste.go +++ b/bitbot/paste.go @@ -10,14 +10,14 @@ package bitbot // will be using this: https://pkg.go.dev/golang.org/x/crypto/pbkdf2?tab=doc import ( "bytes" - "golang.org/x/crypto/pbkdf2" - "crypto/sha256" "compress/zlib" "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "crypto/tls" "crypto/x509" + "golang.org/x/crypto/pbkdf2" // "encoding/base64" "encoding/json" "fmt" @@ -34,13 +34,13 @@ import ( ) const ( - nonceSize = 16 // privatebin uses a nonce of 16 bytes by default - aesKeySize = 32 // using aes-256-gcm; for reference only - gcmTagSize = 16 // for reference - kdfSaltSize = 8 // for reference + nonceSize = 16 // privatebin uses a nonce of 16 bytes by default + aesKeySize = 32 // using aes-256-gcm; for reference only + gcmTagSize = 16 // for reference + kdfSaltSize = 8 // for reference ) -// not used directly in the paste request +// Array1 : not used directly in the paste request type Array1 struct { // TODO: more descriptive name // TODO: the following fields (till EOF) should be a (json ?) array Nonce []byte // base64(cipher_iv); getRandomBytes(16) default @@ -54,6 +54,7 @@ type Array1 struct { // TODO: more descriptive name // EOF } +// AuthData : Format is the paste's format. type AuthData struct { //TODO: should be type "json" ? // EncryptionDetails []interface{} // TODO: more descriptive name (type was: Array1) @@ -62,8 +63,10 @@ type AuthData struct { //TODO: should be type "json" ? BurnAfterReading int // burn-after-reading flag (TODO: not sure if bool works) // } -type PasteData struct { // !shrug (see https://github.com/PrivateBin/PrivateBin/wiki/Encryption-format#data-passed-in) - Paste string `json:"paste"` // ciphertext (encrypted zlib'd plaintext) + +// PasteData : !shrug (see https://github.com/PrivateBin/PrivateBin/wiki/Encryption-format#data-passed-in) +type PasteData struct { + Paste string `json:"paste"` // ciphertext (encrypted zlib'd plaintext) Attachment string `json:"attachment"` AttachementName string `json:"attachment_name"` Children []interface{} `json:"children"` @@ -71,11 +74,12 @@ type PasteData struct { // !shrug (see https://github.com/PrivateBin/PrivateBin/ // ============================================================================================================================================================================================================================ -// https://raw.githubusercontent.com/PrivateBin/PrivateBin/master/js/types.jsonld +// PasteMeta : https://raw.githubusercontent.com/PrivateBin/PrivateBin/master/js/types.jsonld type PasteMeta struct { Expire string `json:"expire"` // ["5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never"] } +// PasteResponse : A request's response, parsed type PasteResponse struct { Status int `json:"status"` Id string `json:"id"` @@ -83,6 +87,7 @@ type PasteResponse struct { Deletetoken string `json:"deletetoken"` } +// PasteRequest : A paste request type PasteRequest struct { AuthData []interface{} `json:"adata"` Meta PasteMeta `json:"meta"` @@ -90,12 +95,11 @@ type PasteRequest struct { CipherText []byte `json:"ct"` } - - +// NewRequest : Forges a new request to be posted. func NewRequest(aData []interface{}, cipherText []byte, expiryDate string) *PasteRequest { var ( - req PasteRequest + req PasteRequest ) meta := PasteMeta{expiryDate} @@ -108,7 +112,8 @@ func NewRequest(aData []interface{}, cipherText []byte, expiryDate string) *Past } -var PasteTrigger = NamedTrigger{ +// PasteTrigger : The trigger that takes care of the pasting. +var PasteTrigger = NamedTrigger{ //nolint ID: "paste", Help: "returns a pastebin link for a PRIVMSG to bitbot" + "\nUsage: !paste ", @@ -125,10 +130,10 @@ var PasteTrigger = NamedTrigger{ err error ) plaintext := []byte(m.Content) // TODO: only fetch paste content and options - key, nonce, kdfsalt := generateEncryptionParameters() - adata := generateAuthenticationData(nonce, kdfsalt, "plaintext", 0, 0) - aesKey := pbkdf2.Key(key, kdfsalt, 100000, aesKeySize, sha256.New) - ciphertext := encrypt(plaintext, aesKey, nonce, adata) // auth tag is appended to ciphertext + key, nonce, kdfsalt := generateEncryptionParameters() + adata := generateAuthenticationData(nonce, kdfsalt, "plaintext", 0, 0) + aesKey := pbkdf2.Key(key, kdfsalt, 100000, aesKeySize, sha256.New) + ciphertext := encrypt(plaintext, aesKey, nonce, adata) // auth tag is appended to ciphertext pasteReq = NewRequest(adata, ciphertext, "1week") if pasteResp, err = recvPaste(pasteReq); err != nil { fmt.Println("could not receive paste") @@ -141,9 +146,9 @@ var PasteTrigger = NamedTrigger{ } url := fmt.Sprintf("https://bin.fraq.io%s#%s", pasteResp.Url, base58.Encode(key)) deleteURL := fmt.Sprintf("https://bin.fraq.io/?pasteid=%s&deletetoken=%s", pasteResp.Id, pasteResp.Deletetoken) - fmt.Printf("response: %v\n", pasteResp) // TODO: use logging - fmt.Printf("url: %s\n delete url: %s\n", url, deleteURL) // TODO: use logging - irc.Reply(m, fmt.Sprintf("Link: %s | Delete paste: %s", url, deleteURL)) + fmt.Printf("response: %v\n", pasteResp) // TODO: use logging + fmt.Printf("url: %s\n delete url: %s\n", url, deleteURL) // TODO: use logging + irc.Reply(m, fmt.Sprintf("Link: %s | Delete paste: %s", url, deleteURL)) return true }, @@ -152,30 +157,29 @@ var PasteTrigger = NamedTrigger{ func generateAuthenticationData(iv []byte, dummyKDFsalt []byte, format string, openDiscussion int, burnAfterReading int) []interface{} { // encryptionInfo := Array1{iv, dummyKDFsalt, 10000, 265, 128, "aes", "gcm", "zlib"} // encryptionInfo := make([]interface{}, 0) - var ( + var ( encryptionInfo []interface{} aData []interface{} // or aData := make([]interface{}, 0); then append - ) - encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "none") // TODO: rget back zlib support + ) + encryptionInfo = append(encryptionInfo, iv, dummyKDFsalt, 100000, 256, 128, "aes", "gcm", "none") // TODO: rget back zlib support aData = append(aData, encryptionInfo, format, openDiscussion, burnAfterReading) - return aData + return aData } -func generateEncryptionParameters() (key, iv, kdfSalt []byte){ - +func generateEncryptionParameters() (key, iv, kdfSalt []byte) { // since we'll be using a different random key for each paste, // a fixed nonce should be OK (but we won't do it anyway) - totalSize := aesKeySize+nonceSize+kdfSaltSize - keyWithNonceAndKdfSalt := make([]byte, totalSize) + totalSize := aesKeySize + nonceSize + kdfSaltSize + keyWithNonceAndKdfSalt := make([]byte, totalSize) if _, err := io.ReadFull(rand.Reader, keyWithNonceAndKdfSalt); err != nil { panic(err.Error()) } key = keyWithNonceAndKdfSalt[:aesKeySize] - iv = keyWithNonceAndKdfSalt[aesKeySize:aesKeySize+nonceSize] - kdfSalt = keyWithNonceAndKdfSalt[totalSize-kdfSaltSize:]// dummy value for PBKDF as we don't use it + iv = keyWithNonceAndKdfSalt[aesKeySize : aesKeySize+nonceSize] + kdfSalt = keyWithNonceAndKdfSalt[totalSize-kdfSaltSize:] // dummy value for PBKDF as we don't use it - return key, iv, kdfSalt + return key, iv, kdfSalt } @@ -242,7 +246,7 @@ func recvPaste(pasteReq *PasteRequest) (resp PasteResponse, err error) { return resp, err } -func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphertext []byte){ +func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphertext []byte) { // compresses the message with zlib and encrypts it with a random key block, err := aes.NewCipher(key) // will auto-pick aes-256 because key size @@ -250,34 +254,34 @@ func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphe panic(err.Error()) } - aesgcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) //TODO: should instruct privatebin to use standard nonce size instead + aesgcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) //TODO: should instruct privatebin to use standard nonce size instead if err != nil { panic(err.Error()) } // compress and encrypt message, then encode key and return var ( - compressedCiphertext bytes.Buffer - // pasteData PasteData + compressedCiphertext bytes.Buffer + // pasteData PasteData // encodedCompressedPlaintext bytes.Buffer - cipherJson, authenticatedDataJson []byte + cipherJson, authenticatedDataJson []byte ) - // pasteData = PasteData{Paste: string(plaintext)} // TODO: support file attachement and paste linking - pasteData := struct { - Paste string `json:"paste"` - }{ - string(plaintext), - } + // pasteData = PasteData{Paste: string(plaintext)} // TODO: support file attachement and paste linking + pasteData := struct { + Paste string `json:"paste"` + }{ + string(plaintext), + } if cipherJson, err = json.Marshal(pasteData); err != nil { // Marshal, not NewEncoder panic(err.Error()) } if authenticatedDataJson, err = json.Marshal(authenticationData); err != nil { // Marshal, not NewEncoder panic(err.Error()) } - fmt.Printf("marshalled cipher: %s\n", cipherJson) - fmt.Printf("marshalled adata: %s\n", authenticatedDataJson) + fmt.Printf("marshalled cipher: %s\n", cipherJson) + fmt.Printf("marshalled adata: %s\n", authenticatedDataJson) - // TODO: add check for compression support (for now, assuming defaults); get back zlib + // TODO: add check for compression support (for now, assuming defaults); get back zlib compressedCiphertextWriter := zlib.NewWriter(&compressedCiphertext) compressedCiphertextWriter.Write(cipherJson) compressedCiphertextWriter.Close() @@ -285,11 +289,11 @@ func encrypt(plaintext, key, iv []byte, authenticationData []interface{}) (ciphe // encoder.Write(compressedCiphertext.Bytes()) // encoder.Close() - // authData is authenticated as well(https://github.com/r4sas/PBinCLI/blob/682b47fbd3e24a8a53c3b484ba896a5dbc85cda2/pbincli/format.py#L122) - // kudos to filo for hinting about the tag location (https://github.com/golang/go/issues/32742) - // look for function " decryptOrPromptPassword" in privatebin.js; start debugging there - // TODO: fully support the API (https://github.com/PrivateBin/PrivateBin/wiki/API) - ciphertext = aesgcm.Seal(nil, iv, cipherJson, authenticatedDataJson) // TODO: zzzzlib + // authData is authenticated as well(https://github.com/r4sas/PBinCLI/blob/682b47fbd3e24a8a53c3b484ba896a5dbc85cda2/pbincli/format.py#L122) + // kudos to filo for hinting about the tag location (https://github.com/golang/go/issues/32742) + // look for function " decryptOrPromptPassword" in privatebin.js; start debugging there + // TODO: fully support the API (https://github.com/PrivateBin/PrivateBin/wiki/API) + ciphertext = aesgcm.Seal(nil, iv, cipherJson, authenticatedDataJson) // TODO: zzzzlib // encodedNonce := base64.StdEncoding.EncodeToString(nonce) // encodedCipherText := base64.StdEncoding.EncodeToString(ciphertext) fmt.Printf("pt: %s\n key: %s\n", plaintext, base58.Encode(key)) diff --git a/cmd/root.go b/cmd/root.go index 7539821..f859460 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -73,7 +73,7 @@ var pluginMap = map[string]bitbot.NamedTrigger{ "markovTrainer": bitbot.MarkovTrainerTrigger, "epeen": bitbot.EpeenTrigger, "ipinfo": bitbot.IPinfoTrigger, - "paste": bitbot.PasteTrigger, + "paste": bitbot.PasteTrigger, "urbd": bitbot.UrbanDictionaryTrigger, }