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  }