github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/cmd/wnode/main.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 // This is a simple Whisper node. It could be used as a stand-alone bootstrap node. 13 // Also, could be used for different test and diagnostics purposes. 14 15 package main 16 17 import ( 18 "bufio" 19 "crypto/ecdsa" 20 "crypto/sha512" 21 "encoding/binary" 22 "encoding/hex" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "time" 31 32 "github.com/Sberex/go-sberex/cmd/utils" 33 "github.com/Sberex/go-sberex/common" 34 "github.com/Sberex/go-sberex/console" 35 "github.com/Sberex/go-sberex/crypto" 36 "github.com/Sberex/go-sberex/log" 37 "github.com/Sberex/go-sberex/p2p" 38 "github.com/Sberex/go-sberex/p2p/discover" 39 "github.com/Sberex/go-sberex/p2p/nat" 40 "github.com/Sberex/go-sberex/whisper/mailserver" 41 whisper "github.com/Sberex/go-sberex/whisper/whisperv6" 42 "golang.org/x/crypto/pbkdf2" 43 ) 44 45 const quitCommand = "~Q" 46 47 // singletons 48 var ( 49 server *p2p.Server 50 shh *whisper.Whisper 51 done chan struct{} 52 mailServer mailserver.WMailServer 53 54 input = bufio.NewReader(os.Stdin) 55 ) 56 57 // encryption 58 var ( 59 symKey []byte 60 pub *ecdsa.PublicKey 61 asymKey *ecdsa.PrivateKey 62 nodeid *ecdsa.PrivateKey 63 topic whisper.TopicType 64 65 asymKeyID string 66 asymFilterID string 67 symFilterID string 68 symPass string 69 msPassword string 70 ) 71 72 // cmd arguments 73 var ( 74 bootstrapMode = flag.Bool("standalone", false, "boostrap node: don't actively connect to peers, wait for incoming connections") 75 forwarderMode = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither send nor decrypt messages") 76 mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand") 77 requestMail = flag.Bool("mailclient", false, "request expired messages from the bootstrap server") 78 asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption") 79 generateKey = flag.Bool("generatekey", false, "generate and show the private key") 80 fileExMode = flag.Bool("fileexchange", false, "file exchange mode") 81 testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics") 82 echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics") 83 84 argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level") 85 argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") 86 argWorkTime = flag.Uint("work", 5, "work time in seconds") 87 argMaxSize = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message") 88 argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") 89 argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") 90 91 argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") 92 argPub = flag.String("pub", "", "public key for asymmetric encryption") 93 argDBPath = flag.String("dbpath", "", "path to the server's DB directory") 94 argIDFile = flag.String("idfile", "", "file name with node id (private key)") 95 argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") 96 argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") 97 argSaveDir = flag.String("savedir", "", "directory where incoming messages will be saved as files") 98 ) 99 100 func main() { 101 processArgs() 102 initialize() 103 run() 104 } 105 106 func processArgs() { 107 flag.Parse() 108 109 if len(*argIDFile) > 0 { 110 var err error 111 nodeid, err = crypto.LoadECDSA(*argIDFile) 112 if err != nil { 113 utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err) 114 } 115 } 116 117 const enodePrefix = "enode://" 118 if len(*argEnode) > 0 { 119 if (*argEnode)[:len(enodePrefix)] != enodePrefix { 120 *argEnode = enodePrefix + *argEnode 121 } 122 } 123 124 if len(*argTopic) > 0 { 125 x, err := hex.DecodeString(*argTopic) 126 if err != nil { 127 utils.Fatalf("Failed to parse the topic: %s", err) 128 } 129 topic = whisper.BytesToTopic(x) 130 } 131 132 if *asymmetricMode && len(*argPub) > 0 { 133 pub = crypto.ToECDSAPub(common.FromHex(*argPub)) 134 if !isKeyValid(pub) { 135 utils.Fatalf("invalid public key") 136 } 137 } 138 139 if len(*argSaveDir) > 0 { 140 if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) { 141 utils.Fatalf("Download directory '%s' does not exist", *argSaveDir) 142 } 143 } else if *fileExMode { 144 utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode") 145 } 146 147 if *echoMode { 148 echo() 149 } 150 } 151 152 func echo() { 153 fmt.Printf("ttl = %d \n", *argTTL) 154 fmt.Printf("workTime = %d \n", *argWorkTime) 155 fmt.Printf("pow = %f \n", *argPoW) 156 fmt.Printf("mspow = %f \n", *argServerPoW) 157 fmt.Printf("ip = %s \n", *argIP) 158 fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub))) 159 fmt.Printf("idfile = %s \n", *argIDFile) 160 fmt.Printf("dbpath = %s \n", *argDBPath) 161 fmt.Printf("boot = %s \n", *argEnode) 162 } 163 164 func initialize() { 165 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) 166 167 done = make(chan struct{}) 168 var peers []*discover.Node 169 var err error 170 171 if *generateKey { 172 key, err := crypto.GenerateKey() 173 if err != nil { 174 utils.Fatalf("Failed to generate private key: %s", err) 175 } 176 k := hex.EncodeToString(crypto.FromECDSA(key)) 177 fmt.Printf("Random private key: %s \n", k) 178 os.Exit(0) 179 } 180 181 if *testMode { 182 symPass = "wwww" // ascii code: 0x77777777 183 msPassword = "wwww" 184 } 185 186 if *bootstrapMode { 187 if len(*argIP) == 0 { 188 argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ") 189 } 190 } else { 191 if len(*argEnode) == 0 { 192 argEnode = scanLineA("Please enter the peer's enode: ") 193 } 194 peer := discover.MustParseNode(*argEnode) 195 peers = append(peers, peer) 196 } 197 198 cfg := &whisper.Config{ 199 MaxMessageSize: uint32(*argMaxSize), 200 MinimumAcceptedPOW: *argPoW, 201 } 202 203 if *mailServerMode { 204 if len(msPassword) == 0 { 205 msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") 206 if err != nil { 207 utils.Fatalf("Failed to read Mail Server password: %s", err) 208 } 209 } 210 211 shh = whisper.New(cfg) 212 shh.RegisterServer(&mailServer) 213 mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW) 214 } else { 215 shh = whisper.New(cfg) 216 } 217 218 if *argPoW != whisper.DefaultMinimumPoW { 219 err := shh.SetMinimumPoW(*argPoW) 220 if err != nil { 221 utils.Fatalf("Failed to set PoW: %s", err) 222 } 223 } 224 225 if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize { 226 err := shh.SetMaxMessageSize(uint32(*argMaxSize)) 227 if err != nil { 228 utils.Fatalf("Failed to set max message size: %s", err) 229 } 230 } 231 232 asymKeyID, err = shh.NewKeyPair() 233 if err != nil { 234 utils.Fatalf("Failed to generate a new key pair: %s", err) 235 } 236 237 asymKey, err = shh.GetPrivateKey(asymKeyID) 238 if err != nil { 239 utils.Fatalf("Failed to retrieve a new key pair: %s", err) 240 } 241 242 if nodeid == nil { 243 tmpID, err := shh.NewKeyPair() 244 if err != nil { 245 utils.Fatalf("Failed to generate a new key pair: %s", err) 246 } 247 248 nodeid, err = shh.GetPrivateKey(tmpID) 249 if err != nil { 250 utils.Fatalf("Failed to retrieve a new key pair: %s", err) 251 } 252 } 253 254 maxPeers := 80 255 if *bootstrapMode { 256 maxPeers = 800 257 } 258 259 server = &p2p.Server{ 260 Config: p2p.Config{ 261 PrivateKey: nodeid, 262 MaxPeers: maxPeers, 263 Name: common.MakeName("wnode", "6.0"), 264 Protocols: shh.Protocols(), 265 ListenAddr: *argIP, 266 NAT: nat.Any(), 267 BootstrapNodes: peers, 268 StaticNodes: peers, 269 TrustedNodes: peers, 270 }, 271 } 272 } 273 274 func startServer() { 275 err := server.Start() 276 if err != nil { 277 utils.Fatalf("Failed to start Whisper peer: %s.", err) 278 } 279 280 fmt.Printf("my public key: %s \n", common.ToHex(crypto.FromECDSAPub(&asymKey.PublicKey))) 281 fmt.Println(server.NodeInfo().Enode) 282 283 if *bootstrapMode { 284 configureNode() 285 fmt.Println("Bootstrap Whisper node started") 286 } else { 287 fmt.Println("Whisper node started") 288 // first see if we can establish connection, then ask for user input 289 waitForConnection(true) 290 configureNode() 291 } 292 293 if !*forwarderMode { 294 fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand) 295 } 296 } 297 298 func isKeyValid(k *ecdsa.PublicKey) bool { 299 return k.X != nil && k.Y != nil 300 } 301 302 func configureNode() { 303 var err error 304 var p2pAccept bool 305 306 if *forwarderMode { 307 return 308 } 309 310 if *asymmetricMode { 311 if len(*argPub) == 0 { 312 s := scanLine("Please enter the peer's public key: ") 313 b := common.FromHex(s) 314 if b == nil { 315 utils.Fatalf("Error: can not convert hexadecimal string") 316 } 317 pub = crypto.ToECDSAPub(b) 318 if !isKeyValid(pub) { 319 utils.Fatalf("Error: invalid public key") 320 } 321 } 322 } 323 324 if *requestMail { 325 p2pAccept = true 326 if len(msPassword) == 0 { 327 msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") 328 if err != nil { 329 utils.Fatalf("Failed to read Mail Server password: %s", err) 330 } 331 } 332 } 333 334 if !*asymmetricMode && !*forwarderMode { 335 if len(symPass) == 0 { 336 symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ") 337 if err != nil { 338 utils.Fatalf("Failed to read passphrase: %v", err) 339 } 340 } 341 342 symKeyID, err := shh.AddSymKeyFromPassword(symPass) 343 if err != nil { 344 utils.Fatalf("Failed to create symmetric key: %s", err) 345 } 346 symKey, err = shh.GetSymKey(symKeyID) 347 if err != nil { 348 utils.Fatalf("Failed to save symmetric key: %s", err) 349 } 350 if len(*argTopic) == 0 { 351 generateTopic([]byte(symPass)) 352 } 353 354 fmt.Printf("Filter is configured for the topic: %x \n", topic) 355 } 356 357 if *mailServerMode { 358 if len(*argDBPath) == 0 { 359 argDBPath = scanLineA("Please enter the path to DB file: ") 360 } 361 } 362 363 symFilter := whisper.Filter{ 364 KeySym: symKey, 365 Topics: [][]byte{topic[:]}, 366 AllowP2P: p2pAccept, 367 } 368 symFilterID, err = shh.Subscribe(&symFilter) 369 if err != nil { 370 utils.Fatalf("Failed to install filter: %s", err) 371 } 372 373 asymFilter := whisper.Filter{ 374 KeyAsym: asymKey, 375 Topics: [][]byte{topic[:]}, 376 AllowP2P: p2pAccept, 377 } 378 asymFilterID, err = shh.Subscribe(&asymFilter) 379 if err != nil { 380 utils.Fatalf("Failed to install filter: %s", err) 381 } 382 } 383 384 func generateTopic(password []byte) { 385 x := pbkdf2.Key(password, password, 4096, 128, sha512.New) 386 for i := 0; i < len(x); i++ { 387 topic[i%whisper.TopicLength] ^= x[i] 388 } 389 } 390 391 func waitForConnection(timeout bool) { 392 var cnt int 393 var connected bool 394 for !connected { 395 time.Sleep(time.Millisecond * 50) 396 connected = server.PeerCount() > 0 397 if timeout { 398 cnt++ 399 if cnt > 1000 { 400 utils.Fatalf("Timeout expired, failed to connect") 401 } 402 } 403 } 404 405 fmt.Println("Connected to peer.") 406 } 407 408 func run() { 409 defer mailServer.Close() 410 startServer() 411 defer server.Stop() 412 shh.Start(nil) 413 defer shh.Stop() 414 415 if !*forwarderMode { 416 go messageLoop() 417 } 418 419 if *requestMail { 420 requestExpiredMessagesLoop() 421 } else if *fileExMode { 422 sendFilesLoop() 423 } else { 424 sendLoop() 425 } 426 } 427 428 func sendLoop() { 429 for { 430 s := scanLine("") 431 if s == quitCommand { 432 fmt.Println("Quit command received") 433 close(done) 434 break 435 } 436 sendMsg([]byte(s)) 437 438 if *asymmetricMode { 439 // print your own message for convenience, 440 // because in asymmetric mode it is impossible to decrypt it 441 timestamp := time.Now().Unix() 442 from := crypto.PubkeyToAddress(asymKey.PublicKey) 443 fmt.Printf("\n%d <%x>: %s\n", timestamp, from, s) 444 } 445 } 446 } 447 448 func sendFilesLoop() { 449 for { 450 s := scanLine("") 451 if s == quitCommand { 452 fmt.Println("Quit command received") 453 close(done) 454 break 455 } 456 b, err := ioutil.ReadFile(s) 457 if err != nil { 458 fmt.Printf(">>> Error: %s \n", err) 459 continue 460 } else { 461 h := sendMsg(b) 462 if (h == common.Hash{}) { 463 fmt.Printf(">>> Error: message was not sent \n") 464 } else { 465 timestamp := time.Now().Unix() 466 from := crypto.PubkeyToAddress(asymKey.PublicKey) 467 fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h) 468 } 469 } 470 } 471 } 472 473 func scanLine(prompt string) string { 474 if len(prompt) > 0 { 475 fmt.Print(prompt) 476 } 477 txt, err := input.ReadString('\n') 478 if err != nil { 479 utils.Fatalf("input error: %s", err) 480 } 481 txt = strings.TrimRight(txt, "\n\r") 482 return txt 483 } 484 485 func scanLineA(prompt string) *string { 486 s := scanLine(prompt) 487 return &s 488 } 489 490 func scanUint(prompt string) uint32 { 491 s := scanLine(prompt) 492 i, err := strconv.Atoi(s) 493 if err != nil { 494 utils.Fatalf("Fail to parse the lower time limit: %s", err) 495 } 496 return uint32(i) 497 } 498 499 func sendMsg(payload []byte) common.Hash { 500 params := whisper.MessageParams{ 501 Src: asymKey, 502 Dst: pub, 503 KeySym: symKey, 504 Payload: payload, 505 Topic: topic, 506 TTL: uint32(*argTTL), 507 PoW: *argPoW, 508 WorkTime: uint32(*argWorkTime), 509 } 510 511 msg, err := whisper.NewSentMessage(¶ms) 512 if err != nil { 513 utils.Fatalf("failed to create new message: %s", err) 514 } 515 envelope, err := msg.Wrap(¶ms) 516 if err != nil { 517 fmt.Printf("failed to seal message: %v \n", err) 518 return common.Hash{} 519 } 520 521 err = shh.Send(envelope) 522 if err != nil { 523 fmt.Printf("failed to send message: %v \n", err) 524 return common.Hash{} 525 } 526 527 return envelope.Hash() 528 } 529 530 func messageLoop() { 531 sf := shh.GetFilter(symFilterID) 532 if sf == nil { 533 utils.Fatalf("symmetric filter is not installed") 534 } 535 536 af := shh.GetFilter(asymFilterID) 537 if af == nil { 538 utils.Fatalf("asymmetric filter is not installed") 539 } 540 541 ticker := time.NewTicker(time.Millisecond * 50) 542 543 for { 544 select { 545 case <-ticker.C: 546 messages := sf.Retrieve() 547 for _, msg := range messages { 548 if *fileExMode || len(msg.Payload) > 2048 { 549 writeMessageToFile(*argSaveDir, msg) 550 } else { 551 printMessageInfo(msg) 552 } 553 } 554 555 messages = af.Retrieve() 556 for _, msg := range messages { 557 if *fileExMode || len(msg.Payload) > 2048 { 558 writeMessageToFile(*argSaveDir, msg) 559 } else { 560 printMessageInfo(msg) 561 } 562 } 563 case <-done: 564 return 565 } 566 } 567 } 568 569 func printMessageInfo(msg *whisper.ReceivedMessage) { 570 timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics 571 text := string(msg.Payload) 572 573 var address common.Address 574 if msg.Src != nil { 575 address = crypto.PubkeyToAddress(*msg.Src) 576 } 577 578 if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { 579 fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself 580 } else { 581 fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer 582 } 583 } 584 585 func writeMessageToFile(dir string, msg *whisper.ReceivedMessage) { 586 timestamp := fmt.Sprintf("%d", msg.Sent) 587 name := fmt.Sprintf("%x", msg.EnvelopeHash) 588 589 var address common.Address 590 if msg.Src != nil { 591 address = crypto.PubkeyToAddress(*msg.Src) 592 } 593 594 if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { 595 // message from myself: don't save, only report 596 fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name) 597 } else if len(dir) > 0 { 598 fullpath := filepath.Join(dir, name) 599 err := ioutil.WriteFile(fullpath, msg.Payload, 0644) 600 if err != nil { 601 fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err) 602 } else { 603 fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(msg.Payload)) 604 } 605 } else { 606 fmt.Printf("\n%s {%x}: big message received (%d bytes), but not saved: %s\n", timestamp, address, len(msg.Payload), name) 607 } 608 } 609 610 func requestExpiredMessagesLoop() { 611 var key, peerID []byte 612 var timeLow, timeUpp uint32 613 var t string 614 var xt, empty whisper.TopicType 615 616 keyID, err := shh.AddSymKeyFromPassword(msPassword) 617 if err != nil { 618 utils.Fatalf("Failed to create symmetric key for mail request: %s", err) 619 } 620 key, err = shh.GetSymKey(keyID) 621 if err != nil { 622 utils.Fatalf("Failed to save symmetric key for mail request: %s", err) 623 } 624 peerID = extractIDFromEnode(*argEnode) 625 shh.AllowP2PMessagesFromPeer(peerID) 626 627 for { 628 timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") 629 timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ") 630 t = scanLine("Please enter the topic (hexadecimal): ") 631 if len(t) >= whisper.TopicLength*2 { 632 x, err := hex.DecodeString(t) 633 if err != nil { 634 utils.Fatalf("Failed to parse the topic: %s", err) 635 } 636 xt = whisper.BytesToTopic(x) 637 } 638 if timeUpp == 0 { 639 timeUpp = 0xFFFFFFFF 640 } 641 642 data := make([]byte, 8+whisper.TopicLength) 643 binary.BigEndian.PutUint32(data, timeLow) 644 binary.BigEndian.PutUint32(data[4:], timeUpp) 645 copy(data[8:], xt[:]) 646 if xt == empty { 647 data = data[:8] 648 } 649 650 var params whisper.MessageParams 651 params.PoW = *argServerPoW 652 params.Payload = data 653 params.KeySym = key 654 params.Src = asymKey 655 params.WorkTime = 5 656 657 msg, err := whisper.NewSentMessage(¶ms) 658 if err != nil { 659 utils.Fatalf("failed to create new message: %s", err) 660 } 661 env, err := msg.Wrap(¶ms) 662 if err != nil { 663 utils.Fatalf("Wrap failed: %s", err) 664 } 665 666 err = shh.RequestHistoricMessages(peerID, env) 667 if err != nil { 668 utils.Fatalf("Failed to send P2P message: %s", err) 669 } 670 671 time.Sleep(time.Second * 5) 672 } 673 } 674 675 func extractIDFromEnode(s string) []byte { 676 n, err := discover.ParseNode(s) 677 if err != nil { 678 utils.Fatalf("Failed to parse enode: %s", err) 679 } 680 return n.ID[:] 681 }