From 714543bbfe2c2100d03adb0fb0d7ae8a009e4cc4 Mon Sep 17 00:00:00 2001 From: Ted Pier Date: Sun, 17 Aug 2025 08:32:30 -0700 Subject: [PATCH] Not the initial commit --- go.mod | 18 ++++++ go.sum | 27 ++++++++ main.go | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1ae5b58 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module miningtcup.me/notes-cli + +go 1.24.6 + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5eca24e --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/main.go b/main.go new file mode 100644 index 0000000..89fb5e9 --- /dev/null +++ b/main.go @@ -0,0 +1,187 @@ +package main + +import ( + // "database/sql" + "bufio" + "crypto/aes" + "crypto/cipher" + "crypto/pbkdf2" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "net/http" + "net/url" + "os/signal" + "syscall" + + "os" + + "golang.org/x/term" + _ "modernc.org/sqlite" +) + +var ( + serverAddress string + username string + password string + dataDir string + dataPath string + cachePath string + scanner bufio.Scanner +) + +func check(err error, format string, a ...any) bool { + if err != nil { + log.Fatalf(format, a, err) + return false + } + return true +} + +func ReadPassword() (string, error) { + stdin := int(syscall.Stdin) + oldState, err := term.GetState(stdin) + if err != nil { + return "", err + } + defer term.Restore(stdin, oldState) + + sigch := make(chan os.Signal, 1) + signal.Notify(sigch, os.Interrupt) + + go func() { + <-sigch + term.Restore(stdin, oldState) + os.Exit(1) + }() + + password, err := term.ReadPassword(stdin) + if err != nil { + return "", err + } + + return string(password), nil +} + +func main() { + scanner = *bufio.NewScanner(os.Stdin) + home := os.Getenv("HOME") + dataPath = os.Getenv("XDG_DATA_HOME") + cachePath = os.Getenv("XDG_CACHE_HOME") + if len(dataPath) < 1 { + dataPath = home + "/.local/share" + } + if len(cachePath) < 1 { + cachePath = home + "/.cache" + } + cachePath += "/notes/notes.db" + dataDir = dataPath + "/notes" + dataPath += "/notes/userinfo.json" + + // db, err := sql.Open("sqlite", cachePath) + // if err != nil { + // log.Fatal("Error opening sqlite database for notes cache at ", cachePath, ": ", err) + // } + args := os.Args[1:] + var command string + if len(args) < 1 { + command = "" + } else { + command = args[0] + } + switch command { + case "l", "ls", "list", "search", "find": + userDataBytes, err := os.ReadFile(dataPath) + if errors.Is(err, fs.ErrNotExist) { + login() + } + check(err, "Error reading user data at '%s': %s", dataPath) + var userData []string + err = json.Unmarshal(userDataBytes, &userData) + check(err, "Error unmarshalling user data: %s") + + serverAddress, username, password = userData[0], userData[1], userData[2] + + response, err := http.Get(fmt.Sprintf("https://%s/get?user=%s", serverAddress, url.QueryEscape(username))) + check(err, "Error getting notes from '%s' as '%s': %s", serverAddress, username) + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + check(err, "Error reading response body while getting notes: %s") + var jsonBody [][2]string + err = json.Unmarshal(body, &jsonBody) + check(err, "Error unmarshalling response body: %s") + + decryptedBody := make([]string, len(jsonBody)) + for i := range len(jsonBody) { + decryptedBody[i] = decrypt(jsonBody[i][1], password) + jsonDecryptedBody := json.Marshal(decryptedBody) + decryptedBody[i] + } + fmt.Println(decryptedBody) + + default: + login() + } +} + +func login() { + fmt.Println("Welcome to Notes! To get started, choose your server and user.") + fmt.Print("Server Address [notes.miningtcup.me]: ") + _, err := fmt.Scanln(&serverAddress) + if err != nil { + serverAddress = "notes.miningtcup.me" + } + + fmt.Print("Username: ") + scanner.Scan() + username = scanner.Text() + + fmt.Print("Password: ") + passwordBytes, err := ReadPassword() + password = string(passwordBytes) + check(err, "Error reading user input (password): %s") + + jsonData, err := json.Marshal([]string{serverAddress, username, password}) + check(err, "Error marshalling server address and credentials: %s") + err = os.Mkdir(dataDir, 0700) + if !errors.Is(err, fs.ErrExist) { + check(err, "Error creating data directory at '%s': %s", dataDir) + } + err = os.WriteFile(dataPath, jsonData, 0600) + check(err, "Error writing to '%s': ", dataPath) + + fmt.Println("\nAwesome! To get started, run 'notes add' or 'notes list'") +} + +func decrypt(data string, password string) string { + dataBytes, err := base64.StdEncoding.DecodeString(data) + check(err, "Error decoding base64 data: %s") + + if len(dataBytes) < 32 { + fmt.Printf("Not enough data (got %d bytes, expected at least 32)\n", len(dataBytes)) + return "" + } + + iv := dataBytes[0:16] + salt := dataBytes[16:32] + ciphertext := dataBytes[32:] + + if len(ciphertext)%aes.BlockSize != 0 { + fmt.Printf("Ciphertext length %d is not a multiple of block size %d\n", len(ciphertext), aes.BlockSize) + return "" + } + + key, err := pbkdf2.Key(sha256.New, password, salt, 65536, 32) + check(err, "Error deriving pbkdf2-sha256 key from password: %s") + block, err := aes.NewCipher(key) + mode := cipher.NewCBCDecrypter(block, iv) + + plaintext := make([]byte, len(ciphertext)) + mode.CryptBlocks(plaintext, ciphertext) + return string(plaintext) +}