465 lines
12 KiB
Go
465 lines
12 KiB
Go
package actions
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"gitea.benny.dog/torjus/ezshare/certs"
|
|
"gitea.benny.dog/torjus/ezshare/config"
|
|
"gitea.benny.dog/torjus/ezshare/pb"
|
|
"gitea.benny.dog/torjus/ezshare/server"
|
|
"github.com/urfave/cli/v2"
|
|
"golang.org/x/term"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
func ActionClientGet(c *cli.Context) error {
|
|
addr := c.String("addr")
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithInsecure())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewFileServiceClient(conn)
|
|
|
|
for _, arg := range c.Args().Slice() {
|
|
req := &pb.GetFileRequest{Id: arg}
|
|
resp, err := client.GetFile(c.Context, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filename := resp.File.FileId
|
|
if resp.File.Metadata.OriginalFilename != "" {
|
|
filename = resp.File.Metadata.OriginalFilename
|
|
}
|
|
|
|
f, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err := f.Write(resp.File.Data); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Wrote file '%s'\n", filename)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ActionClientUpload(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewFileServiceClient(conn)
|
|
|
|
for _, arg := range c.Args().Slice() {
|
|
f, err := os.Open(arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := io.ReadAll(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req := &pb.UploadFileRequest{Data: data, OriginalFilename: filepath.Base(arg)}
|
|
|
|
resp, err := client.UploadFile(c.Context, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("%s uploaded with id %s. Available at %s\n", arg, resp.Id, resp.FileUrl)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ActionClientList(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewFileServiceClient(conn)
|
|
|
|
resp, err := client.ListFiles(c.Context, &pb.ListFilesRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, elem := range resp.Files {
|
|
fmt.Println(elem.FileId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ActionClientDelete(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewFileServiceClient(conn)
|
|
for _, arg := range c.Args().Slice() {
|
|
_, err := client.DeleteFile(c.Context, &pb.DeleteFileRequest{Id: arg})
|
|
if err != nil {
|
|
return fmt.Errorf("error deleting file: %w", err)
|
|
}
|
|
fmt.Printf("Deleted file %s\n", arg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ActionClientLogin(c *cli.Context) error {
|
|
// Ensure config-dir exists
|
|
if err := config.CreateDefaultConfigDir(); err != nil {
|
|
return err
|
|
}
|
|
configFilePath, err := config.DefaultConfigFilePath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if config already exists
|
|
if _, err := os.Stat(configFilePath); !errors.Is(err, os.ErrNotExist) {
|
|
if err == nil {
|
|
if !c.Bool("overwrite") {
|
|
return cli.Exit("Config already exists. To overwrite run with --overwrite\n", 1)
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
configDirPath := filepath.Dir(configFilePath)
|
|
clientCertPath := filepath.Join(configDirPath, "client.pem")
|
|
clientKeyPath := filepath.Join(configDirPath, "client.key")
|
|
serverCertPath := filepath.Join(configDirPath, "server.pem")
|
|
|
|
if c.Args().Len() != 1 {
|
|
return cli.Exit("Need 1 argument", 1)
|
|
}
|
|
|
|
// Fetch server certificate
|
|
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
|
defer cancel()
|
|
certEndpoint := fmt.Sprintf("%s/%s", c.Args().First(), "server.pem")
|
|
certReq, err := http.NewRequestWithContext(ctx, http.MethodGet, certEndpoint, nil)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to create http request: %s", err), 1)
|
|
}
|
|
verbosePrint(c, fmt.Sprintf("fetching cert from %s", certEndpoint))
|
|
certResp, err := http.DefaultClient.Do(certReq)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("error fetching server cert: %s", err), 1)
|
|
}
|
|
defer certResp.Body.Close()
|
|
|
|
serverCert, err := io.ReadAll(certResp.Body)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("error reading certificate from server: %s", err), 1)
|
|
}
|
|
|
|
// Fetch metadata
|
|
mdEndpoint := fmt.Sprintf("%s/%s", c.Args().First(), "metadata")
|
|
mdReq, err := http.NewRequestWithContext(ctx, http.MethodGet, mdEndpoint, nil)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to create http request: %s", err), 1)
|
|
}
|
|
|
|
verbosePrint(c, fmt.Sprintf("fetching metadata from %s", mdEndpoint))
|
|
mdResp, err := http.DefaultClient.Do(mdReq)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("error fetching server cert: %s", err), 1)
|
|
}
|
|
defer mdResp.Body.Close()
|
|
|
|
decoder := json.NewDecoder(mdResp.Body)
|
|
var md server.MetadataResponse
|
|
if err := decoder.Decode(&md); err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to decode metadata response: %s", err), 1)
|
|
}
|
|
|
|
// Prompt for username and password
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
fmt.Printf("username: ")
|
|
scanner.Scan()
|
|
username := scanner.Text()
|
|
|
|
fmt.Printf("password: ")
|
|
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to read password: %s", err), 1)
|
|
}
|
|
password := string(passwordBytes)
|
|
|
|
// Setup certificate credentials
|
|
certPool := x509.NewCertPool()
|
|
if !certPool.AppendCertsFromPEM(serverCert) {
|
|
return cli.Exit(fmt.Sprintf("unable to use server certificate: %s", err), 1)
|
|
}
|
|
|
|
// Generate temporary self-signed cert
|
|
keyBytes, certBytes, err := certs.GenCACert()
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to generate self-signed certificate: %s", err), 1)
|
|
}
|
|
keyPem, err := certs.ToPEM(keyBytes, "EC PRIVATE KEY")
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to pem-encode key: %s", err), 1)
|
|
}
|
|
certPem, err := certs.ToPEM(certBytes, "CERTIFICATE")
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to pem-encode key: %s", err), 1)
|
|
}
|
|
clientCert, err := tls.X509KeyPair(certPem, keyPem)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to use self-signed certificate: %s", err), 1)
|
|
}
|
|
|
|
creds := credentials.NewTLS(&tls.Config{RootCAs: certPool, Certificates: []tls.Certificate{clientCert}})
|
|
|
|
// Connect to grpc-endpoint
|
|
verbosePrint(c, fmt.Sprintf("dialing grpc at %s", md.GRPCEndpoint))
|
|
conn, err := grpc.DialContext(c.Context, md.GRPCEndpoint, grpc.WithTransportCredentials(creds))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewUserServiceClient(conn)
|
|
|
|
resp, err := client.Login(c.Context, &pb.LoginUserRequest{Username: username, Password: password})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write key to file
|
|
verbosePrint(c, fmt.Sprintf("Writing client certificate key to %s", clientKeyPath))
|
|
keyFile, err := os.Create(clientKeyPath)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable create file for client key: %s", err), 1)
|
|
}
|
|
defer keyFile.Close()
|
|
if _, err := keyFile.Write(resp.ClientKey); err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable write client key to file: %s", err), 1)
|
|
}
|
|
|
|
// Write client cert to file
|
|
verbosePrint(c, fmt.Sprintf("Writing client certificate to %s", clientKeyPath))
|
|
clientCertFile, err := os.Create(clientCertPath)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable create file for client cert: %s", err), 1)
|
|
}
|
|
defer clientCertFile.Close()
|
|
if _, err := clientCertFile.Write(resp.ClientCert); err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable write client cert to file: %s", err), 1)
|
|
}
|
|
|
|
// Write server cer to file
|
|
verbosePrint(c, fmt.Sprintf("Writing server certificate to %s", serverCertPath))
|
|
serverCertFile, err := os.Create(serverCertPath)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable create file for client key: %s", err), 1)
|
|
}
|
|
defer serverCertFile.Close()
|
|
if _, err := serverCertFile.Write(serverCert); err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable write client cert to file: %s", err), 1)
|
|
}
|
|
|
|
// Write config
|
|
cfg := config.FromDefault()
|
|
cfg.Client.Certs.CertificateKeyPath = clientKeyPath
|
|
cfg.Client.Certs.CertificatePath = clientCertPath
|
|
cfg.Client.DefaultServer = md.GRPCEndpoint
|
|
cfg.Client.ServerCertPath = serverCertPath
|
|
|
|
verbosePrint(c, fmt.Sprintf("Writing config to %s", configFilePath))
|
|
f, err := os.Create(configFilePath)
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("Unable to open config-file for writing: %s", err), 1)
|
|
}
|
|
defer f.Close()
|
|
if err := cfg.ToWriter(f); err != nil {
|
|
return cli.Exit(fmt.Sprintf("Unable write config to file: %s", err), 1)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ActionClientChangePassword(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
fmt.Printf("current password: ")
|
|
oldPasswordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to read password: %s", err), 1)
|
|
}
|
|
fmt.Println()
|
|
oldPassword := string(oldPasswordBytes)
|
|
fmt.Printf("new password: ")
|
|
newPasswordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to read password: %s", err), 1)
|
|
}
|
|
fmt.Println()
|
|
newPassword := string(newPasswordBytes)
|
|
|
|
client := pb.NewUserServiceClient(conn)
|
|
if _, err := client.ChangePassword(c.Context, &pb.ChangePasswordRequest{OldPassword: oldPassword, NewPassword: newPassword}); err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to change password: %s", err), 1)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ActionClientCertList(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewCertificateServiceClient(conn)
|
|
resp, err := client.ListCertificates(c.Context, &pb.Empty{})
|
|
if err != nil {
|
|
return cli.Exit(fmt.Sprintf("unable to list certificates: %s", err), 1)
|
|
}
|
|
|
|
for _, info := range resp.Certificates {
|
|
fmt.Printf("%s - %s", info.Serial, info.OwnerUsername)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ActionClientCertRevoke(c *cli.Context) error {
|
|
if c.Args().Len() < 1 {
|
|
return cli.Exit("need at least 1 argument", 1)
|
|
}
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addr := cfg.Client.DefaultServer
|
|
if c.IsSet("addr") {
|
|
addr = c.String("addr")
|
|
}
|
|
|
|
clientCreds, err := cfg.Client.Creds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := pb.NewCertificateServiceClient(conn)
|
|
for _, serial := range c.Args().Slice() {
|
|
if _, err := client.RevokeCertificate(c.Context, &pb.RevokeCertificateRequest{Serial: serial}); err != nil {
|
|
fmt.Printf("Revoked %s\n", serial)
|
|
}
|
|
}
|
|
return nil
|
|
}
|