github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbnm/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  
    11  	"github.com/keybase/client/go/kbnm/installer"
    12  	"github.com/qrtz/nativemessaging"
    13  )
    14  
    15  // internalVersion is the logical version of this code (rather than build).
    16  const internalVersion = "1.4.1"
    17  
    18  // Version is the build version of kbnm, overwritten during build with metadata.
    19  var Version = "dev"
    20  
    21  // Response from the kbnm service
    22  type Response struct {
    23  	Client  int         `json:"client"`
    24  	Status  string      `json:"status"`
    25  	Message string      `json:"message"`
    26  	Result  interface{} `json:"result,omitempty"`
    27  }
    28  
    29  // Request to the kbnm service
    30  type Request struct {
    31  	Client int    `json:"client"`
    32  	Method string `json:"method"`
    33  	To     string `json:"to"`
    34  	Body   string `json:"body"`
    35  }
    36  
    37  var plainFlag = flag.Bool("plain", false, "newline-delimited JSON IO, no length prefix")
    38  var versionFlag = flag.Bool("version", false, "print the version and exit")
    39  
    40  // process consumes a single message
    41  func process(h *handler, in nativemessaging.JSONDecoder, out nativemessaging.JSONEncoder) error {
    42  	var resp Response
    43  	var req Request
    44  
    45  	// If input fails to parse, we can't guarantee future inputs will
    46  	// get into a parseable state so we abort after sending an error
    47  	// response.
    48  	abortErr := in.Decode(&req)
    49  
    50  	var err error
    51  	if abortErr == nil {
    52  		resp.Result, err = h.Handle(&req)
    53  	}
    54  
    55  	switch err {
    56  	case nil:
    57  		// Success
    58  		resp.Status = "ok"
    59  	case io.EOF:
    60  		// Closed
    61  		return err
    62  	default:
    63  		resp.Status = "error"
    64  		resp.Message = err.Error()
    65  	}
    66  	resp.Client = req.Client
    67  
    68  	err = out.Encode(resp)
    69  	if err != nil {
    70  		// TODO: Log this somewhere?
    71  		fmt.Fprintf(os.Stderr, "error: %s", err)
    72  		os.Exit(1)
    73  	}
    74  
    75  	return abortErr
    76  }
    77  
    78  func exit(code int, msg string, a ...interface{}) {
    79  	fmt.Fprintf(os.Stderr, msg+"\n", a...)
    80  	os.Exit(code)
    81  }
    82  
    83  func main() {
    84  	flag.Parse()
    85  
    86  	if *versionFlag {
    87  		fmt.Printf("%s-%s\n", internalVersion, Version)
    88  		os.Exit(0)
    89  	}
    90  
    91  	switch flag.Arg(0) {
    92  	case "install":
    93  		kbnmPath, err := findKeybaseBinary(kbnmBinary)
    94  		if err != nil {
    95  			exit(2, "error finding kbnm binary: %s", err)
    96  		}
    97  		log.Print("installing: ", kbnmPath)
    98  		if err := installer.InstallKBNM(kbnmPath); err != nil {
    99  			exit(2, "error installing kbnm whitelist: %s", err)
   100  		}
   101  		exit(0, "Installed NativeMessaging whitelists.")
   102  	case "uninstall":
   103  		if err := installer.UninstallKBNM(); err != nil {
   104  			exit(2, "error uninstalling kbnm whitelist: %s", err)
   105  		}
   106  		exit(0, "Uninstalled NativeMessaging whitelists.")
   107  	}
   108  
   109  	// Native messages include a prefix which describes the length of each message.
   110  	var in nativemessaging.JSONDecoder
   111  	var out nativemessaging.JSONEncoder
   112  
   113  	if *plainFlag {
   114  		// Used for testing interactively
   115  		in = json.NewDecoder(os.Stdin)
   116  		out = json.NewEncoder(os.Stdout)
   117  	} else {
   118  		// Used as part of the NativeMessaging API
   119  		in = nativemessaging.NewNativeJSONDecoder(os.Stdin)
   120  		out = nativemessaging.NewNativeJSONEncoder(os.Stdout)
   121  	}
   122  
   123  	h := newHandler()
   124  
   125  	for {
   126  		err := process(h, in, out)
   127  		if err == io.EOF {
   128  			// Clean close
   129  			break
   130  		}
   131  		if err != nil {
   132  			exit(1, "stream processing error: %s", err)
   133  		}
   134  	}
   135  }