init commit
- first code pass - readme updates - config file work - encrypting passwords
This commit is contained in:
95
cmd/config.go
Normal file
95
cmd/config.go
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
server string
|
||||
port int
|
||||
)
|
||||
|
||||
// configCmd represents the config command
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Create and populate config file",
|
||||
Long: `Creates the .mctl file in the user home directory
|
||||
populating it with the server address, rcon password, and
|
||||
rcon port to be pulled when using Login command`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
//read in password using term to keep it secure/hidden from bash history
|
||||
fmt.Printf("Password: ")
|
||||
ps, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//setup aes encrypter
|
||||
block, err := aes.NewCipher([]byte(viper.Get("device").(string)))
|
||||
cobra.CheckErr(err)
|
||||
//generate and apply random nonce
|
||||
nonce := make([]byte, 12)
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
cobra.CheckErr(err)
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//encrypt rcon password
|
||||
ciphert := aesg.Seal(nil, nonce, ps, nil)
|
||||
|
||||
//update config file with new values
|
||||
viper.Set("server", server)
|
||||
viper.Set("password", string(ciphert))
|
||||
viper.Set("port", port)
|
||||
viper.Set("nonce", string(nonce))
|
||||
viper.WriteConfig()
|
||||
fmt.Println()
|
||||
fmt.Println("Config file updated!")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
initConfig()
|
||||
configCmd.Flags().StringVarP(&server, "server", "s", "", "server address")
|
||||
configCmd.MarkFlagRequired("server")
|
||||
configCmd.Flags().IntVarP(&port, "port", "p", 0, "server rcon port")
|
||||
configCmd.MarkFlagRequired("port")
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
home, err := os.UserHomeDir()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName(".mctl")
|
||||
|
||||
viper.AutomaticEnv()
|
||||
viper.ReadInConfig()
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
//file does not exist, create it
|
||||
viper.Set("server", server)
|
||||
viper.Set("password", "")
|
||||
viper.Set("port", port)
|
||||
viper.Set("nonce", "")
|
||||
|
||||
//generate a uuid to be used as encryption key
|
||||
uu := uuid.New()
|
||||
viper.Set("device", uu.String())
|
||||
|
||||
viper.SafeWriteConfig()
|
||||
}
|
||||
}
|
78
cmd/login.go
Normal file
78
cmd/login.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jake-young-dev/mcr"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// loginCmd represents the login command
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login to server and send commands",
|
||||
Long: `Login to server using saved config and enter command loop
|
||||
sending commands to server and printing the response.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
//grab saved credentials
|
||||
server := viper.Get("server")
|
||||
password := viper.Get("password")
|
||||
port := viper.Get("port")
|
||||
fmt.Printf("Logging into %s on port %d\n", server, port)
|
||||
|
||||
//setup decrypter
|
||||
nonce := viper.Get("nonce")
|
||||
block, err := aes.NewCipher([]byte(viper.Get("device").(string)))
|
||||
cobra.CheckErr(err)
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//decrypt password
|
||||
pwd := []byte(password.(string))
|
||||
nn := []byte(nonce.(string))
|
||||
pt, err := aesg.Open(nil, nn, pwd, nil)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//connect to game server
|
||||
cli := mcr.NewClient(server.(string), mcr.WithPort(port.(int)))
|
||||
err = cli.Connect(string(pt))
|
||||
cobra.CheckErr(err)
|
||||
defer cli.Close()
|
||||
|
||||
//start command loop
|
||||
fmt.Println("Connected! Type 'mctl' to close")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
var runningCmd string
|
||||
for runningCmd != "mctl" {
|
||||
fmt.Printf("RCON@%s /> ", server)
|
||||
|
||||
if scanner.Scan() {
|
||||
runningCmd = scanner.Text()
|
||||
|
||||
if runningCmd == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if runningCmd == "mctl" {
|
||||
break
|
||||
}
|
||||
|
||||
res, err := cli.Command(runningCmd)
|
||||
cobra.CheckErr(err)
|
||||
fmt.Printf("\n%s\n", res)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
}
|
32
cmd/root.go
Normal file
32
cmd/root.go
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "mctl",
|
||||
Short: "A remote console client",
|
||||
Long: `mctl is a terminal-friendly remote console client made to manage game servers.`,
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
//dont show completion subcommand in help message, makes the syntax confusing
|
||||
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
|
||||
}
|
Reference in New Issue
Block a user