diff --git a/auth.go b/auth.go index 0cd5e0d..f26f009 100644 --- a/auth.go +++ b/auth.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/shazow/ssh-chat/internal/sanitize" "github.com/shazow/ssh-chat/set" "github.com/shazow/ssh-chat/sshd" "golang.org/x/crypto/ssh" @@ -18,7 +19,7 @@ import ( // KeyLoader loads public keys, e.g. from an authorized_keys file. // It must return a nil slice on error. -type KeyLoader func() ([]ssh.PublicKey, error) +type KeyLoader func() ([]ssh.PublicKey, []string, error) // ErrNotAllowed Is the error returned when a key is checked that is not allowlisted, // when allowlisting is enabled. @@ -61,6 +62,8 @@ type Auth struct { banned *set.Set allowlist *set.Set ops *set.Set + keynamesByFingerprint map[string]string + fingerprintsByKeyname map[string]string settingsMu sync.RWMutex allowlistMode bool @@ -76,6 +79,8 @@ func NewAuth() *Auth { banned: set.New(), allowlist: set.New(), ops: set.New(), + keynamesByFingerprint: make(map[string]string), + fingerprintsByKeyname: make(map[string]string), } } @@ -158,7 +163,8 @@ func (a *Auth) CheckPassphrase(passphrase string) error { } // Op sets a public key as a known operator. -func (a *Auth) Op(key ssh.PublicKey, d time.Duration) { +// comment is the string that follows the public key in openssh authorized_keys format +func (a *Auth) Op(key ssh.PublicKey, comment string, d time.Duration) { if key == nil { return } @@ -168,6 +174,17 @@ func (a *Auth) Op(key ssh.PublicKey, d time.Duration) { } else { a.ops.Set(authItem) } + + // nick registration + fields := strings.Fields(comment) + if len(fields) >0 { + keyname := sanitize.Name(fields[0]) + if len(a.fingerprintsByKeyname[keyname]) <1 { + a.fingerprintsByKeyname[ keyname ] = authItem.Key() + a.keynamesByFingerprint[ authItem.Key() ] = keyname + } + } + logger.Debugf("Added to ops: %q (for %s)", authItem.Key(), d) } @@ -193,7 +210,8 @@ func (a *Auth) ReloadOps() error { } // Allowlist will set a public key as a allowlisted user. -func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) { +// comment is the string that follows the public key in openssh authorized_keys format +func (a *Auth) Allowlist(key ssh.PublicKey, comment string, d time.Duration) { if key == nil { return } @@ -204,6 +222,18 @@ func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) { } else { err = a.allowlist.Set(authItem) } + + // nick registration + fields := strings.Fields(comment) + if len(fields) >0 { + keyname := sanitize.Name(fields[0]) + if len(a.fingerprintsByKeyname[keyname]) <1 { + a.fingerprintsByKeyname[ keyname ] = authItem.Key() + a.keynamesByFingerprint[ authItem.Key() ] = keyname + } + } + + if err == nil { logger.Debugf("Added to allowlist: %q (for %s)", authItem.Key(), d) } else { @@ -226,13 +256,18 @@ func (a *Auth) ReloadAllowlist() error { return addFromLoader(a.allowlistLoader, a.Allowlist) } -func addFromLoader(loader KeyLoader, adder func(ssh.PublicKey, time.Duration)) error { +func addFromLoader(loader KeyLoader, adder func(ssh.PublicKey, string, time.Duration)) error { if loader == nil { return nil } - keys, err := loader() - for _, key := range keys { - adder(key, 0) + keys, comments, err := loader() + var comment string + for index, key := range keys { + comment = "" + if len(comments) > index { + comment = comments[index] + } + adder(key, comment, 0) } return err } diff --git a/cmd/ssh-chat/cmd.go b/cmd/ssh-chat/cmd.go index 96e5a5a..1566a16 100644 --- a/cmd/ssh-chat/cmd.go +++ b/cmd/ssh-chat/cmd.go @@ -200,28 +200,30 @@ func loaderFromFile(path string, logger *golog.Logger) sshchat.KeyLoader { if path == "" { return nil } - return func() ([]ssh.PublicKey, error) { + return func() ([]ssh.PublicKey, []string, error) { file, err := os.Open(path) if err != nil { - return nil, err + return nil, nil, err } defer file.Close() var keys []ssh.PublicKey + var comments []string scanner := bufio.NewScanner(file) for scanner.Scan() { - key, _, _, _, err := ssh.ParseAuthorizedKey(scanner.Bytes()) + key, comment, _, _, err := ssh.ParseAuthorizedKey(scanner.Bytes()) if err != nil { if err.Error() == "ssh: no key found" { continue // Skip line } - return nil, err + return nil, nil, err } keys = append(keys, key) + comments = append(comments, comment) } if keys == nil { logger.Warning("file", path, "contained no keys") } - return keys, nil + return keys, comments, nil } } diff --git a/host.go b/host.go index be5eeab..62276ab 100644 --- a/host.go +++ b/host.go @@ -626,7 +626,7 @@ func (h *Host) InitCommands(c *chat.Commands) { member.IsOp = opValue id := member.Identifier.(*Identity) - h.auth.Op(id.PublicKey(), until) + h.auth.Op(id.PublicKey(), "", until) var body string if opValue { @@ -780,7 +780,7 @@ func (h *Host) InitCommands(c *chat.Commands) { if pk == nil { noKeyUsers = append(noKeyUsers, user.Identifier.Name()) } else { - h.auth.Allowlist(pk, 0) + h.auth.Allowlist(pk, "", 0) } } return nil @@ -876,9 +876,9 @@ func (h *Host) InitCommands(c *chat.Commands) { case "off": h.auth.SetAllowlistMode(false) case "add": - replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 0) }) + replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, "", 0) }) case "remove": - replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 1) }) + replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, "", 1) }) case "import": replyLines, err = allowlistImport(args[1:]) case "reload":