From 202ac23df2efd7e58ce879576d7743e2e1b9d370 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 16 Jan 2017 23:00:28 +0100 Subject: [PATCH] whisper: mail server introduced --- cmd/wnode/mailserver.go | 168 +++++++++++++++++++++++++++++++++++ cmd/wnode/main.go | 92 ++++++++++--------- whisper/shhapi/api.go | 12 +-- whisper/whisperv5/doc.go | 2 +- whisper/whisperv5/whisper.go | 24 +++-- 5 files changed, 235 insertions(+), 63 deletions(-) create mode 100644 cmd/wnode/mailserver.go diff --git a/cmd/wnode/mailserver.go b/cmd/wnode/mailserver.go new file mode 100644 index 0000000000..6bfd7b6090 --- /dev/null +++ b/cmd/wnode/mailserver.go @@ -0,0 +1,168 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "encoding/binary" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/util" +) + +type WMailServer struct { + db *leveldb.DB + w *whisper.Whisper + pow float64 + key []byte +} + +type DBKey struct { + timestamp uint32 + hash common.Hash + raw []byte +} + +func NewDbKey(t uint32, h common.Hash) *DBKey { + const sz = common.HashLength + sizeOfInt + var k DBKey + k.timestamp = t + k.hash = h + k.raw = make([]byte, sz) + binary.BigEndian.PutUint32(k.raw, k.timestamp) + copy(k.raw[sizeOfInt:], k.hash[:]) + return &k +} + +func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) { + var err error + if len(path) == 0 { + utils.Fatalf("DB file is not specified") + } + + if len(password) == 0 { + utils.Fatalf("Password is not specified for MailServer") + } + + s.db, err = leveldb.OpenFile(path, nil) + if err != nil { + utils.Fatalf("Failed to open DB file: %s", err) + } + + s.w = shh + s.pow = pow + + err = s.w.AddSymKey(mailServerKeyName, []byte(password)) + if err != nil { + utils.Fatalf("Failed to create symmetric key for MailServer: %s", err) + } + s.key = s.w.GetSymKey(mailServerKeyName) +} + +func (s *WMailServer) Close() { + if s.db != nil { + s.db.Close() + } +} + +func (s *WMailServer) Archive(env *whisper.Envelope) { + key := NewDbKey(env.Expiry-env.TTL, env.Hash()) + rawEnvelope, err := rlp.EncodeToBytes(env) + if err != nil { + glog.V(logger.Error).Infof("rlp.EncodeToBytes failed: %s", err) + } else { + err = s.db.Put(key.raw, rawEnvelope, nil) + if err != nil { + glog.V(logger.Error).Infof("Writing to DB failed: %s", err) + } + } +} + +func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) { + ok, lower, upper, topic := s.validate(peer, request) + if !ok { + return + } + + var err error + var zero common.Hash + var empty whisper.TopicType + kl := NewDbKey(lower, zero) + ku := NewDbKey(upper, zero) + i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil) + defer i.Release() + + for i.Next() { + var envelope whisper.Envelope + err = envelope.DecodeBytes(i.Value()) + if err != nil { + glog.V(logger.Error).Infof("RLP decoding failed: %s", err) + } + + if topic == empty || envelope.Topic == topic { + err = s.w.SendP2PDirect(peer, &envelope) + if err != nil { + glog.V(logger.Error).Infof("Failed to send direct message to peer: %s", err) + return + } + } + } + + err = i.Error() + if err != nil { + glog.V(logger.Error).Infof("Level DB iterator error: %s", err) + } +} + +func (s *WMailServer) validate(peer *whisper.Peer, request *whisper.Envelope) (bool, uint32, uint32, whisper.TopicType) { + var topic whisper.TopicType + if s.pow > 0.0 && request.PoW() < s.pow { + return false, 0, 0, topic + } + + f := whisper.Filter{KeySym: s.key} + decrypted := request.Open(&f) + if decrypted == nil { + glog.V(logger.Warn).Infof("Failed to decrypt p2p request") + return false, 0, 0, topic + } + + if len(decrypted.Payload) < sizeOfInt*2 { + glog.V(logger.Warn).Infof("Undersized p2p request") + return false, 0, 0, topic + } + + if bytes.Equal(peer.ID(), decrypted.Signature) { + glog.V(logger.Warn).Infof("Wrong signature of p2p request") + return false, 0, 0, topic + } + + lower := binary.BigEndian.Uint32(decrypted.Payload[:sizeOfInt]) + upper := binary.BigEndian.Uint32(decrypted.Payload[sizeOfInt : sizeOfInt*2]) + + if len(decrypted.Payload) == sizeOfInt*2+whisper.TopicLength { + topic = whisper.BytesToTopic(decrypted.Payload[sizeOfInt*2 : sizeOfInt*2+whisper.TopicLength]) + } + + return true, lower, upper, topic +} diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index d83df46260..4e4556b040 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -43,7 +43,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rlp" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" "golang.org/x/crypto/pbkdf2" ) @@ -53,7 +52,7 @@ const sizeOfInt = 4 var ( quitCommand = "~Q" enodePrefix = "enode://" - mailServerKeyName = "fd8c027e29852ef" + mailServerKeyName = "958e04ab302fb36ad2616a352cbac79dfa08f43d325d0833e07061a9ef8c383d" ) // singletons @@ -81,7 +80,7 @@ var ( echoMode = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics") bootstrapMode = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections") forwarderMode = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages") - mailServerMode = flag.Bool("m", false, "mail server mode: delivers expired messages on demand") + mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand") requestMail = flag.Bool("r", false, "request expired messages from the bootstrap server") asymmetricMode = flag.Bool("a", false, "use asymmetric encryption") testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics") @@ -150,7 +149,6 @@ func echo() { fmt.Printf("mspow = %f \n", *argServerPoW) fmt.Printf("ip = %s \n", *argIP) fmt.Printf("salt = %s \n", *argSalt) - fmt.Printf("topic = %x \n", topic) fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub))) fmt.Printf("idfile = %s \n", *argIDFile) fmt.Printf("dbpath = %s \n", *argDBPath) @@ -175,7 +173,7 @@ func initialize() { if *bootstrapMode { if len(*argIP) == 0 { - argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30303): ") + argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ") } } else { if len(*argEnode) == 0 { @@ -185,12 +183,13 @@ func initialize() { peers = append(peers, peer) } - if *mailServerMode && len(msPassword) == 0 { - msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") - if err != nil { - utils.Fatalf("Failed to read Mail Server password: %s", err) + if *mailServerMode { + if len(msPassword) == 0 { + msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") + if err != nil { + utils.Fatalf("Failed to read Mail Server password: %s", err) + } } - shh = whisper.NewWhisper(&mailServer) mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW) } else { @@ -247,16 +246,29 @@ func isKeyValid(k *ecdsa.PublicKey) bool { func configureNode() { var err error + var p2pAccept bool if *forwarderMode { return } - if *asymmetricMode && len(*argPub) == 0 { - s := scanLine("Please enter the peer's public key: ") - pub = crypto.ToECDSAPub(common.FromHex(s)) - if !isKeyValid(pub) { - utils.Fatalf("Error: invalid public key") + if *asymmetricMode { + if len(*argPub) == 0 { + s := scanLine("Please enter the peer's public key: ") + pub = crypto.ToECDSAPub(common.FromHex(s)) + if !isKeyValid(pub) { + utils.Fatalf("Error: invalid public key") + } + } + } + + if *requestMail { + p2pAccept = true + if len(msPassword) == 0 { + msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") + if err != nil { + utils.Fatalf("Failed to read Mail Server password: %s", err) + } } } @@ -283,15 +295,14 @@ func configureNode() { } } - if *requestMail && len(msPassword) == 0 { - msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") - if err != nil { - utils.Fatalf("Failed to read Mail Server password: %s", err) - } + filter := whisper.Filter{ + KeySym: symKey, + KeyAsym: asymKey, + Topics: []whisper.TopicType{topic}, + AcceptP2P: p2pAccept, } - - filter := &whisper.Filter{KeySym: symKey, KeyAsym: asymKey, Topics: []whisper.TopicType{topic}} - filterID = shh.Watch(filter) + filterID = shh.Watch(&filter) + fmt.Printf("Filter is configured for the topic: %x \n", topic) } func generateTopic(password, salt []byte) { @@ -451,7 +462,10 @@ func printMessageInfo(msg *whisper.ReceivedMessage) { func requestExpiredMessagesLoop() { var key, peerID []byte - var timeLow, timeUpp, t uint32 + var timeLow, timeUpp uint32 + var t string + var xt, empty whisper.TopicType + err := shh.AddSymKey(mailServerKeyName, []byte(msPassword)) if err != nil { utils.Fatalf("Failed to create symmetric key for mail request: %s", err) @@ -463,16 +477,23 @@ func requestExpiredMessagesLoop() { for { timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ") - t = scanUint("Please enter the topic: ") + t = scanLine("Please enter the topic (hexadecimal): ") + if len(t) >= whisper.TopicLength*2 { + x, err := hex.DecodeString(t) + if err != nil { + utils.Fatalf("Failed to parse the topic: %s", err) + } + xt = whisper.BytesToTopic(x) + } if timeUpp == 0 { timeUpp = 0xFFFFFFFF } - data := make([]byte, sizeOfInt*3) + data := make([]byte, sizeOfInt*2+whisper.TopicLength) binary.BigEndian.PutUint32(data, timeLow) binary.BigEndian.PutUint32(data[sizeOfInt:], timeUpp) - binary.BigEndian.PutUint32(data[sizeOfInt*2:], t) - if t == 0 { + copy(data[sizeOfInt*2:], xt[:]) + if xt == empty { data = data[:sizeOfInt*2] } @@ -489,12 +510,7 @@ func requestExpiredMessagesLoop() { utils.Fatalf("Wrap failed: %s", err) } - encoded, err := rlp.EncodeToBytes(env) - if err != nil { - utils.Fatalf("RLP encoding failed: %s", err) - } - - err = shh.RequestHistoricMessages(peerID, encoded) + err = shh.RequestHistoricMessages(peerID, env) if err != nil { utils.Fatalf("Failed to send P2P message: %s", err) } @@ -526,11 +542,3 @@ func extractIdFromEnode(s string) []byte { return b } - -// this is a temporary stub, will be expanded later -type WMailServer struct{} - -func (s *WMailServer) Archive(env *whisper.Envelope) {} -func (s *WMailServer) DeliverMail(peer *whisper.Peer, data []byte) {} -func (s *WMailServer) Init(w *whisper.Whisper, path string, password string, pow float64) {} -func (s *WMailServer) Close() {} diff --git a/whisper/shhapi/api.go b/whisper/shhapi/api.go index 24d54b6533..a7bcb37d60 100644 --- a/whisper/shhapi/api.go +++ b/whisper/shhapi/api.go @@ -93,12 +93,12 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { // data contains parameters (time frame, payment details, etc.), required // by the remote email-like server. Whisper is not aware about the data format, // it will just forward the raw data to the server. -func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error { - if api.whisper == nil { - return whisperOffLineErr - } - return api.whisper.RequestHistoricMessages(peerID, data) -} +//func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error { +// if api.whisper == nil { +// return whisperOffLineErr +// } +// return api.whisper.RequestHistoricMessages(peerID, data) +//} // HasIdentity checks if the whisper node is configured with the private key // of the specified public pair. diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index e2e255e9e1..7a4a98fd7f 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -83,5 +83,5 @@ func (e unknownVersionError) Error() string { // in order to bypass the expiry checks. type MailServer interface { Archive(env *Envelope) - DeliverMail(whisperPeer *Peer, data []byte) + DeliverMail(whisperPeer *Peer, request *Envelope) } diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 5641c7b9ad..e1f4e0a11e 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/pbkdf2" set "gopkg.in/fatih/set.v0" ) @@ -125,13 +124,13 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error { return nil } -func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error { +func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { p, err := w.getPeer(peerID) if err != nil { return err } p.trusted = true - return p2p.Send(p.ws, p2pRequestCode, data) + return p2p.Send(p.ws, p2pRequestCode, envelope) } func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { @@ -361,25 +360,22 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // therefore might not satisfy the PoW, expiry and other requirements. // these messages are only accepted from the trusted peer. if p.trusted { - var envelopes []*Envelope - if err := packet.Decode(&envelopes); err != nil { + var envelope Envelope + if err := packet.Decode(&envelope); err != nil { glog.V(logger.Warn).Infof("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err) return fmt.Errorf("garbage received (directMessage)") } - for _, envelope := range envelopes { - wh.postEvent(envelope, true) - } + wh.postEvent(&envelope, true) } case p2pRequestCode: // Must be processed if mail server is implemented. Otherwise ignore. if wh.mailServer != nil { - s := rlp.NewStream(packet.Payload, uint64(packet.Size)) - data, err := s.Bytes() - if err == nil { - wh.mailServer.DeliverMail(p, data) - } else { - glog.V(logger.Error).Infof("%v: bad requestHistoricMessages received: [%v]", p.peer, err) + var request Envelope + if err := packet.Decode(&request); err != nil { + glog.V(logger.Warn).Infof("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err) + return fmt.Errorf("garbage received (p2p request)") } + wh.mailServer.DeliverMail(p, &request) } default: // New message types might be implemented in the future versions of Whisper.