2022-01-09 13:05:36 +10:30
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-01-15 18:20:31 +10:30
|
|
|
"os"
|
2022-01-17 08:28:08 +10:30
|
|
|
"strconv"
|
2022-01-15 18:20:31 +10:30
|
|
|
|
2022-01-16 12:58:11 +10:30
|
|
|
log "github.com/sirupsen/logrus"
|
2022-01-25 21:07:34 +10:30
|
|
|
"golang.org/x/term"
|
2022-01-16 12:58:11 +10:30
|
|
|
|
2022-01-15 18:20:31 +10:30
|
|
|
"github.com/mattn/go-isatty"
|
2022-01-16 22:36:34 +10:30
|
|
|
flag "github.com/spf13/pflag"
|
2022-01-16 12:58:11 +10:30
|
|
|
"github.com/spf13/viper"
|
2022-01-09 13:05:36 +10:30
|
|
|
)
|
|
|
|
|
2022-01-26 10:49:23 +10:30
|
|
|
const ProtocolVersion = "1.1"
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
type ListValue struct {
|
|
|
|
Required bool
|
|
|
|
Number uint
|
2022-01-17 08:28:08 +10:30
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
func (v *ListValue) String() string {
|
|
|
|
if v.Required {
|
|
|
|
return fmt.Sprintf("YES: %d", v.Number)
|
2022-01-17 08:28:08 +10:30
|
|
|
}
|
|
|
|
return "0"
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
func (v *ListValue) Set(s string) error {
|
|
|
|
v.Required = true
|
2022-01-17 08:28:08 +10:30
|
|
|
num, err := strconv.ParseUint(s, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
v.Number = uint(num)
|
2022-01-17 08:28:08 +10:30
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
func (v *ListValue) Type() string {
|
2022-01-17 08:28:08 +10:30
|
|
|
return "int"
|
|
|
|
}
|
2022-01-16 23:00:29 +10:30
|
|
|
|
2022-01-25 21:07:34 +10:30
|
|
|
func getAuthTokenFromTerminal() string {
|
2025-04-25 14:21:43 +08:00
|
|
|
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0o755)
|
2022-01-25 21:07:34 +10:30
|
|
|
if err != nil {
|
|
|
|
log.Printf("cannot open /dev/tty to read authtoken: %v", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
fd := int(tty.Fd())
|
|
|
|
|
|
|
|
oldState, err := term.MakeRaw(fd)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("cannot set /dev/tty to raw mode: %v", err)
|
|
|
|
return ""
|
|
|
|
}
|
2025-04-25 14:21:43 +08:00
|
|
|
defer func() {
|
|
|
|
_ = term.Restore(fd, oldState)
|
|
|
|
}()
|
2022-01-25 21:07:34 +10:30
|
|
|
|
|
|
|
t := term.NewTerminal(tty, "")
|
|
|
|
pass, err := t.ReadPassword("Enter auth token: ")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("cannot read password from /dev/tty: %v", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return pass
|
|
|
|
}
|
|
|
|
|
2025-04-25 13:15:57 +09:30
|
|
|
var (
|
|
|
|
version = "dev"
|
|
|
|
commit = "none"
|
|
|
|
date = "unknown"
|
|
|
|
)
|
|
|
|
|
2022-01-09 13:05:36 +10:30
|
|
|
func main() {
|
2022-01-16 22:36:34 +10:30
|
|
|
isServer := flag.Bool("server", false, "Run netgiv in server mode")
|
2022-01-16 19:46:29 +10:30
|
|
|
|
|
|
|
// client mode flags
|
2022-01-17 08:28:08 +10:30
|
|
|
isList := flag.BoolP("list", "l", false, "Returns a list of current items on the server")
|
2025-04-25 14:21:43 +08:00
|
|
|
isSend := flag.BoolP("copy", "c", false, "send stdin to netgiv server (copy)")
|
2022-01-17 08:28:08 +10:30
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
pasteFlag := ListValue{}
|
|
|
|
flag.VarP(&pasteFlag, "paste", "p", "receive from netgiv server to stdout (paste), with optional id (see --list)")
|
2022-01-17 08:28:08 +10:30
|
|
|
flag.Lookup("paste").NoOptDefVal = "0"
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
burnFlag := ListValue{}
|
|
|
|
flag.VarP(&burnFlag, "burn", "b", "burn (remove/delete) the item on the netgiv server, with optional id (see --list)")
|
|
|
|
flag.Lookup("burn").NoOptDefVal = "0"
|
|
|
|
|
2022-01-16 20:35:35 +10:30
|
|
|
debug := flag.Bool("debug", false, "turn on debug logging")
|
2022-01-16 19:46:29 +10:30
|
|
|
flag.String("address", "", "IP address/hostname of the netgiv server")
|
|
|
|
|
|
|
|
helpConfig := flag.Bool("help-config", false, "Show help on netgiv configuration")
|
|
|
|
|
|
|
|
// common flags
|
|
|
|
flag.String("authtoken", "", "Authentication token")
|
|
|
|
flag.Int("port", 0, "Port")
|
2022-01-14 23:30:14 +10:30
|
|
|
|
2025-04-25 13:15:57 +09:30
|
|
|
versionFlag := flag.BoolP("version", "v", false, "show version and exit")
|
|
|
|
|
2022-01-09 13:05:36 +10:30
|
|
|
flag.Parse()
|
|
|
|
|
2025-04-25 13:15:57 +09:30
|
|
|
if versionFlag != nil && *versionFlag {
|
2025-04-25 13:20:43 +09:30
|
|
|
fmt.Print(versionInfo(true))
|
2025-04-25 13:15:57 +09:30
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
receiveNum := int(pasteFlag.Number)
|
|
|
|
if !pasteFlag.Required {
|
2022-01-17 08:28:08 +10:30
|
|
|
receiveNum = -1
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
burnNum := int(burnFlag.Number)
|
|
|
|
if !burnFlag.Required {
|
|
|
|
burnNum = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
viper.AddConfigPath("$HOME/.netgiv/")
|
|
|
|
viper.AddConfigPath("$HOME/.config/netgiv/") // calling multiple times adds to search paths
|
2022-01-16 19:46:29 +10:30
|
|
|
viper.SetConfigType("yaml")
|
|
|
|
|
2022-01-16 12:58:11 +10:30
|
|
|
viper.SetDefault("port", 4512)
|
|
|
|
|
2022-01-16 19:46:29 +10:30
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
2022-01-16 22:26:17 +10:30
|
|
|
// don't worry be happy
|
2022-01-16 19:46:29 +10:30
|
|
|
} else {
|
|
|
|
// Config file was found but another error was produced
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
_ = viper.BindPFlags(flag.CommandLine)
|
2022-01-16 12:58:11 +10:30
|
|
|
|
2022-01-16 19:46:29 +10:30
|
|
|
viper.SetEnvPrefix("NETGIV")
|
2025-04-25 14:21:43 +08:00
|
|
|
_ = viper.BindEnv("authtoken")
|
2022-01-16 19:46:29 +10:30
|
|
|
|
|
|
|
// pull the various things into local variables
|
2022-01-16 12:58:11 +10:30
|
|
|
port := viper.GetInt("port") // retrieve value from viper
|
2022-01-16 19:46:29 +10:30
|
|
|
authtoken := viper.GetString("authtoken")
|
2022-01-16 12:58:11 +10:30
|
|
|
|
2022-01-16 19:46:29 +10:30
|
|
|
address := viper.GetString("address")
|
|
|
|
|
|
|
|
if *helpConfig {
|
|
|
|
fmt.Print(
|
|
|
|
`netgiv can be configured by command line parameters (see --help) but it will
|
|
|
|
often be convenient to create a config file. The config file is in yaml format,
|
|
|
|
and should be stored in $HOME/.netgiv/config.yaml.
|
|
|
|
|
|
|
|
For both client and server, you will want to set the 'authtoken' key (they must
|
|
|
|
match). You'll want to also set the 'port' key if you would like to run netgiv
|
|
|
|
on a non-standard port (the default is 4512).
|
|
|
|
|
|
|
|
On the client you will probably want to set the 'address' key, so that your client
|
|
|
|
knows where to find the netgiv server. This key is ignored when running in server
|
|
|
|
mode.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
port: 5412
|
|
|
|
authtoken: verysecretvaluehere
|
|
|
|
address: 10.1.12.20
|
2022-01-16 19:49:03 +10:30
|
|
|
|
|
|
|
Note that it is possible to set/override the authtoken by setting the NETGIV_AUTHTOKEN
|
|
|
|
environment variable. This may be preferable in some environments.
|
|
|
|
|
2022-01-16 19:46:29 +10:30
|
|
|
`)
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-01-16 20:35:35 +10:30
|
|
|
if *debug {
|
|
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
}
|
|
|
|
|
2022-01-25 21:07:34 +10:30
|
|
|
// if still no authtoken and in client mode, try from the terminal, last
|
|
|
|
// ditch effort
|
|
|
|
if !*isServer && authtoken == "" {
|
|
|
|
authtoken = getAuthTokenFromTerminal()
|
|
|
|
}
|
|
|
|
|
2022-01-16 22:26:17 +10:30
|
|
|
if authtoken == "" {
|
|
|
|
log.Fatal("authtoken must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !*isServer && address == "" {
|
|
|
|
log.Fatal("an address must be provided on the command line, or configuration")
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
log.Debugf("protocol version: %s", ProtocolVersion)
|
2022-01-16 19:46:29 +10:30
|
|
|
if *isServer {
|
|
|
|
s := Server{port: port, authToken: authtoken}
|
2022-01-09 13:05:36 +10:30
|
|
|
s.Run()
|
|
|
|
} else {
|
2025-04-25 14:21:43 +08:00
|
|
|
if !*isList && !*isSend && burnNum == -1 && receiveNum == -1 {
|
2022-01-15 18:20:31 +10:30
|
|
|
// try to work out the intent based on whether or not stdin/stdout
|
|
|
|
// are ttys
|
|
|
|
stdinTTY := isatty.IsTerminal(os.Stdin.Fd())
|
|
|
|
stdoutTTY := isatty.IsTerminal(os.Stdout.Fd())
|
|
|
|
|
|
|
|
if stdinTTY && !stdoutTTY {
|
2022-01-17 08:28:08 +10:30
|
|
|
receiveNum = 0
|
2022-01-15 18:20:31 +10:30
|
|
|
} else if !stdinTTY && stdoutTTY {
|
|
|
|
*isSend = true
|
2022-01-15 23:11:21 +10:30
|
|
|
} else if !stdinTTY && !stdoutTTY {
|
|
|
|
log.Fatal("I can't cope with both stdin and stdout being pipes")
|
2022-01-16 19:46:29 +10:30
|
|
|
} else {
|
|
|
|
flag.Usage()
|
|
|
|
os.Exit(1)
|
2022-01-15 18:20:31 +10:30
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-04-25 14:21:43 +08:00
|
|
|
c := Client{port: port, address: address, list: *isList, send: *isSend, burnNum: burnNum, receiveNum: receiveNum, authToken: authtoken}
|
2022-01-09 13:05:36 +10:30
|
|
|
err := c.Connect()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Print(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-04-25 13:20:43 +09:30
|
|
|
|
|
|
|
func versionInfo(verbose bool) string {
|
|
|
|
out := ""
|
|
|
|
out += fmt.Sprintf("netgiv %s, built at %s\n", version, date)
|
|
|
|
if verbose {
|
|
|
|
out += fmt.Sprintf("commit: %s\n", commit)
|
|
|
|
out += fmt.Sprintf("http://github.com/tardisx/netgiv\n")
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|