2017-11-28 03:12:09 +03:00
|
|
|
// Copyright (c) 2013-2017 The btcsuite developers
|
|
|
|
// Copyright (c) 2015-2016 The Decred developers
|
|
|
|
// Copyright (C) 2015-2017 The Lightning Network Developers
|
|
|
|
|
2015-12-30 05:31:03 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-08-18 04:51:33 +03:00
|
|
|
"io/ioutil"
|
2015-12-30 05:31:03 +03:00
|
|
|
"os"
|
2018-02-26 06:41:44 +03:00
|
|
|
"os/user"
|
2017-07-26 02:22:06 +03:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2017-08-18 04:51:33 +03:00
|
|
|
|
2018-01-16 19:18:41 +03:00
|
|
|
macaroon "gopkg.in/macaroon.v2"
|
2015-12-30 05:31:03 +03:00
|
|
|
|
2016-01-16 21:45:54 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
2017-08-18 04:51:33 +03:00
|
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
2017-07-26 02:22:06 +03:00
|
|
|
"github.com/roasbeef/btcutil"
|
2016-07-26 20:42:35 +03:00
|
|
|
"github.com/urfave/cli"
|
2015-12-30 05:31:03 +03:00
|
|
|
|
|
|
|
"google.golang.org/grpc"
|
2017-07-26 02:22:06 +03:00
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-08-18 04:51:33 +03:00
|
|
|
defaultTLSCertFilename = "tls.cert"
|
|
|
|
defaultMacaroonFilename = "admin.macaroon"
|
2017-07-26 02:22:06 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-02-24 23:59:22 +03:00
|
|
|
defaultLndDir = btcutil.AppDataDir("lnd", false)
|
|
|
|
defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename)
|
|
|
|
defaultMacaroonPath = filepath.Join(defaultLndDir, defaultMacaroonFilename)
|
2015-12-30 05:31:03 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
func fatal(err error) {
|
|
|
|
fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2017-10-12 12:42:06 +03:00
|
|
|
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient, func()) {
|
2018-02-01 03:04:56 +03:00
|
|
|
conn := getClientConn(ctx, true)
|
2017-10-12 12:42:06 +03:00
|
|
|
|
|
|
|
cleanUp := func() {
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
return lnrpc.NewWalletUnlockerClient(conn), cleanUp
|
|
|
|
}
|
|
|
|
|
2017-01-30 01:51:30 +03:00
|
|
|
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
|
2018-02-01 03:04:56 +03:00
|
|
|
conn := getClientConn(ctx, false)
|
2017-01-30 01:51:30 +03:00
|
|
|
|
|
|
|
cleanUp := func() {
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
return lnrpc.NewLightningClient(conn), cleanUp
|
2015-12-30 05:31:03 +03:00
|
|
|
}
|
|
|
|
|
2018-02-01 03:04:56 +03:00
|
|
|
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
|
2018-02-24 23:59:22 +03:00
|
|
|
lndDir := cleanAndExpandPath(ctx.GlobalString("lnddir"))
|
|
|
|
if lndDir != defaultLndDir {
|
|
|
|
// If a custom lnd directory was set, we'll also check if custom
|
|
|
|
// paths for the TLS cert and macaroon file were set as well. If
|
|
|
|
// not, we'll override their paths so they can be found within
|
|
|
|
// the custom lnd directory set. This allows us to set a custom
|
|
|
|
// lnd directory, along with custom paths to the TLS cert and
|
|
|
|
// macaroon file.
|
|
|
|
tlsCertPath := cleanAndExpandPath(ctx.GlobalString("tlscertpath"))
|
|
|
|
if tlsCertPath == defaultTLSCertPath {
|
|
|
|
ctx.GlobalSet("tlscertpath",
|
|
|
|
filepath.Join(lndDir, defaultTLSCertFilename))
|
|
|
|
}
|
|
|
|
|
|
|
|
macPath := cleanAndExpandPath(ctx.GlobalString("macaroonpath"))
|
|
|
|
if macPath == defaultMacaroonPath {
|
2018-03-13 19:17:49 +03:00
|
|
|
ctx.GlobalSet("macaroonpath",
|
2018-02-24 23:59:22 +03:00
|
|
|
filepath.Join(lndDir, defaultMacaroonFilename))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-18 04:51:33 +03:00
|
|
|
// Load the specified TLS certificate and build transport credentials
|
|
|
|
// with it.
|
|
|
|
tlsCertPath := cleanAndExpandPath(ctx.GlobalString("tlscertpath"))
|
|
|
|
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
|
|
|
|
if err != nil {
|
2017-07-26 02:22:06 +03:00
|
|
|
fatal(err)
|
|
|
|
}
|
2017-08-18 04:51:33 +03:00
|
|
|
|
|
|
|
// Create a dial options array.
|
|
|
|
opts := []grpc.DialOption{
|
|
|
|
grpc.WithTransportCredentials(creds),
|
2017-07-26 02:22:06 +03:00
|
|
|
}
|
2017-08-18 04:51:33 +03:00
|
|
|
|
2018-02-01 03:04:56 +03:00
|
|
|
// Only process macaroon credentials if --no-macaroons isn't set and
|
|
|
|
// if we're not skipping macaroon processing.
|
|
|
|
if !ctx.GlobalBool("no-macaroons") && !skipMacaroons {
|
2017-08-18 04:51:33 +03:00
|
|
|
// Load the specified macaroon file.
|
|
|
|
macPath := cleanAndExpandPath(ctx.GlobalString("macaroonpath"))
|
|
|
|
macBytes, err := ioutil.ReadFile(macPath)
|
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
mac := &macaroon.Macaroon{}
|
|
|
|
if err = mac.UnmarshalBinary(macBytes); err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-09-02 03:45:14 +03:00
|
|
|
macConstraints := []macaroons.Constraint{
|
|
|
|
// We add a time-based constraint to prevent replay of the
|
|
|
|
// macaroon. It's good for 60 seconds by default to make up for
|
|
|
|
// any discrepancy between client and server clocks, but leaking
|
|
|
|
// the macaroon before it becomes invalid makes it possible for
|
|
|
|
// an attacker to reuse the macaroon. In addition, the validity
|
|
|
|
// time of the macaroon is extended by the time the server clock
|
|
|
|
// is behind the client clock, or shortened by the time the
|
|
|
|
// server clock is ahead of the client clock (or invalid
|
|
|
|
// altogether if, in the latter case, this time is more than 60
|
|
|
|
// seconds).
|
|
|
|
// TODO(aakselrod): add better anti-replay protection.
|
|
|
|
macaroons.TimeoutConstraint(ctx.GlobalInt64("macaroontimeout")),
|
|
|
|
|
2017-09-02 03:46:27 +03:00
|
|
|
// Lock macaroon down to a specific IP address.
|
|
|
|
macaroons.IPLockConstraint(ctx.GlobalString("macaroonip")),
|
|
|
|
|
2017-09-02 03:45:14 +03:00
|
|
|
// ... Add more constraints if needed.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply constraints to the macaroon.
|
2017-09-13 23:44:05 +03:00
|
|
|
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
|
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
2017-08-18 04:51:33 +03:00
|
|
|
|
|
|
|
// Now we append the macaroon credentials to the dial options.
|
2017-09-02 03:45:14 +03:00
|
|
|
cred := macaroons.NewMacaroonCredential(constrainedMac)
|
|
|
|
opts = append(opts, grpc.WithPerRPCCredentials(cred))
|
2017-07-26 02:22:06 +03:00
|
|
|
}
|
2015-12-30 05:31:03 +03:00
|
|
|
|
|
|
|
conn, err := grpc.Dial(ctx.GlobalString("rpcserver"), opts...)
|
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return conn
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
app := cli.NewApp()
|
|
|
|
app.Name = "lncli"
|
2018-03-15 12:08:34 +03:00
|
|
|
app.Version = "0.4"
|
2016-12-28 02:45:10 +03:00
|
|
|
app.Usage = "control plane for your Lightning Network Daemon (lnd)"
|
2015-12-30 05:31:03 +03:00
|
|
|
app.Flags = []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "rpcserver",
|
2016-03-23 04:47:10 +03:00
|
|
|
Value: "localhost:10009",
|
2016-01-16 21:45:54 +03:00
|
|
|
Usage: "host:port of ln daemon",
|
2015-12-30 05:31:03 +03:00
|
|
|
},
|
2018-02-24 23:59:22 +03:00
|
|
|
cli.StringFlag{
|
|
|
|
Name: "lnddir",
|
|
|
|
Value: defaultLndDir,
|
|
|
|
Usage: "path to lnd's base directory",
|
|
|
|
},
|
2017-07-26 02:22:06 +03:00
|
|
|
cli.StringFlag{
|
|
|
|
Name: "tlscertpath",
|
|
|
|
Value: defaultTLSCertPath,
|
|
|
|
Usage: "path to TLS certificate",
|
|
|
|
},
|
2017-08-18 04:51:33 +03:00
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "no-macaroons",
|
|
|
|
Usage: "disable macaroon authentication",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "macaroonpath",
|
|
|
|
Value: defaultMacaroonPath,
|
|
|
|
Usage: "path to macaroon file",
|
|
|
|
},
|
|
|
|
cli.Int64Flag{
|
|
|
|
Name: "macaroontimeout",
|
|
|
|
Value: 60,
|
|
|
|
Usage: "anti-replay macaroon validity time in seconds",
|
|
|
|
},
|
2017-09-02 03:46:27 +03:00
|
|
|
cli.StringFlag{
|
|
|
|
Name: "macaroonip",
|
|
|
|
Usage: "if set, lock macaroon to specific IP address",
|
|
|
|
},
|
2015-12-30 05:31:03 +03:00
|
|
|
}
|
|
|
|
app.Commands = []cli.Command{
|
2017-10-12 12:42:06 +03:00
|
|
|
createCommand,
|
|
|
|
unlockCommand,
|
2017-02-24 16:32:33 +03:00
|
|
|
newAddressCommand,
|
|
|
|
sendManyCommand,
|
|
|
|
sendCoinsCommand,
|
|
|
|
connectCommand,
|
2017-05-06 01:54:25 +03:00
|
|
|
disconnectCommand,
|
2017-02-24 16:32:33 +03:00
|
|
|
openChannelCommand,
|
|
|
|
closeChannelCommand,
|
2018-01-24 05:34:29 +03:00
|
|
|
closeAllChannelsCommand,
|
2017-02-24 16:32:33 +03:00
|
|
|
listPeersCommand,
|
|
|
|
walletBalanceCommand,
|
|
|
|
channelBalanceCommand,
|
|
|
|
getInfoCommand,
|
|
|
|
pendingChannelsCommand,
|
|
|
|
sendPaymentCommand,
|
2017-10-28 01:39:54 +03:00
|
|
|
payInvoiceCommand,
|
2017-02-24 16:32:33 +03:00
|
|
|
addInvoiceCommand,
|
|
|
|
lookupInvoiceCommand,
|
|
|
|
listInvoicesCommand,
|
|
|
|
listChannelsCommand,
|
|
|
|
listPaymentsCommand,
|
|
|
|
describeGraphCommand,
|
|
|
|
getChanInfoCommand,
|
|
|
|
getNodeInfoCommand,
|
2017-03-21 05:01:57 +03:00
|
|
|
queryRoutesCommand,
|
2017-02-24 16:32:33 +03:00
|
|
|
getNetworkInfoCommand,
|
|
|
|
debugLevelCommand,
|
2018-02-07 06:11:11 +03:00
|
|
|
decodePayReqCommand,
|
2017-03-09 07:44:32 +03:00
|
|
|
listChainTxnsCommand,
|
2017-05-12 00:55:56 +03:00
|
|
|
stopCommand,
|
2017-04-20 05:33:09 +03:00
|
|
|
signMessageCommand,
|
|
|
|
verifyMessageCommand,
|
2017-08-22 10:29:08 +03:00
|
|
|
feeReportCommand,
|
2017-12-17 01:13:17 +03:00
|
|
|
updateChannelPolicyCommand,
|
2018-02-28 09:24:37 +03:00
|
|
|
forwardingHistoryCommand,
|
2015-12-30 05:31:03 +03:00
|
|
|
}
|
2016-01-17 06:10:29 +03:00
|
|
|
|
2015-12-30 05:31:03 +03:00
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2017-07-26 02:22:06 +03:00
|
|
|
|
|
|
|
// cleanAndExpandPath expands environment variables and leading ~ in the
|
|
|
|
// passed path, cleans the result, and returns it.
|
|
|
|
// This function is taken from https://github.com/btcsuite/btcd
|
|
|
|
func cleanAndExpandPath(path string) string {
|
|
|
|
// Expand initial ~ to OS specific home directory.
|
|
|
|
if strings.HasPrefix(path, "~") {
|
2018-02-26 06:41:44 +03:00
|
|
|
var homeDir string
|
|
|
|
|
|
|
|
user, err := user.Current()
|
|
|
|
if err == nil {
|
|
|
|
homeDir = user.HomeDir
|
|
|
|
} else {
|
|
|
|
homeDir = os.Getenv("HOME")
|
|
|
|
}
|
|
|
|
|
2017-07-26 02:22:06 +03:00
|
|
|
path = strings.Replace(path, "~", homeDir, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
|
|
|
|
// but the variables can still be expanded via POSIX-style $VARIABLE.
|
|
|
|
return filepath.Clean(os.ExpandEnv(path))
|
|
|
|
}
|