2018-01-23 21:53:05 +00:00
|
|
|
// Util to generate/store passwords for users in a file akin to /etc/passwd
|
|
|
|
// suitable for the demo hkexsh server, using bcrypt.
|
2018-04-07 20:04:10 +00:00
|
|
|
//
|
2019-10-30 03:34:09 +00:00
|
|
|
// Copyright (c) 2017-2019 Russell Magee
|
2018-04-07 20:04:10 +00:00
|
|
|
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
|
|
|
// distribution)
|
|
|
|
//
|
|
|
|
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
2018-01-23 21:53:05 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/csv"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
|
2019-12-12 01:37:56 +00:00
|
|
|
xs "blitter.com/go/xs"
|
2018-09-08 03:56:42 +00:00
|
|
|
"github.com/jameskeane/bcrypt"
|
2018-01-23 21:53:05 +00:00
|
|
|
)
|
|
|
|
|
2019-07-11 17:12:38 +00:00
|
|
|
var (
|
|
|
|
version string
|
|
|
|
gitCommit string
|
|
|
|
)
|
|
|
|
|
2018-11-25 18:24:10 +00:00
|
|
|
// nolint: gocyclo
|
2018-01-23 21:53:05 +00:00
|
|
|
func main() {
|
2019-07-11 17:12:38 +00:00
|
|
|
var vopt bool
|
2018-01-23 21:53:05 +00:00
|
|
|
var pfName string
|
|
|
|
var newpw string
|
|
|
|
var confirmpw string
|
|
|
|
var userName string
|
|
|
|
|
2019-07-11 17:12:38 +00:00
|
|
|
flag.BoolVar(&vopt, "v", false, "show version")
|
2018-01-23 21:53:05 +00:00
|
|
|
flag.StringVar(&userName, "u", "", "username")
|
2019-10-30 03:34:09 +00:00
|
|
|
flag.StringVar(&pfName, "f", "/etc/xs.passwd", "passwd file")
|
2018-01-23 21:53:05 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
2019-07-11 17:12:38 +00:00
|
|
|
if vopt {
|
|
|
|
fmt.Printf("version %s (%s)\n", version, gitCommit)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2018-01-23 21:53:05 +00:00
|
|
|
var uname string
|
|
|
|
if len(userName) == 0 {
|
|
|
|
log.Println("specify username with -u")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2018-09-08 03:56:42 +00:00
|
|
|
//u, err := user.Lookup(userName)
|
|
|
|
//if err != nil {
|
|
|
|
// log.Printf("Invalid user %s\n", userName)
|
|
|
|
// log.Fatal(err)
|
|
|
|
//}
|
|
|
|
//uname = u.Username
|
|
|
|
uname = userName
|
2018-01-23 21:53:05 +00:00
|
|
|
|
|
|
|
fmt.Printf("New Password:")
|
2020-04-26 01:03:29 +00:00
|
|
|
ab, err := xs.ReadPassword(os.Stdin.Fd())
|
2018-01-23 21:53:05 +00:00
|
|
|
fmt.Printf("\r\n")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
newpw = string(ab)
|
|
|
|
|
|
|
|
fmt.Printf("Confirm:")
|
2020-04-26 01:03:29 +00:00
|
|
|
ab, err = xs.ReadPassword(os.Stdin.Fd())
|
2018-01-23 21:53:05 +00:00
|
|
|
fmt.Printf("\r\n")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
confirmpw = string(ab)
|
|
|
|
|
|
|
|
if confirmpw != newpw {
|
|
|
|
log.Println("New passwords do not match.")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate a random salt with specific rounds of complexity
|
|
|
|
// (default in jameskeane/bcrypt is 12 but we'll be explicit here)
|
|
|
|
salt, err := bcrypt.Salt(12)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("ERROR: bcrypt.Salt() failed.")
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
|
|
|
// hash and verify a password with explicit (random) salt
|
|
|
|
hash, err := bcrypt.Hash(newpw, salt)
|
|
|
|
if err != nil || !bcrypt.Match(newpw, hash) {
|
|
|
|
fmt.Println("ERROR: bcrypt.Match() failed.")
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
//fmt.Println("Salt:", salt, "Hash:", hash)
|
|
|
|
|
2018-11-25 18:24:10 +00:00
|
|
|
b, err := ioutil.ReadFile(pfName) // nolint: gosec
|
2018-01-23 21:53:05 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
r := csv.NewReader(bytes.NewReader(b))
|
|
|
|
|
|
|
|
r.Comma = ':'
|
|
|
|
r.Comment = '#'
|
2018-09-08 03:56:42 +00:00
|
|
|
r.FieldsPerRecord = 3 // username:salt:authCookie [TODO:disallowedCmdList (a,b,...)]
|
2018-01-23 21:53:05 +00:00
|
|
|
|
|
|
|
records, err := r.ReadAll()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-09-08 03:56:42 +00:00
|
|
|
|
|
|
|
recFound := false
|
2018-11-25 18:24:10 +00:00
|
|
|
for i := range records {
|
2018-01-23 21:53:05 +00:00
|
|
|
//fmt.Println(records[i])
|
|
|
|
if records[i][0] == uname {
|
2018-09-08 03:56:42 +00:00
|
|
|
recFound = true
|
2018-01-23 21:53:05 +00:00
|
|
|
records[i][1] = salt
|
|
|
|
records[i][2] = hash
|
|
|
|
}
|
|
|
|
//// csv lib doesn't preserve comment in record, so put it back
|
|
|
|
//if records[i][0][0] == '!' {
|
|
|
|
// records[i][0] = "#" + records[i][0]
|
|
|
|
//}
|
|
|
|
}
|
2018-09-08 03:56:42 +00:00
|
|
|
if !recFound {
|
|
|
|
newRec := []string{uname, salt, hash}
|
|
|
|
records = append(records, newRec)
|
|
|
|
}
|
2018-01-23 21:53:05 +00:00
|
|
|
|
2019-10-30 03:34:09 +00:00
|
|
|
outFile, err := ioutil.TempFile("", "xs-passwd")
|
2018-01-23 21:53:05 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
w := csv.NewWriter(outFile)
|
|
|
|
w.Comma = ':'
|
|
|
|
//w.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...)
|
2018-11-25 18:24:10 +00:00
|
|
|
err = w.Write([]string{"#username", "salt", "authCookie" /*, "disallowedCmdList"*/})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
err = w.WriteAll(records)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-01-23 21:53:05 +00:00
|
|
|
if err = w.Error(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.Remove(pfName)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
err = os.Rename(outFile.Name(), pfName)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|