Commits

Fazlul Shahriar committed adf615b

initial commit

Comments (0)

Files changed (7)

+# This is the official list of go-plan9-auth authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as
+#	Name or Organization <email address>
+# The email address is not required for organizations.
+
+# Please keep the list sorted.
+
+Fazlul Shahriar <fshahriar@gmail.com>
+Copyright (c) 2012 The go-plan9-auth Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// Copyright 2012 The go-plan9-auth Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+	"code.google.com/p/goplan9/plan9"
+	"code.google.com/p/goplan9/plan9/client"
+	"os/exec"
+)
+
+// GetKeyFunc takes an attribute-value-list as argument.
+// GetKey is an example implementation.
+type GetKeyFunc func(string) error
+
+// GetKey prompts the user for missing key information and
+// passes the information to factotum. Params is an
+// attribute-value-list.
+func GetKey(params string) error {
+	path := "/boot/factotum"
+	if _, err := exec.LookPath(path); err != nil {
+		// Probably using Plan 9 Port
+		path = "factotum"
+	}
+	return exec.Command(path, "-g", params).Run()
+}
+
+type control struct {
+	f *client.Fid
+}
+
+func newControl() (*control, error) {
+	fsys, err := client.MountService("factotum")
+	if err != nil {
+		return nil, err
+	}
+	fid, err := fsys.Open("ctl", plan9.ORDWR)
+	if err != nil {
+		return nil, err
+	}
+	return &control{f: fid}, nil
+}
+
+func (c *control) Close() error {
+	return c.f.Close()
+}
+
+func (c *control) AddKey(params string) error {
+	_, err := c.f.Write([]byte("key " + params))
+	return err
+}
+
+func (c *control) DeleteKey(params string) error {
+	_, err := c.f.Write([]byte("delkey " + params))
+	return err
+}
+// Copyright 2012 The go-plan9-auth Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package auth provides routines to authenticate users
+// on Plan 9 and related systems.
+package auth
+
+import (
+	"bytes"
+	"code.google.com/p/goplan9/plan9"
+	"code.google.com/p/goplan9/plan9/client"
+	"errors"
+	"fmt"
+)
+
+const (
+	rpcMaxLen = 4096
+)
+
+var rpcReplyStatuses = []string{
+	"badkey",
+	"done",
+	"error",
+	"error",
+	"needkey",
+	"ok",
+	"phase",
+	"toosmall",
+}
+
+// RPC represents an opened factotum(4) rpc file.
+type RPC struct {
+	f   *client.Fid
+	buf [rpcMaxLen]byte
+}
+
+// NewRPC creates and returns a new RPC.
+func NewRPC() (*RPC, error) {
+	fsys, err := client.MountService("factotum")
+	if err != nil {
+		return nil, err
+	}
+	fid, err := fsys.Open("rpc", plan9.ORDWR)
+	if err != nil {
+		return nil, err
+	}
+	return &RPC{f: fid}, nil
+}
+
+// Close closes the underlying rpc file.
+func (rpc *RPC) Close() error {
+	return rpc.f.Close()
+}
+
+// Call sends a RPC request to factotum and returns the response.
+func (rpc *RPC) Call(verb string, arg []byte) (string, []byte, error) {
+	if len(verb)+1+len(arg) > rpcMaxLen {
+		return "", nil, errors.New("request too big")
+	}
+	i := copy(rpc.buf[:], []byte(verb))
+	i += copy(rpc.buf[i:], []byte{' '})
+	i += copy(rpc.buf[i:], arg)
+	if _, err := rpc.f.Write(rpc.buf[:i]); err != nil {
+		return "", nil, err
+	}
+	n, err := rpc.f.Read(rpc.buf[:])
+	if err != nil {
+		return "", nil, err
+	}
+	b := rpc.buf[:n]
+	for _, s := range rpcReplyStatuses {
+		ns := len(s)
+		if bytes.HasPrefix(b, []byte(s)) {
+			if len(b) == ns {
+				return string(b), nil, nil
+			}
+			if b[ns] == ' ' {
+				return string(b[:ns]), b[ns+1:], nil
+			}
+		}
+	}
+	return "", nil, errors.New("bad rpc response: " + string(b))
+}
+
+// CallNeedKey is similar to Call except if the key involved
+// is missing or incomplete, getKey is called in an attempt
+// to obtain missing information.
+func (rpc *RPC) callNeedKey(getKey GetKeyFunc, verb string, arg []byte) (string, []byte, error) {
+	status, b, err := rpc.Call(verb, arg)
+	if err != nil {
+		return status, b, err
+	}
+	switch status {
+	case "neekkey", "badkey":
+		if getKey == nil {
+			return status, b, err
+		}
+		if err := getKey(string(b)); err != nil {
+			return status, b, err
+		}
+	}
+	return status, b, err
+}
+
+// GetUserPassword returns the username and password for the key
+// formatted using format and a. GetKey is called to obtain missing
+// information (if any).
+func (rpc *RPC) GetUserPassword(getKey GetKeyFunc, format string, a ...interface{}) (string, string, error) {
+	status, b, err := rpc.callNeedKey(getKey, "start", []byte(fmt.Sprintf(format, a...)))
+	if status != "ok" {
+		return "", "", fmt.Errorf("rpc start failed: %v", err)
+	}
+	status, b, err = rpc.callNeedKey(getKey, "read", nil)
+	if status != "ok" {
+		return "", "", fmt.Errorf("rpc read failed: %v", err)
+	}
+	up := tokenize(string(b), 2)
+	if len(up) != 2 {
+		return "", "", fmt.Errorf("bad factotum rpc response: %v", up)
+	}
+	return up[0], up[1], nil
+}
+// Copyright 2012 The go-plan9-auth Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestGetUserPassword(t *testing.T) {
+	user, pass := "gopher", "gorocks"
+	params := "dom=testing.golang.org proto=pass role=client"
+	key := params + fmt.Sprintf(" user=%s !password=%s", user, pass)
+
+	ctl, err := newControl()
+	if err != nil {
+		t.Fatalf("open factotum/ctl: %v", err)
+	}
+	defer ctl.Close()
+	if err := ctl.AddKey(key); err != nil {
+		t.Fatalf("AddKey failed: %v\n", err)
+	}
+
+	rpc, err := NewRPC()
+	if err != nil {
+		t.Fatalf("open factotum/rpc: %v\n", err)
+	}
+	defer rpc.Close()
+	user1, pass1, err := rpc.GetUserPassword(nil, params)
+	if err != nil {
+		t.Errorf("GetUserPassword failed: %v\n", err)
+	}
+	if user1 != user || pass1 != pass {
+		t.Errorf("GetUserPassword gave user=%s !password=%s; want user=%s !password=%s\n", user1, pass1, user, pass)
+	}
+
+	if err := ctl.DeleteKey(params); err != nil {
+		t.Errorf("DeleteKey failed: %v\n", err)
+	}
+}
+// Copyright 2012 The go-plan9-auth Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import "bytes"
+
+func isSpace(r rune) bool {
+	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
+}
+
+func isNotSpace(r rune) bool {
+	return !isSpace(r)
+}
+
+func getToken(b []byte) (string, []byte) {
+	quote := false
+	w := 0
+	r := 0
+	for ; r < len(b); r++ {
+		c := rune(b[r])
+		if !quote && isSpace(c) {
+			break
+		}
+		if c != '\'' {
+			b[w] = b[r]
+			w++
+			continue
+		}
+		if !quote {
+			quote = true
+			continue
+		}
+		if r+1 == len(b) || b[r+1] != '\'' {
+			quote = false
+			continue
+		}
+		// found a doubled quote
+		r++
+		b[w] = b[r]
+		w++
+	}
+	return string(b[:w]), b[r:]
+}
+
+// Tokenize returns the tokens in s separated by one or more
+// whitespaces. Maximum of n tokens are returns. Tokens can
+// be optionally quoted using rc-style quotes.
+func tokenize(s string, n int) []string {
+	tok := make([]string, n)
+	b := []byte(s)
+	i := 0
+	for ; i < n; i++ {
+		r := bytes.IndexFunc(b, isNotSpace)
+		if r < 0 {
+			break
+		}
+		tok[i], b = getToken(b[r:])
+	}
+	return tok[:i]
+}

auth/tokenize_test.go

+// Copyright 2012 The go-plan9-auth Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+	"reflect"
+	"testing"
+)
+
+type TokenizeTest struct {
+	s      string
+	n      int
+	tokens []string
+}
+
+var tokenizeTests = []TokenizeTest{
+	{"", 0, []string{}},
+	{"abc", 1, []string{"abc"}},
+	{"abc def", 2, []string{"abc", "def"}},
+	{"abc def", 5, []string{"abc", "def"}},
+	{"abc def ghi", 2, []string{"abc", "def"}},
+	{"abc 'def ghi'", 2, []string{"abc", "def ghi"}},
+	{"'gopher''s' 'go routines' are running", 2, []string{"gopher's", "go routines"}},
+}
+
+func TestTokenize(t *testing.T) {
+	for _, tt := range tokenizeTests {
+		tokens := tokenize(tt.s, tt.n)
+		if !reflect.DeepEqual(tokens, tt.tokens) {
+			t.Errorf("tokenize(%q, %d) returned %v; want %v\n",
+				tt.s, tt.n, tokens, tt.tokens)
+		}
+	}
+}