github.com/decred/politeia@v1.4.0/politeiad/cmd/politeia/politeia.go (about) 1 // Copyright (c) 2017-2020 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bufio" 9 "context" 10 "crypto/sha256" 11 "encoding/hex" 12 "flag" 13 "fmt" 14 "net/url" 15 "os" 16 "path/filepath" 17 "regexp" 18 "strconv" 19 "time" 20 21 "github.com/decred/dcrd/dcrutil/v3" 22 v1 "github.com/decred/politeia/politeiad/api/v1" 23 "github.com/decred/politeia/politeiad/api/v1/identity" 24 "github.com/decred/politeia/politeiad/api/v1/mime" 25 v2 "github.com/decred/politeia/politeiad/api/v2" 26 pdclient "github.com/decred/politeia/politeiad/client" 27 "github.com/decred/politeia/util" 28 ) 29 30 const allowInteractive = "i-know-this-is-a-bad-idea" 31 32 var ( 33 regexMD = regexp.MustCompile(`^metadata:`) 34 regexMDID = regexp.MustCompile(`[a-z]{1,16}[\d]{1,2}:`) 35 regexMDPluginID = regexp.MustCompile(`[a-z]{1,16}`) 36 regexMDStreamID = regexp.MustCompile(`[\d]{1,2}`) 37 regexAppendMD = regexp.MustCompile(`^appendmetadata:`) 38 regexOverwriteMD = regexp.MustCompile(`^overwritemetadata:`) 39 regexFileAdd = regexp.MustCompile(`^add:`) 40 regexFileDel = regexp.MustCompile(`^del:`) 41 regexToken = regexp.MustCompile(`^token:`) 42 43 defaultHomeDir = dcrutil.AppDataDir("politeia", false) 44 defaultIdentityFilename = "identity.json" 45 46 defaultPDAppDir = dcrutil.AppDataDir("politeiad", false) 47 defaultRPCCertFile = filepath.Join(defaultPDAppDir, "https.cert") 48 49 identityFilename = flag.String("id", filepath.Join(defaultHomeDir, 50 defaultIdentityFilename), "remote server identity file") 51 testnet = flag.Bool("testnet", false, "Use testnet port") 52 verbose = flag.Bool("v", false, "Verbose") 53 rpcuser = flag.String("rpcuser", "", "RPC user name for privileged calls") 54 rpcpass = flag.String("rpcpass", "", "RPC password for privileged calls") 55 rpchost = flag.String("rpchost", "", "RPC host") 56 rpccert = flag.String("rpccert", "", "RPC certificate") 57 interactive = flag.String("interactive", "", "Set to "+ 58 allowInteractive+" to to turn off interactive mode during "+ 59 "identity fetch") 60 ) 61 62 const availableCmds = ` 63 Available commands: 64 identity Get server identity 65 new Submit new record 66 Args: [metadata:<id>:metadataJSON]... <filepaths>... 67 verify Verify record was accepted 68 Args: <serverkey> <token> <signature> <filepaths>... 69 edit Edit record 70 Args: [actionMetadata:<id>:metadataJSON]... 71 <actionfile:filename>... token:<token> 72 editmetadata Edit record metdata 73 Args: [actionMetadata:<id>:metadataJSON]... token:<token> 74 setstatus Set record status 75 Args: <token> <status> 76 record Get a record 77 Args: <token> 78 inventory Get the record inventory 79 Args (optional): <state> <status> <page> 80 81 Metadata actions: appendmetadata, overwritemetadata 82 File actions: add, del 83 Record statuses: public, censored, or archived 84 85 A metadata <id> consists of the <pluginID><streamID>. Plugin IDs are strings 86 and stream IDs are uint32. Below are example metadata arguments where the 87 plugin ID is 'testid' and the stream ID is '1'. 88 89 Submit new metadata: 'metadata:testid1:{"foo":"bar"}' 90 Append metadata : 'appendmetadata:testid1:{"foo":"bar"}' 91 Overwrite metadata : 'overwritemetadata:testid1:{"foo":"bar"}' 92 93 ` 94 95 func usage() { 96 fmt.Fprintf(os.Stderr, "usage: politeia [flags] <action> [arguments]\n") 97 fmt.Fprintf(os.Stderr, " flags:\n") 98 flag.PrintDefaults() 99 fmt.Fprintf(os.Stderr, availableCmds) 100 } 101 102 func printRecord(header string, r v2.Record) { 103 // Pretty print record 104 status, ok := v2.RecordStatuses[r.Status] 105 if !ok { 106 status = v2.RecordStatuses[v2.RecordStatusInvalid] 107 } 108 fmt.Printf("%v:\n", header) 109 fmt.Printf(" Status : %v\n", status) 110 fmt.Printf(" Timestamp : %v\n", time.Unix(r.Timestamp, 0).UTC()) 111 fmt.Printf(" Version : %v\n", r.Version) 112 fmt.Printf(" Censorship record:\n") 113 fmt.Printf(" Merkle : %v\n", r.CensorshipRecord.Merkle) 114 fmt.Printf(" Token : %v\n", r.CensorshipRecord.Token) 115 fmt.Printf(" Signature: %v\n", r.CensorshipRecord.Signature) 116 for k, v := range r.Files { 117 fmt.Printf(" File (%02v) :\n", k) 118 fmt.Printf(" Name : %v\n", v.Name) 119 fmt.Printf(" MIME : %v\n", v.MIME) 120 fmt.Printf(" Digest : %v\n", v.Digest) 121 } 122 for _, v := range r.Metadata { 123 fmt.Printf(" Metadata stream %v %02v:\n", v.PluginID, v.StreamID) 124 fmt.Printf(" %v\n", v.Payload) 125 } 126 } 127 128 // parseMetadataIDs parses and returns the plugin ID and stream ID from a full 129 // metadata ID string. See the example below. 130 // 131 // Metadata ID string: "pluginid12:" 132 // Plugin ID: "plugindid" 133 // Stream ID: 12 134 func parseMetadataIDs(mdID string) (string, uint32, error) { 135 // Parse the plugin ID. This is the "pluginid" part of the 136 // "pluginid12:" metadata ID. 137 pluginID := regexMDPluginID.FindString(mdID) 138 139 // Parse the stream ID. This is the "12" part of the 140 // "pluginid12:" metadata ID. 141 streamID, err := strconv.ParseUint(regexMDStreamID.FindString(mdID), 142 10, 64) 143 if err != nil { 144 return "", 0, err 145 } 146 147 return pluginID, uint32(streamID), nil 148 } 149 150 // parseMetadata returns the metadata streams for all metadata flags. 151 func parseMetadata(flags []string) ([]v2.MetadataStream, error) { 152 md := make([]v2.MetadataStream, 0, len(flags)) 153 for _, v := range flags { 154 // Example metadata: 'metadata:pluginid12:{"moo":"lala"}' 155 156 // Parse metadata tag. This is the 'metadata:' part of the 157 // example metadata. 158 mdTag := regexMD.FindString(v) 159 if mdTag == "" { 160 // This is not metadata 161 continue 162 } 163 164 // Parse the full metatdata ID string. This is the "pluginid12:" 165 // part of the example metadata. 166 mdID := regexMDID.FindString(v) 167 168 // Parse the plugin ID and stream ID 169 pluginID, streamID, err := parseMetadataIDs(mdID) 170 if err != nil { 171 return nil, err 172 } 173 174 md = append(md, v2.MetadataStream{ 175 PluginID: pluginID, 176 StreamID: streamID, 177 Payload: v[len(mdTag)+len(mdID):], 178 }) 179 } 180 181 return md, nil 182 } 183 184 // parseMetadata returns the metadata streams for all appendmetadata flags. 185 func parseMetadataAppend(flags []string) ([]v2.MetadataStream, error) { 186 md := make([]v2.MetadataStream, 0, len(flags)) 187 for _, v := range flags { 188 // Example metadata: 'appendmetadata:pluginid12:{"moo":"lala"}' 189 190 // Parse append metadata tag. This is the 'appendmetadata:' part 191 // of the example metadata. 192 appendTag := regexAppendMD.FindString(v) 193 if appendTag == "" { 194 // This is not a metadata append 195 continue 196 } 197 198 // Parse the full metatdata ID string. This is the "pluginid12:" 199 // part of the example metadata. 200 mdID := regexMDID.FindString(v) 201 202 // Parse the plugin ID and stream ID 203 pluginID, streamID, err := parseMetadataIDs(mdID) 204 if err != nil { 205 return nil, err 206 } 207 208 md = append(md, v2.MetadataStream{ 209 PluginID: pluginID, 210 StreamID: streamID, 211 Payload: v[len(appendTag)+len(mdID):], 212 }) 213 } 214 215 return md, nil 216 } 217 218 // parseMetadata returns the metadata streams for all overwritemetadata flags. 219 func parseMetadataOverwrite(flags []string) ([]v2.MetadataStream, error) { 220 md := make([]v2.MetadataStream, 0, len(flags)) 221 for _, v := range flags { 222 // Example metadata: 'overwritemetadata:pluginid12:{"moo":"lala"}' 223 224 // Parse overwrite metadata tag. This is the 'overwritemetadata:' 225 // part of the example metadata. 226 overwriteTag := regexOverwriteMD.FindString(v) 227 if overwriteTag == "" { 228 // This is not a metadata overwrite 229 continue 230 } 231 232 // Parse the full metatdata ID string. This is the "pluginid12:" 233 // part of the example metadata. 234 mdID := regexMDID.FindString(v) 235 236 // Parse the plugin ID and stream ID 237 pluginID, streamID, err := parseMetadataIDs(mdID) 238 if err != nil { 239 return nil, err 240 } 241 242 md = append(md, v2.MetadataStream{ 243 PluginID: pluginID, 244 StreamID: streamID, 245 Payload: v[len(overwriteTag)+len(mdID):], 246 }) 247 } 248 249 return md, nil 250 } 251 252 // parseFiles returns the files for all filename flags. 253 func parseFiles(flags []string) ([]v2.File, error) { 254 // Parse file names from flags 255 filenames := make([]string, 0, len(flags)) 256 for _, v := range flags { 257 if regexMD.FindString(v) != "" { 258 // This is metadata, not a filename 259 continue 260 } 261 262 // This is a filename 263 filenames = append(filenames, v) 264 } 265 if len(filenames) == 0 { 266 return nil, fmt.Errorf("no filenames provided") 267 } 268 269 // Read files from disk 270 files := make([]v2.File, 0, len(filenames)) 271 for _, v := range filenames { 272 f, _, err := getFile(v) 273 if err != nil { 274 return nil, err 275 } 276 files = append(files, *f) 277 } 278 279 return files, nil 280 281 } 282 283 // parseFileAdds returns the files for all file add flags. 284 func parseFileAdds(flags []string) ([]v2.File, error) { 285 // Parse file names from flags 286 filenames := make([]string, 0, len(flags)) 287 for _, v := range flags { 288 fileAddTag := regexFileAdd.FindString(v) 289 if fileAddTag == "" { 290 // This is not a file add flag 291 continue 292 } 293 294 // This is a filename 295 filenames = append(filenames, v[len(fileAddTag):]) 296 } 297 298 // Read files from disk 299 files := make([]v2.File, 0, len(filenames)) 300 for _, v := range filenames { 301 f, _, err := getFile(v) 302 if err != nil { 303 return nil, err 304 } 305 files = append(files, *f) 306 } 307 308 return files, nil 309 } 310 311 // parseFileDels returns the filenames for all file del flags. 312 func parseFileDels(flags []string) []string { 313 // Parse file names from flags 314 filenames := make([]string, 0, len(flags)) 315 for _, v := range flags { 316 fileDelTag := regexFileDel.FindString(v) 317 if fileDelTag == "" { 318 // This is not a file del flag 319 continue 320 } 321 322 // This is a filename 323 filenames = append(filenames, v[len(fileDelTag):]) 324 } 325 return filenames 326 } 327 328 // parseToken returns the token from the flags. 329 func parseToken(flags []string) string { 330 var token string 331 for _, v := range flags { 332 tokenTag := regexToken.FindString(v) 333 if tokenTag == "" { 334 // This is not the token 335 continue 336 } 337 token = v[len(tokenTag):] 338 } 339 return token 340 } 341 342 // decodeToken decodes the provided token string into a byte slice. The token 343 // must be a full length politeiad v2 token. 344 func decodeToken(t string) ([]byte, error) { 345 return util.TokenDecode(util.TokenTypeTstore, t) 346 } 347 348 func convertStatus(s string) v2.RecordStatusT { 349 switch s { 350 case "unreviewed": 351 return v2.RecordStatusUnreviewed 352 case "public": 353 return v2.RecordStatusPublic 354 case "censored": 355 return v2.RecordStatusCensored 356 case "archived": 357 return v2.RecordStatusArchived 358 } 359 return v2.RecordStatusInvalid 360 } 361 362 func convertState(s string) v2.RecordStateT { 363 switch s { 364 case "unvetted": 365 return v2.RecordStateUnvetted 366 case "vetted": 367 return v2.RecordStateVetted 368 } 369 return v2.RecordStateInvalid 370 } 371 372 func getFile(filename string) (*v2.File, *[sha256.Size]byte, error) { 373 var err error 374 375 filename = util.CleanAndExpandPath(filename) 376 file := &v2.File{ 377 Name: filepath.Base(filename), 378 } 379 file.MIME, file.Digest, file.Payload, err = util.LoadFile(filename) 380 if err != nil { 381 return nil, nil, err 382 } 383 if !mime.MimeValid(file.MIME) { 384 return nil, nil, fmt.Errorf("unsupported mime type '%v' "+ 385 "for file '%v'", file.MIME, filename) 386 } 387 388 // Get digest 389 digest, err := hex.DecodeString(file.Digest) 390 if err != nil { 391 return nil, nil, err 392 } 393 394 // Store for merkle root verification later 395 var digest32 [sha256.Size]byte 396 copy(digest32[:], digest) 397 398 return file, &digest32, nil 399 } 400 401 // getIdentity retrieves the politeiad server identity, i.e. public key. 402 func getIdentity() error { 403 // Fetch remote identity 404 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, nil) 405 if err != nil { 406 return err 407 } 408 id, err := c.Identity(context.Background()) 409 if err != nil { 410 return err 411 } 412 413 rf := filepath.Join(defaultHomeDir, defaultIdentityFilename) 414 415 // Pretty print identity. 416 fmt.Printf("Key : %x\n", id.Key) 417 fmt.Printf("Fingerprint: %v\n", id.Fingerprint()) 418 419 // Ask user if we like this identity 420 if *interactive != allowInteractive { 421 fmt.Printf("\nSave to %v or ctrl-c to abort ", rf) 422 scanner := bufio.NewScanner(os.Stdin) 423 scanner.Scan() 424 if err = scanner.Err(); err != nil { 425 return err 426 } 427 if len(scanner.Text()) != 0 { 428 rf = scanner.Text() 429 } 430 } else { 431 fmt.Printf("Saving identity to %v\n", rf) 432 } 433 rf = util.CleanAndExpandPath(rf) 434 435 // Save identity 436 err = os.MkdirAll(filepath.Dir(rf), 0700) 437 if err != nil { 438 return err 439 } 440 err = id.SavePublicIdentity(rf) 441 if err != nil { 442 return err 443 } 444 fmt.Printf("Identity saved to: %v\n", rf) 445 446 return nil 447 } 448 449 // recordNew submits a new record to the politeiad v2 API. 450 func recordNew() error { 451 flags := flag.Args()[1:] // Chop off action. 452 453 // Parse metadata and files 454 metadata, err := parseMetadata(flags) 455 if err != nil { 456 return err 457 } 458 files, err := parseFiles(flags) 459 if err != nil { 460 return err 461 } 462 463 // Load server identity 464 pid, err := identity.LoadPublicIdentity(*identityFilename) 465 if err != nil { 466 return err 467 } 468 469 // Setup client 470 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 471 if err != nil { 472 return err 473 } 474 475 // Submit record 476 r, err := c.RecordNew(context.Background(), metadata, files) 477 if err != nil { 478 return err 479 } 480 481 if *verbose { 482 printRecord("Record submitted", *r) 483 fmt.Printf("Server public key: %v\n", pid.String()) 484 } 485 486 // Verify record 487 return pdclient.RecordVerify(*r, pid.String()) 488 } 489 490 // recordVerify verifies that a record was submitted by verifying the 491 // censorship record signature. 492 func recordVerify() error { 493 flags := flag.Args()[1:] // Chop off action. 494 if len(flags) < 3 { 495 return fmt.Errorf("arguments are missing") 496 } 497 498 // Unpack args 499 var ( 500 serverKey = flags[0] 501 token = flags[1] 502 signature = flags[2] 503 ) 504 505 // Parse files 506 files, err := parseFiles(flags[3:]) 507 if err != nil { 508 return err 509 } 510 if len(files) == 0 { 511 return fmt.Errorf("no files found") 512 } 513 514 // Calc merkle root of files 515 digests := make([]string, 0, len(files)) 516 for _, v := range files { 517 digests = append(digests, v.Digest) 518 } 519 mr, err := util.MerkleRoot(digests) 520 if err != nil { 521 return err 522 } 523 merkle := hex.EncodeToString(mr[:]) 524 525 // Load identity 526 pid, err := identity.PublicIdentityFromString(serverKey) 527 if err != nil { 528 return err 529 } 530 531 // Verify record 532 r := v2.Record{ 533 Files: files, 534 CensorshipRecord: v2.CensorshipRecord{ 535 Token: token, 536 Merkle: merkle, 537 Signature: signature, 538 }, 539 } 540 err = pdclient.RecordVerify(r, pid.String()) 541 if err != nil { 542 return err 543 } 544 545 fmt.Printf("Server key : %s\n", serverKey) 546 fmt.Printf("Token : %s\n", token) 547 fmt.Printf("Merkle root: %s\n", merkle) 548 fmt.Printf("Signature : %s\n\n", signature) 549 fmt.Println("Record successfully verified") 550 551 return nil 552 } 553 554 // recordEdit edits an existing record. 555 func recordEdit() error { 556 flags := flag.Args()[1:] // Chop off action. 557 558 // Parse args 559 mdAppend, err := parseMetadataAppend(flags) 560 if err != nil { 561 return err 562 } 563 mdOverwrite, err := parseMetadataOverwrite(flags) 564 if err != nil { 565 return err 566 } 567 fileAdds, err := parseFileAdds(flags) 568 if err != nil { 569 return err 570 } 571 fileDels := parseFileDels(flags) 572 token := parseToken(flags) 573 if token == "" { 574 return fmt.Errorf("must provide token") 575 } 576 577 // Load server identity 578 pid, err := identity.LoadPublicIdentity(*identityFilename) 579 if err != nil { 580 return err 581 } 582 583 // Setup client 584 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 585 if err != nil { 586 return err 587 } 588 589 // Edit record 590 r, err := c.RecordEdit(context.Background(), token, 591 mdAppend, mdOverwrite, fileAdds, fileDels) 592 if err != nil { 593 return err 594 } 595 596 if *verbose { 597 printRecord("Record updated", *r) 598 fmt.Printf("Server public key: %v\n", pid.String()) 599 } 600 601 // Verify record 602 return pdclient.RecordVerify(*r, pid.String()) 603 } 604 605 // recordEditMetadata edits the metadata of a record. 606 func recordEditMetadata() error { 607 flags := flag.Args()[1:] // Chop off action. 608 609 // Parse args 610 mdAppend, err := parseMetadataAppend(flags) 611 if err != nil { 612 return err 613 } 614 mdOverwrite, err := parseMetadataOverwrite(flags) 615 if err != nil { 616 return err 617 } 618 token := parseToken(flags) 619 if token == "" { 620 return fmt.Errorf("must provide token") 621 } 622 623 // Load server identity 624 pid, err := identity.LoadPublicIdentity(*identityFilename) 625 if err != nil { 626 return err 627 } 628 629 // Setup client 630 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 631 if err != nil { 632 return err 633 } 634 635 // Edit record metadata 636 r, err := c.RecordEditMetadata(context.Background(), 637 token, mdAppend, mdOverwrite) 638 if err != nil { 639 return err 640 } 641 642 if *verbose { 643 printRecord("Record metadata updated", *r) 644 fmt.Printf("Server public key: %v\n", pid.String()) 645 } 646 647 // Verify record 648 return pdclient.RecordVerify(*r, pid.String()) 649 } 650 651 // recordSetStatus sets the status of a record. 652 func recordSetStatus() error { 653 flags := flag.Args()[1:] 654 655 // Make sure we have the status and the censorship token 656 if len(flags) < 2 { 657 return fmt.Errorf("must at least provide status and " + 658 "censorship token") 659 } 660 661 // Validate censorship token 662 token := flags[0] 663 _, err := decodeToken(token) 664 if err != nil { 665 return err 666 } 667 668 // Validate status 669 status := convertStatus(flags[1]) 670 if status == v2.RecordStatusInvalid { 671 return fmt.Errorf("invalid status") 672 } 673 674 // Load server identity 675 pid, err := identity.LoadPublicIdentity(*identityFilename) 676 if err != nil { 677 return err 678 } 679 680 // Setup client 681 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 682 if err != nil { 683 return err 684 } 685 686 // Set record status 687 r, err := c.RecordSetStatus(context.Background(), 688 token, status, nil, nil) 689 if err != nil { 690 return err 691 } 692 693 if *verbose { 694 printRecord("Record status updated", *r) 695 fmt.Printf("Server public key: %v\n", pid.String()) 696 } 697 698 // Verify record 699 return pdclient.RecordVerify(*r, pid.String()) 700 } 701 702 // record retreives a record. 703 func record() error { 704 flags := flag.Args()[1:] // Chop off action. 705 706 // Make sure we have the censorship token 707 if len(flags) != 1 { 708 return fmt.Errorf("must provide one and only one censorship " + 709 "token") 710 } 711 712 // Validate censorship token 713 token := flags[0] 714 _, err := decodeToken(token) 715 if err != nil { 716 return err 717 } 718 719 // Load server identity 720 pid, err := identity.LoadPublicIdentity(*identityFilename) 721 if err != nil { 722 return err 723 } 724 725 // Setup client 726 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 727 if err != nil { 728 return err 729 } 730 731 // Set record status 732 reqs := []v2.RecordRequest{ 733 { 734 Token: token, 735 }, 736 } 737 records, err := c.Records(context.Background(), reqs) 738 if err != nil { 739 return err 740 } 741 r, ok := records[token] 742 if !ok { 743 return fmt.Errorf("record not found") 744 } 745 746 if *verbose { 747 printRecord("Record", r) 748 fmt.Printf("Server public key: %v\n", pid.String()) 749 } 750 751 // Verify record 752 return pdclient.RecordVerify(r, pid.String()) 753 } 754 755 // recordInventory retrieves the censorship record tokens of the records in 756 // the inventory, categorized by their record state and record status. 757 func recordInventory() error { 758 flags := flag.Args()[1:] // Chop off action. 759 760 // Either the state, status and page number must all be given or 761 // none should be given at all. 762 if len(flags) > 0 && len(flags) != 3 { 763 return fmt.Errorf("invalid number of arguments (%v); you can "+ 764 "either provide a state, status, and page number or you can "+ 765 "provide no arguments at all", len(flags)) 766 } 767 768 // Unpack args 769 var ( 770 state v2.RecordStateT 771 status v2.RecordStatusT 772 pageNumber uint32 773 ) 774 if len(flags) == 3 { 775 state = convertState(flags[0]) 776 status = convertStatus(flags[1]) 777 u, err := strconv.ParseUint(flags[2], 10, 64) 778 if err != nil { 779 return fmt.Errorf("unable to parse page number '%v': %v", 780 flags[2], err) 781 } 782 pageNumber = uint32(u) 783 } 784 785 // Load server identity 786 pid, err := identity.LoadPublicIdentity(*identityFilename) 787 if err != nil { 788 return err 789 } 790 791 // Setup client 792 c, err := pdclient.New(*rpchost, *rpccert, *rpcuser, *rpcpass, pid) 793 if err != nil { 794 return err 795 } 796 797 // Get inventory 798 ir, err := c.Inventory(context.Background(), state, status, pageNumber) 799 if err != nil { 800 return err 801 } 802 803 if *verbose { 804 if len(ir.Unvetted) > 0 { 805 fmt.Printf("Unvetted\n") 806 fmt.Printf("%v\n", util.FormatJSON(ir.Unvetted)) 807 } 808 if len(ir.Vetted) > 0 { 809 fmt.Printf("Vetted\n") 810 fmt.Printf("%v\n", util.FormatJSON(ir.Vetted)) 811 } 812 } 813 814 return nil 815 } 816 817 func _main() error { 818 flag.Usage = usage 819 flag.Parse() 820 if len(flag.Args()) == 0 { 821 usage() 822 return fmt.Errorf("must provide action") 823 } 824 825 // Setup RPC host 826 if *rpchost == "" { 827 if *testnet { 828 *rpchost = v1.DefaultTestnetHost 829 } else { 830 *rpchost = v1.DefaultMainnetHost 831 } 832 } 833 port := v1.DefaultMainnetPort 834 if *testnet { 835 port = v1.DefaultTestnetPort 836 } 837 *rpchost = util.NormalizeAddress(*rpchost, port) 838 u, err := url.Parse("https://" + *rpchost) 839 if err != nil { 840 return err 841 } 842 *rpchost = u.String() 843 844 // Setup RPC cert 845 if *rpccert == "" { 846 *rpccert = defaultRPCCertFile 847 } 848 849 // Scan through command line arguments. 850 for i, a := range flag.Args() { 851 // Select action 852 if i == 0 { 853 switch a { 854 case "identity": 855 return getIdentity() 856 case "new": 857 return recordNew() 858 case "verify": 859 return recordVerify() 860 case "edit": 861 return recordEdit() 862 case "editmetadata": 863 return recordEditMetadata() 864 case "setstatus": 865 return recordSetStatus() 866 case "record": 867 return record() 868 case "inventory": 869 return recordInventory() 870 default: 871 return fmt.Errorf("invalid action: %v", a) 872 } 873 } 874 } 875 876 return nil 877 } 878 879 func main() { 880 err := _main() 881 if err != nil { 882 fmt.Fprintf(os.Stderr, "%v\n", err) 883 os.Exit(1) 884 } 885 }