1
0
Fork 0
mirror of https://github.com/Luzifer/password.git synced 2024-12-20 04:41:17 +00:00

Fix: Vendor new dependencies

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2017-10-06 22:59:38 +02:00
parent 495cb05264
commit 8af625be60
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
392 changed files with 81886 additions and 1 deletions

20
Gopkg.lock generated
View file

@ -1,6 +1,12 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/Luzifer/go_helpers"
packages = ["str"]
revision = "e31c3a2659d3f4901f696692cfe98bd0eb5168f9"
version = "v2.2.0"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
@ -31,9 +37,21 @@
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/tredoe/osutil"
packages = ["user/crypt","user/crypt/apr1_crypt","user/crypt/common","user/crypt/md5_crypt","user/crypt/sha256_crypt","user/crypt/sha512_crypt"]
revision = "7d3ee1afa71c90fd1514c8f557ae6c5f414208eb"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["bcrypt","blowfish"]
revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "14edde36d0973957ba17c13f4fc38f5e8997e23d9ffa3364f48757f0c61e5924"
inputs-digest = "236339331c5c38a149e57813a37edb224e5f7f6fd1414cd7970e07981b425b77"
solver-name = "gps-cdcl"
solver-version = 1

5
vendor/github.com/Luzifer/go_helpers/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,5 @@
language: go
go:
- 1.7
- tip

34
vendor/github.com/Luzifer/go_helpers/History.md generated vendored Normal file
View file

@ -0,0 +1,34 @@
# 2.2.0 / 2017-04-13
* Add HTTPLogHandler
# 2.1.0 / 2016-12-23
* Add time.Duration formatter
# 2.0.0 / 2016-10-12
* Drop Go1.5 / Go1.6 support with using contexts
* Add github-binary update helper
# 1.4.0 / 2016-05-29
* Added environment helpers
# 1.3.0 / 2016-05-18
* Added AccessLogResponseWriter
# 1.2.0 / 2016-05-16
* Added helper to find binaries in path or directory
# 1.1.0 / 2016-05-06
* Added Haversine helper functions
1.0.0 / 2016-04-23
==================
* First versioned revision for use with gopkg.in

View file

@ -0,0 +1,37 @@
package accessLogger
import (
"fmt"
"net/http"
"strconv"
)
type AccessLogResponseWriter struct {
StatusCode int
Size int
http.ResponseWriter
}
func New(res http.ResponseWriter) *AccessLogResponseWriter {
return &AccessLogResponseWriter{
StatusCode: 200,
Size: 0,
ResponseWriter: res,
}
}
func (a *AccessLogResponseWriter) Write(out []byte) (int, error) {
s, err := a.ResponseWriter.Write(out)
a.Size += s
return s, err
}
func (a *AccessLogResponseWriter) WriteHeader(code int) {
a.StatusCode = code
a.ResponseWriter.WriteHeader(code)
}
func (a *AccessLogResponseWriter) HTTPResponseType() string {
return fmt.Sprintf("%sxx", strconv.FormatInt(int64(a.StatusCode), 10)[0])
}

61
vendor/github.com/Luzifer/go_helpers/duration/time.go generated vendored Normal file
View file

@ -0,0 +1,61 @@
package duration
import (
"bytes"
"math"
"strings"
"text/template"
"time"
"github.com/leekchan/gtf"
)
const defaultDurationFormat = `{{if gt .Years 0}}{{.Years}} year{{.Years|pluralize "s"}}, {{end}}` +
`{{if gt .Days 0}}{{.Days}} day{{.Days|pluralize "s"}}, {{end}}` +
`{{if gt .Hours 0}}{{.Hours}} hour{{.Hours|pluralize "s"}}, {{end}}` +
`{{if gt .Minutes 0}}{{.Minutes}} minute{{.Minutes|pluralize "s"}}, {{end}}` +
`{{if gt .Seconds 0}}{{.Seconds}} second{{.Seconds|pluralize "s"}}{{end}}`
func HumanizeDuration(in time.Duration) string {
f, err := CustomHumanizeDuration(in, defaultDurationFormat)
if err != nil {
panic(err)
}
return strings.Trim(f, " ,")
}
func CustomHumanizeDuration(in time.Duration, tpl string) (string, error) {
result := struct{ Years, Days, Hours, Minutes, Seconds int64 }{}
in = time.Duration(math.Abs(float64(in)))
for in > 0 {
switch {
case in > 365.25*24*time.Hour:
result.Years = int64(in / (365 * 24 * time.Hour))
in = in - time.Duration(result.Years)*365*24*time.Hour
case in > 24*time.Hour:
result.Days = int64(in / (24 * time.Hour))
in = in - time.Duration(result.Days)*24*time.Hour
case in > time.Hour:
result.Hours = int64(in / time.Hour)
in = in - time.Duration(result.Hours)*time.Hour
case in > time.Minute:
result.Minutes = int64(in / time.Minute)
in = in - time.Duration(result.Minutes)*time.Minute
default:
result.Seconds = int64(in / time.Second)
in = 0
}
}
tmpl, err := template.New("timeformat").Funcs(template.FuncMap(gtf.GtfFuncMap)).Parse(tpl)
if err != nil {
return "", err
}
buf := bytes.NewBuffer([]byte{})
tmpl.Execute(buf, result)
return buf.String(), nil
}

View file

@ -0,0 +1,35 @@
package duration
import (
"testing"
"time"
)
func TestCustomFormat(t *testing.T) {
d := 389*24*time.Hour +
12*time.Hour +
31*time.Minute +
54*time.Second +
346*time.Millisecond
f := `{{.Years}} - {{.Days}} - {{.Hours}} - {{.Minutes}} - {{.Seconds}}`
e := `1 - 24 - 12 - 31 - 54`
if s, _ := CustomHumanizeDuration(d, f); s != e {
t.Errorf("Got unexpected result: expected=%q result=%q", e, s)
}
}
func TestDefaultFormat(t *testing.T) {
d := 389*24*time.Hour +
12*time.Hour +
31*time.Minute +
54*time.Second +
346*time.Millisecond
e := `1 year, 24 days, 12 hours, 31 minutes, 54 seconds`
if s := HumanizeDuration(d); s != e {
t.Errorf("Got unexpected result: expected=%q result=%q", e, s)
}
}

26
vendor/github.com/Luzifer/go_helpers/env/env.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
package env
import "strings"
// ListToMap converts a list of strings in format KEY=VALUE into a map
func ListToMap(list []string) map[string]string {
out := map[string]string{}
for _, entry := range list {
if len(entry) == 0 || entry[0] == '#' {
continue
}
parts := strings.SplitN(entry, "=", 2)
out[parts[0]] = strings.Trim(parts[1], "\"")
}
return out
}
// MapToList converts a map into a list of strings in format KEY=VALUE
func MapToList(envMap map[string]string) []string {
out := []string{}
for k, v := range envMap {
out = append(out, k+"="+v)
}
return out
}

View file

@ -0,0 +1,13 @@
package env_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestEnv(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Env Suite")
}

55
vendor/github.com/Luzifer/go_helpers/env/env_test.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
package env_test
import (
"sort"
. "github.com/Luzifer/go_helpers/env"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Env", func() {
Context("ListToMap", func() {
var (
list = []string{
"FIRST_KEY=firstvalue",
"SECOND_KEY=secondvalue",
"WEIRD=",
"",
}
emap = map[string]string{
"FIRST_KEY": "firstvalue",
"SECOND_KEY": "secondvalue",
"WEIRD": "",
}
)
It("should convert the list in the expected way", func() {
Expect(ListToMap(list)).To(Equal(emap))
})
})
Context("MapToList", func() {
var (
list = []string{
"FIRST_KEY=firstvalue",
"SECOND_KEY=secondvalue",
"WEIRD=",
}
emap = map[string]string{
"FIRST_KEY": "firstvalue",
"SECOND_KEY": "secondvalue",
"WEIRD": "",
}
)
It("should convert the map in the expected way", func() {
l := MapToList(emap)
sort.Strings(l) // Workaround: The test needs the elements to be in same order
Expect(l).To(Equal(list))
})
})
})

View file

@ -0,0 +1,13 @@
package float_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestFloat(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Float Suite")
}

14
vendor/github.com/Luzifer/go_helpers/float/round.go generated vendored Normal file
View file

@ -0,0 +1,14 @@
package float
import "math"
// Round returns a float rounded according to "Round to nearest, ties away from zero" IEEE floaing point rounding rule
func Round(x float64) float64 {
var absx, y float64
absx = math.Abs(x)
y = math.Floor(absx)
if absx-y >= 0.5 {
y += 1.0
}
return math.Copysign(y, x)
}

View file

@ -0,0 +1,35 @@
package float_test
import (
"math"
. "github.com/Luzifer/go_helpers/float"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Round", func() {
It("should match the example table of IEEE 754 rules", func() {
Expect(Round(11.5)).To(Equal(12.0))
Expect(Round(12.5)).To(Equal(13.0))
Expect(Round(-11.5)).To(Equal(-12.0))
Expect(Round(-12.5)).To(Equal(-13.0))
})
It("should have correct rounding for numbers near 0.5", func() {
Expect(Round(0.499999999997)).To(Equal(0.0))
Expect(Round(-0.499999999997)).To(Equal(0.0))
})
It("should be able to handle +/-Inf", func() {
Expect(Round(math.Inf(1))).To(Equal(math.Inf(1)))
Expect(Round(math.Inf(-1))).To(Equal(math.Inf(-1)))
})
It("should be able to handle NaN", func() {
Expect(math.IsNaN(Round(math.NaN()))).To(Equal(true))
})
})

213
vendor/github.com/Luzifer/go_helpers/github/updater.go generated vendored Normal file
View file

@ -0,0 +1,213 @@
package github
import (
"bufio"
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"runtime"
"strings"
"text/template"
"time"
update "github.com/inconshreveable/go-update"
)
const (
defaultTimeout = 60 * time.Second
defaultNamingScheme = `{{.ProductName}}_{{.GOOS}}_{{.GOARCH}}{{.EXT}}`
)
var (
errReleaseNotFound = errors.New("Release not found")
)
// Updater is the core struct of the update library holding all configurations
type Updater struct {
repo string
myVersion string
HTTPClient *http.Client
RequestTimeout time.Duration
Context context.Context
Filename string
releaseCache string
}
// NewUpdater initializes a new Updater and tries to guess the Filename
func NewUpdater(repo, myVersion string) (*Updater, error) {
var err error
u := &Updater{
repo: repo,
myVersion: myVersion,
HTTPClient: http.DefaultClient,
RequestTimeout: defaultTimeout,
Context: context.Background(),
}
u.Filename, err = u.compileFilename()
return u, err
}
// HasUpdate checks which tag was used in the latest version and compares it to the current version. If it differs the function will return true. No comparison is done to determine whether the found version is higher than the current one.
func (u *Updater) HasUpdate(forceRefresh bool) (bool, error) {
if forceRefresh {
u.releaseCache = ""
}
latest, err := u.getLatestRelease()
switch err {
case nil:
return u.myVersion != latest, nil
case errReleaseNotFound:
return false, nil
default:
return false, err
}
}
// Apply downloads the new binary from Github, fetches the SHA256 sum from the SHA256SUMS file and applies the update to the currently running binary
func (u *Updater) Apply() error {
updateAvailable, err := u.HasUpdate(false)
if err != nil {
return err
}
if !updateAvailable {
return nil
}
checksum, err := u.getSHA256(u.Filename)
if err != nil {
return err
}
newRelease, err := u.getFile(u.Filename)
if err != nil {
return err
}
defer newRelease.Close()
return update.Apply(newRelease, update.Options{
Checksum: checksum,
})
}
func (u Updater) getSHA256(filename string) ([]byte, error) {
shaFile, err := u.getFile("SHA256SUMS")
if err != nil {
return nil, err
}
defer shaFile.Close()
scanner := bufio.NewScanner(shaFile)
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, u.Filename) {
continue
}
return hex.DecodeString(line[0:64])
}
return nil, fmt.Errorf("No SHA256 found for file %q", u.Filename)
}
func (u Updater) getFile(filename string) (io.ReadCloser, error) {
release, err := u.getLatestRelease()
if err != nil {
return nil, err
}
requestURL := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", u.repo, release, filename)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return nil, err
}
ctx, _ := context.WithTimeout(u.Context, u.RequestTimeout)
res, err := u.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("File not found: %q", requestURL)
}
return res.Body, nil
}
func (u *Updater) getLatestRelease() (string, error) {
if u.releaseCache != "" {
return u.releaseCache, nil
}
result := struct {
TagName string `json:"tag_name"`
}{}
requestURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", u.repo)
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return "", err
}
ctx, cancel := context.WithTimeout(u.Context, u.RequestTimeout)
defer cancel()
res, err := u.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return "", err
}
defer res.Body.Close()
if err = json.NewDecoder(res.Body).Decode(&result); err != nil {
return "", err
}
if res.StatusCode != 200 || result.TagName == "" {
return "", errReleaseNotFound
}
u.releaseCache = result.TagName
return result.TagName, nil
}
func (u Updater) compileFilename() (string, error) {
repoName := strings.Split(u.repo, "/")
if len(repoName) != 2 {
return "", errors.New("Repository name not in format <owner>/<repository>")
}
tpl, err := template.New("filename").Parse(defaultNamingScheme)
if err != nil {
return "", err
}
var ext string
if runtime.GOOS == "windows" {
ext = ".exe"
}
buf := bytes.NewBuffer([]byte{})
if err = tpl.Execute(buf, map[string]interface{}{
"GOOS": runtime.GOOS,
"GOARCH": runtime.GOARCH,
"EXT": ext,
"ProductName": repoName[1],
}); err != nil {
return "", err
}
return buf.String(), nil
}

View file

@ -0,0 +1,35 @@
package http
import (
"log"
"net/http"
"time"
"github.com/Luzifer/go_helpers/accessLogger"
)
type HTTPLogHandler struct {
Handler http.Handler
}
func NewHTTPLogHandler(h http.Handler) http.Handler {
return HTTPLogHandler{Handler: h}
}
func (l HTTPLogHandler) ServeHTTP(res http.ResponseWriter, r *http.Request) {
start := time.Now()
ares := accessLogger.New(res)
l.Handler.ServeHTTP(ares, r)
log.Printf("%s - \"%s %s\" %d %d \"%s\" \"%s\" %s",
r.RemoteAddr,
r.Method,
r.URL.Path,
ares.StatusCode,
ares.Size,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
time.Since(start),
)
}

View file

@ -0,0 +1,21 @@
package position
import "math"
const (
earthRadius = float64(6371)
)
func Haversine(lonFrom float64, latFrom float64, lonTo float64, latTo float64) (distance float64) {
var deltaLat = (latTo - latFrom) * (math.Pi / 180)
var deltaLon = (lonTo - lonFrom) * (math.Pi / 180)
var a = math.Sin(deltaLat/2)*math.Sin(deltaLat/2) +
math.Cos(latFrom*(math.Pi/180))*math.Cos(latTo*(math.Pi/180))*
math.Sin(deltaLon/2)*math.Sin(deltaLon/2)
var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
distance = earthRadius * c
return
}

View file

@ -0,0 +1,34 @@
package position_test
import (
. "github.com/Luzifer/go_helpers/position"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Haversine", func() {
var testCases = []struct {
SourceLat float64
SourceLon float64
DestLat float64
DestLon float64
Distance float64
}{
{50.066389, -5.714722, 58.643889, -3.070000, 968.8535441168448},
{50.063995, -5.609464, 53.553027, 9.993782, 1137.894906816002},
{53.553027, 9.993782, 53.554528, 9.991357, 0.23133816528015647},
{50, 9, 51, 9, 111.19492664455873},
{0, 9, 0, 10, 111.19492664455873},
{1, 0, -1, 0, 222.38985328911747},
}
It("should have the documented distance", func() {
for i := range testCases {
tc := testCases[i]
Expect(Haversine(tc.SourceLon, tc.SourceLat, tc.DestLon, tc.DestLat)).To(Equal(tc.Distance))
}
})
})

View file

@ -0,0 +1,13 @@
package position_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestPosition(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Position Suite")
}

21
vendor/github.com/Luzifer/go_helpers/str/slice.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
package str
// AppendIfMissing adds a string to a slice when it's not present yet
func AppendIfMissing(slice []string, s string) []string {
for _, e := range slice {
if e == s {
return slice
}
}
return append(slice, s)
}
// StringInSlice checks for the existence of a string in the slice
func StringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

52
vendor/github.com/Luzifer/go_helpers/str/slice_test.go generated vendored Normal file
View file

@ -0,0 +1,52 @@
package str_test
import (
. "github.com/Luzifer/go_helpers/str"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Slice", func() {
Context("AppendIfMissing", func() {
var sl = []string{
"test1",
"test2",
"test3",
}
It("should not append existing elements", func() {
Expect(len(AppendIfMissing(sl, "test1"))).To(Equal(3))
Expect(len(AppendIfMissing(sl, "test2"))).To(Equal(3))
Expect(len(AppendIfMissing(sl, "test3"))).To(Equal(3))
})
It("should append not existing elements", func() {
Expect(len(AppendIfMissing(sl, "test4"))).To(Equal(4))
Expect(len(AppendIfMissing(sl, "test5"))).To(Equal(4))
Expect(len(AppendIfMissing(sl, "test6"))).To(Equal(4))
})
})
Context("StringInSlice", func() {
var sl = []string{
"test1",
"test2",
"test3",
}
It("should find elements of slice", func() {
Expect(StringInSlice("test1", sl)).To(Equal(true))
Expect(StringInSlice("test2", sl)).To(Equal(true))
Expect(StringInSlice("test3", sl)).To(Equal(true))
})
It("should not find elements not in slice", func() {
Expect(StringInSlice("test4", sl)).To(Equal(false))
Expect(StringInSlice("test5", sl)).To(Equal(false))
Expect(StringInSlice("test6", sl)).To(Equal(false))
})
})
})

View file

@ -0,0 +1,13 @@
package str_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestStr(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Str Suite")
}

54
vendor/github.com/Luzifer/go_helpers/which/which.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
package which
import (
"errors"
"os"
"path"
"strings"
)
// Common named errors to match in programs using this library
var (
ErrBinaryNotFound = errors.New("Requested binary was not found")
ErrNoSearchSpecified = errors.New("You need to specify a binary to search")
)
// FindInPath searches the specified binary in directories listed in $PATH and returns first match
func FindInPath(binary string) (string, error) {
pathEnv := os.Getenv("PATH")
if len(pathEnv) == 0 {
return "", errors.New("Found empty $PATH, not able to search $PATH")
}
for _, part := range strings.Split(pathEnv, ":") {
if len(part) == 0 {
continue
}
if found, err := FindInDirectory(binary, part); err != nil {
return "", err
} else if found {
return path.Join(part, binary), nil
}
}
return "", ErrBinaryNotFound
}
// FindInDirectory checks whether the specified file is present in the directory
func FindInDirectory(binary, directory string) (bool, error) {
if len(binary) == 0 {
return false, ErrNoSearchSpecified
}
_, err := os.Stat(path.Join(directory, binary))
switch {
case err == nil:
return true, nil
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}

View file

@ -0,0 +1,13 @@
package which_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestWhich(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Which Suite")
}

View file

@ -0,0 +1,63 @@
package which_test
import (
. "github.com/Luzifer/go_helpers/which"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Which", func() {
var (
result string
err error
found bool
)
Context("With a file available on linux systems", func() {
BeforeEach(func() {
found, err = FindInDirectory("bash", "/bin")
})
It("should not have errored", func() {
Expect(err).NotTo(HaveOccurred())
})
It("should have found the binary at /bin/bash", func() {
Expect(found).To(BeTrue())
})
})
Context("Searching bash on the system", func() {
BeforeEach(func() {
result, err = FindInPath("bash")
})
It("should not have errored", func() {
Expect(err).NotTo(HaveOccurred())
})
It("should have a result", func() {
Expect(len(result)).NotTo(Equal(0))
})
})
Context("Searching a non existent file", func() {
BeforeEach(func() {
result, err = FindInPath("dfqoiwurgtqi3uegrds")
})
It("should have errored", func() {
Expect(err).To(Equal(ErrBinaryNotFound))
})
})
Context("Searching an empty file", func() {
BeforeEach(func() {
result, err = FindInPath("")
})
It("should have errored", func() {
Expect(err).To(Equal(ErrNoSearchSpecified))
})
})
})

16
vendor/github.com/tredoe/osutil/AUTHORS.md generated vendored Normal file
View file

@ -0,0 +1,16 @@
# Authors
This is the official list of authors for copyright purposes.
This file is distinct from the 'CONTRIBUTORS' file. See the latter for an explanation.
Names should be added to this file as:
Name or Organization <email address> / (url address)
(The email address is not required for organizations)
Please keep the list sorted.
## Code
* Jonas mg (https://github.com/tredoe)

18
vendor/github.com/tredoe/osutil/CONTRIBUTORS.md generated vendored Normal file
View file

@ -0,0 +1,18 @@
# Contributors
This is the official list of people who can contribute (and typically
have contributed) to the repository.
The 'AUTHORS' file lists the copyright holders; this file lists people. For
example, the employees of an organization are listed here but not in 'AUTHORS',
because the organization holds the copyright.
Names should be added to this file as:
Name <email address> / (url address)
Please keep the list sorted.
## Code
* Jonas mg (https://github.com/tredoe)

374
vendor/github.com/tredoe/osutil/LICENSE-MPL.txt generated vendored Normal file
View file

@ -0,0 +1,374 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

25
vendor/github.com/tredoe/osutil/README.md generated vendored Normal file
View file

@ -0,0 +1,25 @@
osutil
======
Access to operating system functionality dependent of every platform and
utility packages for the Shell.
+ config/env: set persistent environment variables
+ config/shconf: parser and scanner for the configuration in format shell-variable
+ distro: detects the Linux distribution
+ file: common operations in files
+ pkg: basic operations for the management of packages in operating systems
+ sh: interprets a command line like it is done in the Bash shell
+ user: provides access to UNIX users database in local files
+ user/crypt: password hashing used in UNIX
[Documentation online](http://godoc.org/github.com/tredoe/osutil)
## License
The source files are distributed under the [Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
unless otherwise noted.
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
if you have further questions regarding the license.
* * *
*Generated by [Gowizard](https://github.com/tredoe/wizard)*

36
vendor/github.com/tredoe/osutil/TODO.md generated vendored Normal file
View file

@ -0,0 +1,36 @@
## pkg
Check the error code returned at testing.
## file
edit.go: the comment character used by default
Copy with options: (overwrite)
type option int
const (
_ option = iota
ARCHIVE // preserve all attributes
BACKUP // make a backup of each existing destination file
ATTRIBUTES // only copy attributes
HLINK // make hard link instead of copying
SLINK // make symbolic links instead of copying
DEREFERENCE // always follow symbolic links in SOURCE
RECURSIVE // copy directories recursively
UPDATE // copy only when the source file is newer than the destination file
// or when the destination file is missing
)
## sh
doc. about the usage of Installer since it uses a logger into a directory installed
by such tool.

74
vendor/github.com/tredoe/osutil/config/env/Info.md generated vendored Normal file
View file

@ -0,0 +1,74 @@
Persistent variables shell
==========================
Environment variables are a per process thing. So something needs to store the
state to disk and load it in to it's environment and pass that environment on to
it's child processes.
This is generally done by the shell, I don't think there is a cross shell way to
tell a shell to persist an environment variable to it's configuration.
https://help.ubuntu.com/community/EnvironmentVariables
## Persistent environment variables
The names of environment variables are case sensitive.
It is a common practice to name all environment variables with only English
capital letters and underscore (_) signs.
Note: The shell techniques explained in the following sections apply to the
Bourne Shell family of command line shells, which includes sh, ksh, and bash,
which is the default shell shipped with Ubuntu. The commands may be different on
other shells such as csh.
### System-wide environment variables
Environment variable settings that affect the system as a whole (rather than
just a particular user) should not be placed in any of the many system-level
scripts that get executed when the system or the desktop session are loaded, but
into
/etc/environment - This file is specifically meant for system-wide
environment variable settings. It is not a script file, but rather consists
of assignment expressions, one per line. Specifically, this file stores the
system-wide locale and path settings.
Note: Any variables added to these locations will not be reflected when invoking
them with a sudo command, as sudo has a default policy of resetting the
Environment and setting a secure path (this behavior is defined in
/etc/sudoers). As a workaround, you can use "sudo su" that will provide a shell
with root privileges but retaining any modified PATH variables.
Note: When dealing with end-user/home desktop systems may be appropriate to
place settings in the user's ~/.pam_environment files discussed above rather
than the system-wide ones, since those files do not require one to utilize root
privileges in order to edit and are easily moved between systems.
Note: Some systems now use an envvar.sh placed in the /etc/profile.d/ directory
to set system wide environment strings.
### Session-wide environment variables
Environment variable settings that should affect just a particular user (rather
than the system as a whole) should be set into:
~/.pam_environment - This file is specifically meant for setting a user's
environment. It is not a script file, but rather consists of assignment
expressions, one per line.
PATH DEFAULT=${PATH}:${HOME}/MyPrograms
Note: Using .pam_environment requires a re-login in order to initialize the
variables. Restarting just the terminal is not sufficient to be able to use the
variables.
If you are using KDE, see http://userbase.kde.org/Session_Environment_Variables/en
## Windows
http://support.microsoft.com/kb/310519

342
vendor/github.com/tredoe/osutil/config/env/env.go generated vendored Normal file
View file

@ -0,0 +1,342 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package env implements the setting of persistent environment variables.
//
// The environment variables must be named with only English capital letters and
// underscore signs (_).
package env
import (
"bytes"
"errors"
"fmt"
"os"
"path"
"github.com/tredoe/osutil/config/shconf"
"github.com/tredoe/osutil/file"
"github.com/tredoe/osutil/user"
)
var _IS_ROOT bool
func init() {
if os.Getuid() == 0 {
_IS_ROOT = true
}
}
// == Errors
var ErrNoRoot = errors.New("you have to be Root")
// NoShellError represents an account without shell.
type NoShellError string
func (e NoShellError) Error() string {
return "shell not found for user: " + string(e)
}
// NoHomeError represents an account without home directory.
type NoHomeError string
func (e NoHomeError) Error() string {
return "home directory not found for user: " + string(e)
}
var errKey = errors.New("environment variables must use only English capital" +
" letters and underscore signs")
// ===
// Families of shell commands.
var (
shFamily = []string{"bash", "ksh", "sh"}
cshFamily = []string{"tcsh", "csh"}
)
// shellType represents a type of shell.
type shellType int
const (
_SHELL_SH shellType = 1 + iota
_SHELL_CSH
)
// == Settings for each Shell family
//
var systemFile = []string{
_SHELL_SH: "/etc/environment",
//_SHELL_CSH: "",
}
var userFile = []string{
_SHELL_SH: ".pam_environment",
//_SHELL_CSH: "",
}
// To set environment variables that affect your whole session, KDE will execute
// any script it finds in '$HOME/.kde/env' whose filename ends in '.sh', and it
// will maintain all the environment variables set by them.
var kdeFile = ".kde/env"
// settings represents the files of configuration for an user, according to the shell.
type settings struct {
global string // System wide
user string
kde string
useKDE bool
}
// Settings files of the caller.
var _SETTINGS settings
// Sets the settings files of the caller.
func init() {
var err error
_SETTINGS, err = getSettingsForUid(os.Getuid())
if err != nil {
panic(err)
}
}
// getSettingsForUid returns the settings files of the given user id.
func getSettingsForUid(id int) (settings, error) {
u, err := user.LookupUID(id)
if err != nil {
return settings{}, err
}
if u.Shell == "" {
return settings{}, NoShellError(u.Name)
}
if u.Dir == "" {
return settings{}, NoHomeError(u.Name)
}
shell := path.Base(u.Shell)
_settings := settings{
global: systemFile[_SHELL_SH],
user: path.Join(u.Dir, userFile[_SHELL_SH]),
kde: path.Join(u.Dir, kdeFile),
}
info, err := os.Stat(_settings.kde)
if err == nil && info.IsDir() {
_settings.useKDE = true
}
for _, v := range shFamily {
if v == shell {
return _settings, nil
}
}
/*for _, v := range cshFamily {
if v == shell {
return _settings, nil
}
}*/
return settings{}, fmt.Errorf("shell unsopported: %s", shell)
}
// == Set variables
//
// _Set sets the value named by the key in the given filename.
func _Set(filename, key, value string) error {
// Check if the key is already used.
conf, err := shconf.ParseFile(filename)
if err != nil {
if err != os.ErrNotExist {
return err
}
println("ErrNotExist") //TODO: remove
} else {
if _, err = conf.Get(key); err != shconf.ErrKey {
panic("OPS")
}
}
return file.AppendString(filename, key+string(conf.Separator())+value)
}
// _MSet sets multiple values named by the keys in the given filename.
func _MSet(filename string, keys, values []string) error {
if len(keys) != len(values) {
return fmt.Errorf("number of keys is different to number of values")
}
// Check if the key is already used.
conf, err := shconf.ParseFile(filename)
if err != nil {
if err != os.ErrNotExist {
return err
}
println("ErrNotExist") //TODO: remove
}
var buf bytes.Buffer
for i, key := range keys {
if _, err = conf.Get(key); err != nil {
continue // TODO: log key already set.
}
buf.WriteString(key)
buf.Write(conf.Separator())
buf.WriteString(values[i])
buf.WriteByte('\n')
}
return file.Append(filename, buf.Bytes())
}
// == Set session-wide variables
// Set sets the value of the environment variable named by the key that
// affects the current user.
// It returns an error, if any.
func Set(key, value string) error {
err := _Set(_SETTINGS.user, key, value)
if err != nil {
return err
}
if _SETTINGS.useKDE {
return _Set(_SETTINGS.kde, key, value)
}
return nil
}
// MSet sets multiple values of the environment variables named by the keys
// that affects the current user.
// It returns an error, if any.
func MSet(keys, values []string) error {
err := _MSet(_SETTINGS.user, keys, values)
if err != nil {
return err
}
if _SETTINGS.useKDE {
return _MSet(_SETTINGS.kde, keys, values)
}
return nil
}
// SetForUid sets the value of the environment variable named by the key that
// affects a particular user.
// It returns an error, if any.
func SetForUid(id int, key, value string) error {
_settings, err := getSettingsForUid(id)
if err != nil {
return err
}
if err = _Set(_settings.user, key, value); err != nil {
return err
}
if _settings.useKDE {
return _Set(_settings.kde, key, value)
}
return nil
}
// MSetForUid sets multiple values of the environment variables named by the
// keys that affects a particular user.
// It returns an error, if any.
func MSetForUid(id int, keys, values []string) error {
_settings, err := getSettingsForUid(id)
if err != nil {
return err
}
if err = _MSet(_settings.user, keys, values); err != nil {
return err
}
if _settings.useKDE {
return _MSet(_settings.kde, keys, values)
}
return nil
}
// == Set system-wide variables
// Setsys sets the value of the environment variable named by the key that
// affects the system as a whole. You must be Root.
// It returns an error, if any.
func Setsys(key, value string) error {
if !_IS_ROOT {
return ErrNoRoot
}
return _Set(_SETTINGS.global, key, value)
}
// MSetsys sets multiple values of the environment variables named by the keys
// that affects the system as a whole. You must be Root.
// It returns an error, if any.
func MSetsys(keys, values []string) error {
if !_IS_ROOT {
return ErrNoRoot
}
return _MSet(_SETTINGS.global, keys, values)
}
// SetsysForUid sets the value of the environment variable named by the key that
// affects the system as a whole. You must be Root.
// It returns an error, if any.
func SetsysForUid(id int, key, value string) error {
if !_IS_ROOT {
return ErrNoRoot
}
_settings, err := getSettingsForUid(id)
if err != nil {
return err
}
return _Set(_settings.global, key, value)
}
// MSetsysForUid sets multiple values of the environment variables named by the
// keys that affects the system as a whole. You must be Root.
// It returns an error, if any.
func MSetsysForUid(id int, keys, values []string) error {
if !_IS_ROOT {
return ErrNoRoot
}
_settings, err := getSettingsForUid(id)
if err != nil {
return err
}
return _MSet(_settings.global, keys, values)
}
// == Unset variables
//
// _Unset unsets the key in the given filename.
/*func _Unset(filename, key string) error {
}*/
// == Utility
//
// It is a common practice to name all environment variables with only English
// capital letters and underscore (_) signs.
// checkKey reports whether the key uses English capital letters and underscore
// signs.
func checkKey(s string) {
for _, char := range s {
if (char < 'A' || char > 'Z') && char != '_' {
panic(errKey)
}
}
}

34
vendor/github.com/tredoe/osutil/config/env/env_test.go generated vendored Normal file
View file

@ -0,0 +1,34 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package env
import "testing"
func TestCheckKey(t *testing.T) {
caught := false
defer func() {
if x := recover(); x != nil {
if x != errKey {
t.Fatal("expected to get error: errKey")
}
caught = true
}
}()
goodKey := "FOO_BAR"
checkKey(goodKey)
if caught == true {
t.Fatalf("expected to don't get a call to panic with key %q", goodKey)
}
badKey := goodKey + "_a"
checkKey(badKey)
if caught == false {
t.Fatalf("expected to get a call to panic with key %q", badKey)
}
}

356
vendor/github.com/tredoe/osutil/config/shconf/scan.go generated vendored Normal file
View file

@ -0,0 +1,356 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package shconf
import (
"bufio"
"bytes"
"io"
"strconv"
"unicode"
"unicode/utf8"
)
type extraCharError int
func (e extraCharError) Error() string {
return strconv.Itoa(int(e)) + ": extra character/s after of the value"
}
type keyError int
func (e keyError) Error() string {
return strconv.Itoa(int(e)) + ": key not found"
}
type noASCIIKeyError int
func (e noASCIIKeyError) Error() string {
return strconv.Itoa(int(e)) + ": the key only must to have ASCII characters"
}
type openQuoteError int
func (e openQuoteError) Error() string {
return strconv.Itoa(int(e)) + ": the quote in the value is not closed"
}
type valueError int
func (e valueError) Error() string {
return strconv.Itoa(int(e)) + ": value not found"
}
// * * *
// _DEF_SEPARATOR is the character used like separator, by default.
var _DEF_SEPARATOR = []byte{'='}
// option represents the option to run the scanner.
type Option uint8
const (
SKIP_KEYS_DISABLED Option = iota + 1
GET_KEYS_DISABLED
)
// Scanner provides a convenient interface for reading data such as a file of
// lines of text in format key-value. Successive calls to the Scan method will
// step through the 'tokens' of a file, skipping the bytes between the tokens.
//
// Scanning stops unrecoverably at EOF, the first I/O error, or a token too
// large to fit in the buffer.
type Scanner struct {
buf *bufio.Reader
// Character/s used to separate the value from key.
// It is only get in the first call to "Scan()".
separator []byte
key []byte
value []byte
err error
line int // Number of line being scanned
}
// NewScanner returns a new Scanner to read from r, with the option to skip the
// keys disabled.
func NewScanner(r io.Reader) *Scanner { return &Scanner{buf: bufio.NewReader(r)} }
// Scan advances the Scanner to the next tokens, which will then be available
// through the Bytes or Text method. It returns false when the scan stops, either
// by reaching the end of the input or an error. After Scan returns false, the Err
// method will return any error that occurred during scanning, except that if it
// was io.EOF, Err will return nil.
func (s *Scanner) Scan() bool {
var thisRune rune
var n int
var err error
for s.line++; ; s.line++ {
// Skip leading spaces.
for thisRune, n, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
s.err = err
return false
}
if thisRune == '\n' {
s.line++
continue
}
if !unicode.IsSpace(thisRune) {
break
}
}
// Skip line comment and section.
if thisRune == '#' || thisRune == '[' {
if _, err = s.buf.ReadBytes('\n'); err != nil {
s.err = err
return false
}
continue
}
break
}
if thisRune == '=' {
s.err = keyError(s.line)
return false
}
var key, value, separator []rune
// Get key
for ; ; thisRune, n, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
// The separator charater could the the equal sign or space.
if thisRune == '=' || unicode.IsSpace(thisRune) {
break
}
if n > 1 || !isValidChar(thisRune) {
s.err = noASCIIKeyError(s.line)
return false
}
key = append(key, thisRune)
}
// Skip spaces before and after of the separator character.
if unicode.IsSpace(thisRune) {
separator = append(separator, thisRune)
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
if !unicode.IsSpace(thisRune) {
break
}
separator = append(separator, thisRune)
}
}
if thisRune == '=' {
separator = append(separator, thisRune)
if thisRune, _, err = s.buf.ReadRune(); err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
}
if unicode.IsSpace(thisRune) {
separator = append(separator, thisRune)
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
} else {
s.err = valueError(s.line)
}
return false
}
if !unicode.IsSpace(thisRune) {
break
}
separator = append(separator, thisRune)
}
}
// Get value
var valueIsString, valueInDQuote bool
var lastRune rune
if thisRune == '"' { // The value is between double quotes
valueIsString = true
valueInDQuote = true
} else if thisRune == '\'' { // between single quotes
valueIsString = true
} else {
value = append(value, thisRune)
}
for thisRune, _, err = s.buf.ReadRune(); ; thisRune, _, err = s.buf.ReadRune() {
if err != nil {
if err != io.EOF {
s.err = err
return false
}
if valueIsString {
s.err = openQuoteError(s.line)
return false
}
s.err = err
break
}
if valueIsString {
if valueInDQuote {
if thisRune == '"' && lastRune != '\\' {
break
}
} else if thisRune == '\'' && lastRune != '\\' {
break
}
lastRune = thisRune // To checking if it is a quote escaped.
value = append(value, thisRune)
} else {
if unicode.IsSpace(thisRune) {
break
}
value = append(value, thisRune)
}
}
// Sanity check
if thisRune != '\n' && thisRune != '\r' {
doCheck := true
last, _, err := s.buf.ReadLine()
if err != nil {
if err != io.EOF {
s.err = err
return false
} else {
doCheck = false
}
}
if doCheck {
for _, char := range last {
if unicode.IsSpace(rune(char)) {
continue
} else if char == '#' {
break
} else {
s.err = extraCharError(s.line)
return false
}
}
}
}
// Store key and value
var bufKey, bufValue, bufSep bytes.Buffer
var bytes []byte
for _, r := range key {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufKey.Write(bytes)
}
s.key = bufKey.Bytes()
for _, r := range value {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufValue.Write(bytes)
}
s.value = bufValue.Bytes()
// Store separator character
if s.separator == nil {
for _, r := range separator {
bytes = make([]byte, utf8.RuneLen(r))
utf8.EncodeRune(bytes, r)
bufSep.Write(bytes)
}
s.separator = bufSep.Bytes()
}
return true
}
// Bytes returns the most recents tokens generated by a call to Scan. The
// underlying array may point to data that will be overwritten by a subsequent
// call to Scan. It does no allocation.
func (s *Scanner) Bytes() (key, value []byte) { return s.key, s.value }
// Text returns the most recents tokens generated by a call to Scan as a newly
// allocated string holding its bytes.
func (s *Scanner) Text() (key, value string) {
return string(s.key), string(s.value)
}
// Err returns the first non-EOF error that was encountered by the Scanner.
func (s *Scanner) Err() error {
if s.err != io.EOF {
return s.err
}
return nil
}
// Separator returns the character/s used to separate the key from the value.
//
// The separator is got in the first call to "Scan()"; if it has not been
// called, this makes it explicitly but panics when there is any error.
func (s *Scanner) Separator() []byte {
if s.separator == nil {
if found := s.Scan(); !found {
if err := s.Err(); err != nil {
panic(err)
}
return _DEF_SEPARATOR
}
}
return s.separator
}
// == Utility
func isValidChar(r rune) bool {
if (r < 'A' || r > 'Z') /*&& r < 'a' && r > 'z'*/ && r != '_' {
return false
}
return true
}

View file

@ -0,0 +1,80 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package shconf
import (
"bytes"
"testing"
)
var scanTests = []struct {
in string
key string
value string
}{
{" \n # qwe", "", ""},
{" \n [ qwe ]\n ABC=def", "ABC", "def"},
{" \n # qwe \n ABC=def", "ABC", "def"},
{"ABC=def", "ABC", "def"},
{" ABC = def ", "ABC", "def"},
{" \n FOO=bar ", "FOO", "bar"}, // 5
{`FOO="bar"`, "FOO", "bar"},
{"FOO='bar'", "FOO", "bar"},
}
func TestScanKeys(t *testing.T) {
for n, test := range scanTests {
s := NewScanner(bytes.NewBufferString(test.in))
s.Scan()
k, v := s.Text()
if test.key != k {
t.Errorf("#%d: key: expected %q, got %q\n", n, test.key, k)
}
if test.value != v {
t.Errorf("#%d: value: expected %q, got %q\n", n, test.value, v)
}
if err := s.Err(); err != nil {
t.Errorf("#%d: %v", n, err)
}
if s.separator != nil && len(_DEF_SEPARATOR) == len(s.separator) {
if _DEF_SEPARATOR[0] != s.separator[0] {
t.Errorf("#%d: separator: expected %q, got %q\n", n, _DEF_SEPARATOR, s.separator)
}
}
}
}
var errorTests = []struct {
in string
err error
}{
{"FOO=bar z", extraCharError(1)},
{" =bar", keyError(1)},
{"FOO", valueError(1)},
{" FOO=", valueError(1)},
{"FOO_€=bar", noASCIIKeyError(1)},
{`FOO="bar`, openQuoteError(1)},
{`FOO='bar`, openQuoteError(1)},
}
func TestErrors(t *testing.T) {
var err error
for n, test := range errorTests {
s := NewScanner(bytes.NewBufferString(test.in))
s.Scan()
err = s.Err()
if test.err != err {
t.Errorf("#%d: expected error %q, got %v\n", n, test.err, err)
}
}
}

213
vendor/github.com/tredoe/osutil/config/shconf/shconf.go generated vendored Normal file
View file

@ -0,0 +1,213 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package shconf implements a parser and scanner for the configuration in
// format shell-variable.
//
// The configuration file consists on entries with the format:
// "key" [separator] "value"
// The comments are indicated by "#" at the beginning of a line and upon the keys.
package shconf
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"sync"
"github.com/tredoe/osutil/file"
)
var (
ErrKey = errors.New("key not found")
ErrStructPtr = errors.New("argument must be a pointer to struct")
)
// A TypeError represents the type no supported in function Unmarshal.
type TypeError string
func (e TypeError) Error() string { return "type no supported: " + string(e) }
// A Config represents the configuration.
type Config struct {
sync.RWMutex
data map[string]string // key: value
separator []byte
filename string
}
// ParseFile creates a new Config and parses the file configuration from the
// named file.
func ParseFile(name string) (*Config, error) {
file, err := os.Open(name)
if err != nil {
return nil, err
}
cfg := &Config{
sync.RWMutex{},
make(map[string]string),
make([]byte, 0),
file.Name(),
}
cfg.Lock()
defer cfg.Unlock()
defer file.Close()
gotSeparator := false
s := NewScanner(file)
for found := s.Scan(); found; found = s.Scan() {
if found {
k, v := s.Text()
cfg.data[k] = v
if !gotSeparator {
cfg.separator = s.Separator()
gotSeparator = true
}
continue
} else if s.Err() != nil {
return nil, err
}
}
return cfg, nil
}
// Print outputs the keys and values parsed.
func (c *Config) Print() {
fmt.Println(c.filename)
for k, v := range c.data {
fmt.Printf("\t%s: %s\n", k, v)
}
}
// Separator returns the character/s used to separate the key from the value.
func (c *Config) Separator() []byte { return c.separator }
// Unmarshal assigns values into the pointer to struct given by out, for the
// keys found.
//
// The valid types in the struct fields for the matched keys are: bool, int,
// uint, float64, string.
//
// The errors that Unmarshal return can be ErrStructPtr or TypeError.
func (c *Config) Unmarshal(out interface{}) error {
valueof := reflect.ValueOf(out)
if valueof.Kind() != reflect.Ptr {
return ErrStructPtr
}
valueof = valueof.Elem()
if valueof.Kind() != reflect.Struct {
return ErrStructPtr
}
typeof := valueof.Type()
for i := 0; i < valueof.NumField(); i++ {
fieldT := typeof.Field(i)
fieldV := valueof.Field(i)
if value, found := c.data[fieldT.Name]; found {
switch fieldV.Kind() {
case reflect.Bool:
v, _ := strconv.ParseBool(value)
fieldV.SetBool(v)
case reflect.Int:
v, _ := strconv.ParseInt(value, 10, 0)
fieldV.SetInt(v)
case reflect.Uint:
v, _ := strconv.ParseUint(value, 10, 0)
fieldV.SetUint(v)
case reflect.Float64:
v, _ := strconv.ParseFloat(value, 64)
fieldV.SetFloat(v)
/*case reflect.Float32:
v, _ := strconv.ParseFloat(value, 32)
fieldV.SetFloat(v)*/
case reflect.String:
fieldV.SetString(value)
default:
return TypeError(fieldV.Kind().String())
}
}
}
return nil
}
// * * *
// Get returns the string value for a given key.
func (c *Config) Get(key string) (string, error) {
if value, found := c.data[key]; found {
return value, nil
}
return "", ErrKey
}
// Getbool returns the boolean value for a given key.
func (c *Config) Getbool(key string) (bool, error) {
if value, found := c.data[key]; found {
return strconv.ParseBool(value)
}
return false, ErrKey
}
// Getint returns the integer value for a given key.
func (c *Config) Getint(key string) (int, error) {
if value, found := c.data[key]; found {
v, err := strconv.ParseInt(value, 10, 0)
return int(v), err
}
return 0, ErrKey
}
// Getuint returns the unsigned integer value for a given key.
func (c *Config) Getuint(key string) (uint, error) {
if value, found := c.data[key]; found {
v, err := strconv.ParseUint(value, 10, 0)
return uint(v), err
}
return 0, ErrKey
}
// Getfloat returns the float value for a given key.
func (c *Config) Getfloat(key string) (float64, error) {
if value, found := c.data[key]; found {
return strconv.ParseFloat(value, 64)
}
return 0, ErrKey
}
// Set writes a new value for key.
func (c *Config) Set(key, value string) error {
c.Lock()
defer c.Unlock()
if _, found := c.data[key]; !found {
return ErrKey
}
separator := string(c.Separator())
replAt := []file.ReplacerAtLine{
{key + separator, separator + ".*", separator + value},
}
if err := file.ReplaceAtLine(c.filename, replAt); err != nil {
return err
}
c.data[key] = value
return nil
}

View file

@ -0,0 +1,122 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package shconf
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
var testdata = []struct {
k string
v string
}{
{"BOOL", "true"},
{"INT", "-2"},
{"UINT", "5"},
{"FLOAT", "3.3"},
{"STRING", "small"},
}
type conf struct {
BOOL bool
INT int
UINT uint
FLOAT float64
STRING string
}
func TestParseFile(t *testing.T) {
// == Create temporary file
file, _ := ioutil.TempFile("", "test")
buf := bufio.NewWriter(file)
buf.WriteString("# main comment\n\n")
buf.WriteString(fmt.Sprintf("%s=%s\n", testdata[0].k, testdata[0].v))
buf.WriteString(fmt.Sprintf("%s=%s\n\n", testdata[1].k, testdata[1].v))
buf.WriteString(fmt.Sprintf("%s=%s\n\n", testdata[2].k, testdata[2].v))
buf.WriteString("# Another comment\n")
buf.WriteString(fmt.Sprintf("%s=%s\n", testdata[3].k, testdata[3].v))
buf.WriteString(fmt.Sprintf("%s=%s\n", testdata[4].k, testdata[4].v))
buf.Flush()
file.Close()
defer func() {
files, err := filepath.Glob(file.Name() + "*")
if err != nil {
t.Error(err)
return
}
for _, v := range files {
if err = os.Remove(v); err != nil {
t.Error(err)
}
}
}()
// == Parser
conf_ok := &conf{}
conf_bad := conf{}
cfg, err := ParseFile(file.Name())
if err != nil {
t.Fatal(err)
}
for k, _ := range cfg.data {
switch k {
case "BOOL":
_, err = cfg.Getbool(k)
case "INT":
_, err = cfg.Getint(k)
case "UINT":
_, err = cfg.Getuint(k)
case "FLOAT":
_, err = cfg.Getfloat(k)
case "STRING":
_, err = cfg.Get(k)
}
if err != nil {
t.Errorf("parser: %q got wrong value", k)
}
}
if _, err = cfg.Get("no_key"); err != ErrKey {
t.Error("expected to get ErrKey")
}
if err = cfg.Unmarshal(conf_ok); err != nil {
t.Error(err)
}
if err = cfg.Unmarshal(conf_bad); err != ErrStructPtr {
t.Error("expected to get ErrStructPtr")
}
if _DEF_SEPARATOR[0] != cfg.separator[0] {
t.Errorf("separator: expected %q, got %q", _DEF_SEPARATOR, cfg.separator)
}
// == Editing
if err = cfg.Set("STRING", "big"); err != nil {
t.Fatal(err)
}
if cfg.data["STRING"] != "big" {
t.Errorf("edit: value %q could not be set in key %q", "big", "STRING")
}
if err = cfg.Set("Not", ""); err == nil {
t.Errorf("edit: key %q should not exist", "Not")
}
// ==
if testing.Verbose() {
cfg.Print()
}
}

86
vendor/github.com/tredoe/osutil/distro/distro.go generated vendored Normal file
View file

@ -0,0 +1,86 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package distro detects the Linux distribution.
package distro
import (
"os"
"github.com/tredoe/osutil/config/shconf"
)
// Distro represents a distribution of Linux system.
type Distro int
// Most used Linux distributions.
const (
Arch Distro = iota + 1
CentOS
Debian
Fedora
Gentoo
Mageia
OpenSUSE
PCLinuxOS
Slackware
Ubuntu
)
var distroNames = [...]string{
Arch: "Arch", // Manjaro
CentOS: "CentOS",
Debian: "Debian",
Fedora: "Fedora",
Gentoo: "Gentoo",
Mageia: "Mageia", // Mandriva fork
OpenSUSE: "openSUSE",
PCLinuxOS: "PCLinuxOS",
Slackware: "Slackware", // Slax
Ubuntu: "Ubuntu",
}
func (s Distro) String() string { return distroNames[s] }
var idToDistro = map[string]Distro{
"arch": Arch,
"manjaro": Arch,
"debian": Debian,
"fedora": Fedora,
"gentoo": Gentoo,
"mageia": Mageia,
"opensuse": OpenSUSE,
"slackware": Slackware,
"ubuntu": Ubuntu,
}
// Detect returns the Linux distribution.
func Detect() (Distro, error) {
var id string
var err error
if _, err = os.Stat("/etc/os-release"); !os.IsNotExist(err) {
cfg, err := shconf.ParseFile("/etc/os-release")
if err != nil {
return 0, err
}
if id, err = cfg.Get("ID"); err != nil {
return 0, err
}
if v, found := idToDistro[id]; found {
return v, nil
}
} else if _, err = os.Stat("/etc/centos-release"); !os.IsNotExist(err) {
return CentOS, nil
} else if _, err = os.Stat("/etc/pclinuxos-release"); !os.IsNotExist(err) {
return PCLinuxOS, nil
}
panic("Linux distribution unsopported")
}

15
vendor/github.com/tredoe/osutil/distro/distro_test.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package distro
import "testing"
func TestDetect(t *testing.T) {
if _, err := Detect(); err != nil {
t.Fatal(err)
}
}

17
vendor/github.com/tredoe/osutil/file/a_test.go generated vendored Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"os"
"path/filepath"
)
var (
TEMP_FILE = filepath.Join(os.TempDir(), "test-file.txt")
TEMP_BACKUP = TEMP_FILE + "+1~"
)

17
vendor/github.com/tredoe/osutil/file/doc.go generated vendored Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/* Package file handles common operations in files.
The editing of files is very important in the shell scripting to working with
the configuration files. There are a great number of functions related to it,
avoiding to have to use an external command to get the same result and with the
advantage of that it creates automatically a backup before of editing.
NewEdit creates a new struct, edit, which has a variable, CommentChar,
with a value by default, '#'. That value is the character used in comments.
*/
package file

468
vendor/github.com/tredoe/osutil/file/edit.go generated vendored Normal file
View file

@ -0,0 +1,468 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"bufio"
"bytes"
"io"
"io/ioutil"
"os"
"regexp"
)
// editDefault represents the vaues by default to set in type edit.
type editDefault struct {
CommentChar string // character used in comments
//DoBackup bool // do backup before of edit?
}
// Values by default for type edit.
var _editDefault = editDefault{"#"}
// edit represents the file to edit.
type edit struct {
editDefault
file *os.File
buf *bufio.ReadWriter
}
type Replacer struct {
Search, Replace string
}
type ReplacerAtLine struct {
Line, Search, Replace string
}
// NewEdit opens a file to edit; it is created a backup.
func NewEdit(filename string) (*edit, error) {
if err := Backup(filename); err != nil {
return nil, err
}
file, err := os.OpenFile(filename, os.O_RDWR, 0666)
if err != nil {
return nil, err
}
return &edit{
_editDefault,
file,
bufio.NewReadWriter(bufio.NewReader(file), bufio.NewWriter(file)),
}, nil
}
// Append writes len(b) bytes at the end of the File. It returns an error, if any.
func (e *edit) Append(b []byte) error {
_, err := e.file.Seek(0, os.SEEK_END)
if err != nil {
return err
}
_, err = e.file.Write(b)
return err
}
// AppendString is like Append, but writes the contents of string s rather than
// an array of bytes.
func (e *edit) AppendString(s string) error {
return e.Append([]byte(s))
}
// Close closes the file.
func (e *edit) Close() error {
return e.file.Close()
}
// Comment inserts the comment character in lines that mach any regular expression
// in reLine.
func (e *edit) Comment(reLine []string) error {
allReSearch := make([]*regexp.Regexp, len(reLine))
for i, v := range reLine {
if re, err := regexp.Compile(v); err != nil {
return err
} else {
allReSearch[i] = re
}
}
if _, err := e.file.Seek(0, os.SEEK_SET); err != nil {
return err
}
char := []byte(e.CommentChar + " ")
isNew := false
buf := new(bytes.Buffer)
// Check every line.
for {
line, err := e.buf.ReadBytes('\n')
if err == io.EOF {
break
}
for _, v := range allReSearch {
if v.Match(line) {
line = append(char, line...)
if !isNew {
isNew = true
}
break
}
}
if _, err = buf.Write(line); err != nil {
return err
}
}
if isNew {
return e.rewrite(buf.Bytes())
}
return nil
}
// CommentOut removes the comment character of lines that mach any regular expression
// in reLine.
func (e *edit) CommentOut(reLine []string) error {
allSearch := make([]ReplacerAtLine, len(reLine))
for i, v := range reLine {
allSearch[i] = ReplacerAtLine{
v,
"[[:space:]]*" + e.CommentChar + "[[:space:]]*",
"",
}
}
return e.ReplaceAtLineN(allSearch, 1)
}
/*// Insert writes len(b) bytes at the start of the File. It returns an error, if any.
func (e *edit) Insert(b []byte) error {
return e.rewrite(b)
}
// InsertString is like Insert, but writes the contents of string s rather than
// an array of bytes.
func (e *edit) InsertString(s string) error {
return e.rewrite([]byte(s))
}*/
// Replace replaces all regular expressions mathed in r.
func (e *edit) Replace(r []Replacer) error {
return e.genReplace(r, -1)
}
// ReplaceN replaces regular expressions mathed in r. The count determines the
// number to match:
// n > 0: at most n matches
// n == 0: the result is none
// n < 0: all matches
func (e *edit) ReplaceN(r []Replacer, n int) error {
return e.genReplace(r, n)
}
// ReplaceAtLine replaces all regular expressions mathed in r, if the line is
// matched at the first.
func (e *edit) ReplaceAtLine(r []ReplacerAtLine) error {
return e.genReplaceAtLine(r, -1)
}
// ReplaceAtLine replaces regular expressions mathed in r, if the line is
// matched at the first. The count determines the
// number to match:
// n > 0: at most n matches
// n == 0: the result is none
// n < 0: all matches
func (e *edit) ReplaceAtLineN(r []ReplacerAtLine, n int) error {
return e.genReplaceAtLine(r, n)
}
// Generic Replace: replaces a number of regular expressions matched in r.
func (e *edit) genReplace(r []Replacer, n int) error {
if n == 0 {
return nil
}
if _, err := e.file.Seek(0, os.SEEK_SET); err != nil {
return err
}
content, err := ioutil.ReadAll(e.buf)
if err != nil {
return err
}
isNew := false
for _, v := range r {
reSearch, err := regexp.Compile(v.Search)
if err != nil {
return err
}
i := n
repl := []byte(v.Replace)
content = reSearch.ReplaceAllFunc(content, func(s []byte) []byte {
if !isNew {
isNew = true
}
if i != 0 {
i--
return repl
}
return s
})
}
if isNew {
return e.rewrite(content)
}
return nil
}
// Generic ReplaceAtLine: replaces a number of regular expressions matched in r,
// if the line is matched at the first.
func (e *edit) genReplaceAtLine(r []ReplacerAtLine, n int) error {
if n == 0 {
return nil
}
if _, err := e.file.Seek(0, os.SEEK_SET); err != nil {
return err
}
// == Cache the regular expressions
allReLine := make([]*regexp.Regexp, len(r))
allReSearch := make([]*regexp.Regexp, len(r))
allRepl := make([][]byte, len(r))
for i, v := range r {
if reLine, err := regexp.Compile(v.Line); err != nil {
return err
} else {
allReLine[i] = reLine
}
if reSearch, err := regexp.Compile(v.Search); err != nil {
return err
} else {
allReSearch[i] = reSearch
}
allRepl[i] = []byte(v.Replace)
}
buf := new(bytes.Buffer)
isNew := false
// Replace every line, if it maches
for {
line, err := e.buf.ReadBytes('\n')
if err == io.EOF {
break
}
for i, _ := range r {
if allReLine[i].Match(line) {
j := n
line = allReSearch[i].ReplaceAllFunc(line, func(s []byte) []byte {
if !isNew {
isNew = true
}
if j != 0 {
j--
return allRepl[i]
}
return s
})
}
}
if _, err = buf.Write(line); err != nil {
return err
}
}
if isNew {
return e.rewrite(buf.Bytes())
}
return nil
}
func (e *edit) rewrite(b []byte) error {
if _, err := e.file.Seek(0, os.SEEK_SET); err != nil {
return err
}
n, err := e.file.Write(b)
if err != nil {
return err
}
if err = e.file.Truncate(int64(n)); err != nil {
return err
}
return nil // e.file.Sync()
}
// * * *
// Append writes len(b) bytes at the end of the named file. It returns an
// error, if any. The file is backed up.
func Append(filename string, b []byte) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.Append(b)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// AppendString is like Append, but writes the contents of string s rather than
// an array of bytes.
func AppendString(filename, s string) error {
return Append(filename, []byte(s))
}
// Comment inserts the comment character in lines that mach the regular expression
// in reLine, in the named file.
func Comment(filename, reLine string) error {
return CommentM(filename, []string{reLine})
}
// CommentM inserts the comment character in lines that mach any regular expression
// in reLine, in the named file.
func CommentM(filename string, reLine []string) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.Comment(reLine)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// CommentOut removes the comment character of lines that mach the regular expression
// in reLine, in the named file.
func CommentOut(filename, reLine string) error {
return CommentOutM(filename, []string{reLine})
}
// CommentOutM removes the comment character of lines that mach any regular expression
// in reLine, in the named file.
func CommentOutM(filename string, reLine []string) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.CommentOut(reLine)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
/*// Insert writes len(b) bytes at the start of the named file. It returns an
// error, if any. The file is backed up.
func Insert(filename string, b []byte) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.Insert(b)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// InsertString is like Insert, but writes the contents of string s rather than
// an array of bytes.
func InsertString(filename, s string) error {
return Insert(filename, []byte(s))
}*/
// Replace replaces all regular expressions mathed in r for the named file.
func Replace(filename string, r []Replacer) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.genReplace(r, -1)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// ReplaceN replaces a number of regular expressions mathed in r for the named
// file.
func ReplaceN(filename string, r []Replacer, n int) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.genReplace(r, n)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// ReplaceAtLine replaces all regular expressions mathed in r for the named
// file, if the line is matched at the first.
func ReplaceAtLine(filename string, r []ReplacerAtLine) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.genReplaceAtLine(r, -1)
err2 := e.Close()
if err != nil {
return err
}
return err2
}
// ReplaceAtLineN replaces a number of regular expressions mathed in r for the
// named file, if the line is matched at the first.
func ReplaceAtLineN(filename string, r []ReplacerAtLine, n int) error {
e, err := NewEdit(filename)
if err != nil {
return err
}
err = e.genReplaceAtLine(r, n)
err2 := e.Close()
if err != nil {
return err
}
return err2
}

162
vendor/github.com/tredoe/osutil/file/edit_test.go generated vendored Normal file
View file

@ -0,0 +1,162 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"os"
"testing"
"github.com/tredoe/osutil/sh"
)
func TestCreate(t *testing.T) {
if err := CreateString(TEMP_FILE, `
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
`); err != nil {
t.Fatal(err)
}
out, err := sh.Runf("wc -l %s", TEMP_FILE)
if err != nil {
t.Fatal(err)
}
if out[0] != '7' {
t.Fatalf("got %q lines, want 7", out[0])
}
}
func TestEdit(t *testing.T) {
line := "I've heard that the night is all magic.\n"
e, err := NewEdit(TEMP_FILE)
if err != nil {
t.Fatal(err)
}
/*defer func() {
if err = os.Remove(TEMP_FILE); err != nil {
t.Error(err)
}
}()*/
defer func() {
if err = e.Close(); err != nil {
t.Error(err)
}
}()
// The backup should be created.
if _, err = os.Stat(TEMP_BACKUP); err != nil {
t.Error(err)
}
defer func() {
if err = os.Remove(TEMP_BACKUP); err != nil {
t.Error(err)
}
}()
// Append
if err = e.AppendString("\n" + line); err != nil {
t.Error(err)
} else {
if out, _ := sh.Run("tail -n1 " + TEMP_FILE); string(out) != line {
t.Errorf("Append => got %q, want %q", out, line)
}
}
/*// Insert
if err = e.InsertString(line); err != nil {
t.Error(err)
} else {
if out, _, _ := sh.Run("head -n1 " + TEMP_FILE); out != line {
t.Errorf("Insert => got %q, want %q", out, line)
}
}*/
// Replace
repl := []Replacer{
{"dolor", "DOL_"},
{"labor", "LABOR_"},
}
resul := "3\n"
if err = e.Replace(repl); err != nil {
t.Error(err)
} else {
if out, _ := sh.Runf("grep -c %s %s", repl[1].Replace, TEMP_FILE); string(out) != resul {
t.Errorf("Replace (%s) => got %v, want %v", repl[1].Replace, out, resul)
}
}
repl = []Replacer{
{"DOL_", "dOlOr"},
{"LABOR_", "lAbOr"},
}
resul = "1\n"
if err = e.ReplaceN(repl, 1); err != nil {
t.Error(err)
} else {
for i := 0; i <= 1; i++ {
if out, _ := sh.Runf("grep -c %s %s", repl[i].Replace, TEMP_FILE); string(out) != resul {
t.Errorf("Replace (%s) => got %v, want %v", repl[i].Replace, out, resul)
}
}
}
// ReplaceAtLine
replAt := []ReplacerAtLine{
{"LABOR", "o", "OO"},
}
resul = "2\n"
if err = e.ReplaceAtLine(replAt); err != nil {
t.Error(err)
} else {
if out, _ := sh.Run("grep -c OO " + TEMP_FILE); string(out) != resul {
t.Errorf("ReplaceAtLine => got %v, want %v", out, resul)
}
}
replAt = []ReplacerAtLine{
{"heard", "a", "AA"},
}
resul = "1\n"
if err = e.ReplaceAtLineN(replAt, 2); err != nil {
t.Error(err)
} else {
if out, _ := sh.Runf("tail -n1 %s | grep -c A", TEMP_FILE); string(out) != resul {
t.Errorf("ReplaceAtLineN => got %v, want %v", out, resul)
}
}
// Comment
resul = "2\n"
if err = e.Comment([]string{"night", "quis"}); err != nil {
t.Error(err)
} else {
if out, _ := sh.Runf("grep -c %s %s", e.CommentChar, TEMP_FILE); string(out) != resul {
t.Errorf("Comment => got %v, want %v", out, resul)
}
}
// CommentOut
resul = "0\n"
if err = e.CommentOut([]string{"night", "quis"}); err != nil {
t.Error(err)
} else {
if out, _ := sh.Runf("grep -c %s %s", e.CommentChar, TEMP_FILE); string(out) != resul {
t.Errorf("CommentOut => got %v, want %v", out, resul)
}
}
}

62
vendor/github.com/tredoe/osutil/file/find.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
)
// Contain returns whether the named file contains the byte slice b. The
// return value is a boolean.
func Contain(filename string, b []byte) (bool, error) {
f, err := os.Open(filename)
if err != nil {
return false, fmt.Errorf("Contain: %s", err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, err := buf.ReadBytes('\n')
if err == io.EOF {
break
}
if bytes.Contains(line, b) {
return true, nil
}
}
return false, nil
}
// ContainString returns whether the named file contains the string s. The
// return value is a boolean.
func ContainString(filename, s string) (bool, error) {
f, err := os.Open(filename)
if err != nil {
return false, fmt.Errorf("ContainString: %s", err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, err := buf.ReadString('\n')
if err == io.EOF {
break
}
if strings.Contains(line, s) {
return true, nil
}
}
return false, nil
}

27
vendor/github.com/tredoe/osutil/file/find_test.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import "testing"
func TestFind(t *testing.T) {
ok, err := ContainString(TEMP_FILE, "night")
if err != nil {
t.Fatal(err)
}
if !ok {
t.Errorf("ContainString: expected %t, found %t", !ok, ok)
}
ok, err = Contain(TEMP_FILE, []byte("night"))
if err != nil {
t.Fatal(err)
}
if !ok {
t.Errorf("Contain: expected %t, found %t", !ok, ok)
}
}

172
vendor/github.com/tredoe/osutil/file/info.go generated vendored Normal file
View file

@ -0,0 +1,172 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import "os"
// flags got in: `man 2 stat`
const (
modeROwner = 00400 // owner has read permission
modeWOwner = 00200 // owner has write permission
modeXOwner = 00100 // owner has execute permission
modeRGroup = 00040 // group has read permission
modeWGroup = 00020 // group has write permission
modeXGroup = 00010 // group has execute permission
modeROthers = 00004 // others have read permission
modeWOthers = 00002 // others have write permission
modeXOthers = 00001 // others have execute permission
)
type perm uint8
// permissions
const (
_ perm = iota
R // read
W // write
X // execute
)
// info represents a wrapper about os.FileInfo to append some functions.
type info struct{ fi os.FileInfo }
// NewInfo returns a info describing the named file.
func NewInfo(name string) (*info, error) {
i, err := os.Stat(name)
if err != nil {
return nil, err
}
return &info{i}, nil
}
// IsDir reports whether if it is a directory.
func (i *info) IsDir() bool {
return i.fi.IsDir()
}
// IsFile reports whether it is a regular file.
func (i *info) IsFile() bool {
return i.fi.Mode()&os.ModeType == 0
}
// OwnerHas reports whether the owner has all given permissions.
func (i *info) OwnerHas(p ...perm) bool {
mode := i.fi.Mode()
for _, v := range p {
switch v {
case R:
if mode&modeROwner == 0 {
return false
}
case W:
if mode&modeWOwner == 0 {
return false
}
case X:
if mode&modeXOwner == 0 {
return false
}
}
}
return true
}
// GroupHas reports whether the group has all given permissions.
func (i *info) GroupHas(p ...perm) bool {
mode := i.fi.Mode()
for _, v := range p {
switch v {
case R:
if mode&modeRGroup == 0 {
return false
}
case W:
if mode&modeWGroup == 0 {
return false
}
case X:
if mode&modeXGroup == 0 {
return false
}
}
}
return true
}
// OthersHave reports whether the others have all given permissions.
func (i *info) OthersHave(p ...perm) bool {
mode := i.fi.Mode()
for _, v := range p {
switch v {
case R:
if mode&modeROthers == 0 {
return false
}
case W:
if mode&modeWOthers == 0 {
return false
}
case X:
if mode&modeXOthers == 0 {
return false
}
}
}
return true
}
// * * *
// IsDir reports whether if the named file is a directory.
func IsDir(name string) (bool, error) {
i, err := NewInfo(name)
if err != nil {
return false, err
}
return i.IsDir(), nil
}
// IsFile reports whether the named file is a regular file.
func IsFile(name string) (bool, error) {
i, err := NewInfo(name)
if err != nil {
return false, err
}
return i.IsFile(), nil
}
// OwnerHas reports whether the named file has all given permissions for the owner.
func OwnerHas(name string, p ...perm) (bool, error) {
i, err := NewInfo(name)
if err != nil {
return false, err
}
return i.OwnerHas(p...), nil
}
// GroupHas reports whether the named file has all given permissions for the group.
func GroupHas(name string, p ...perm) (bool, error) {
i, err := NewInfo(name)
if err != nil {
return false, err
}
return i.GroupHas(p...), nil
}
// OthersHave reports whether the named file have all given permissions for the others.
func OthersHave(name string, p ...perm) (bool, error) {
i, err := NewInfo(name)
if err != nil {
return false, err
}
return i.OthersHave(p...), nil
}

32
vendor/github.com/tredoe/osutil/file/info_test.go generated vendored Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"testing"
)
func TestInfo(t *testing.T) {
ok, err := IsDir("../file")
if err != nil {
t.Error(err)
} else if !ok {
t.Error("IsDir got false")
}
fi, err := NewInfo("info.go")
if err != nil {
t.Fatal(err)
}
if !fi.OwnerHas(R, W) {
t.Error("OwnerHas(R,W) got false")
}
if fi.OwnerHas(X) {
t.Error("OwnerHas(X) got true")
}
}

185
vendor/github.com/tredoe/osutil/file/io.go generated vendored Normal file
View file

@ -0,0 +1,185 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
// Copy copies file in source to file in dest preserving the mode attributes.
func Copy(source, dest string) (err error) {
// Don't backup files of backup.
if dest[len(dest)-1] != '~' {
if err = Backup(dest); err != nil {
return
}
}
srcFile, err := os.Open(source)
if err != nil {
return err
}
defer func() {
err2 := srcFile.Close()
if err2 != nil && err == nil {
err = err2
}
}()
srcInfo, err := os.Stat(source)
if err != nil {
return err
}
dstFile, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode().Perm())
if err != nil {
return err
}
_, err = io.Copy(dstFile, srcFile)
err2 := dstFile.Close()
if err2 != nil && err == nil {
err = err2
}
return
}
// Create creates a new file with b bytes.
func Create(filename string, b []byte) (err error) {
file, err := os.Create(filename)
if err != nil {
return err
}
_, err = file.Write(b)
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return
}
// CreateString is like Create, but writes the contents of string s rather than
// an array of bytes.
func CreateString(filename, s string) error {
return Create(filename, []byte(s))
}
// Overwrite truncates the named file to zero and writes len(b) bytes. It
// returns an error, if any.
func Overwrite(filename string, b []byte) (err error) {
if err := Backup(filename); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
_, err = file.Write(b)
err2 := file.Close()
if err2 != nil && err == nil {
err = err2
}
return
}
// OverwriteString is like Overwrite, but writes the contents of string s rather
// than an array of bytes.
func OverwriteString(filename, s string) error {
return Overwrite(filename, []byte(s))
}
// == Utility
const _BACKUP_SUFFIX = "+[1-9]~" // Suffix pattern added to backup's file name.
const PREFIX_TEMP = "test-" // Prefix to add to temporary files.
// Backup creates a backup of the named file.
//
// The schema used for the new name is: {name}\+[1-9]~
// name: The original file name.
// + : Character used to separate the file name from rest.
// number: A number from 1 to 9, using rotation.
// ~ : To indicate that it is a backup, just like it is used in Unix systems.
func Backup(filename string) error {
// Check if it is empty
info, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if info.Size() == 0 {
return nil
}
files, err := filepath.Glob(filename + _BACKUP_SUFFIX)
if err != nil {
return err
}
// Number rotation
numBackup := byte(1)
if len(files) != 0 {
lastFile := files[len(files)-1]
numBackup = lastFile[len(lastFile)-2] + 1 // next number
if numBackup > '9' {
numBackup = '1'
}
} else {
numBackup = '1'
}
return Copy(filename, fmt.Sprintf("%s+%s~", filename, string(numBackup)))
}
// CopytoTemp creates a temporary file from the source file into the default
// directory for temporary files (see os.TempDir), whose name begins with prefix.
// If prefix is the empty string, uses the default value PREFIX_TEMP.
// Returns the temporary file name.
func CopytoTemp(source, prefix string) (tmpFile string, err error) {
if prefix == "" {
prefix = PREFIX_TEMP
}
src, err := os.Open(source)
if err != nil {
return "", err
}
defer func() {
err2 := src.Close()
if err2 != nil && err == nil {
err = err2
}
}()
dest, err := ioutil.TempFile("", prefix)
if err != nil {
return "", err
}
defer func() {
err2 := dest.Close()
if err2 != nil && err == nil {
err = err2
}
}()
if _, err = io.Copy(dest, src); err != nil {
return "", err
}
return dest.Name(), nil
}

68
vendor/github.com/tredoe/osutil/file/io_test.go generated vendored Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestBackupSuffix(t *testing.T) {
okFilenames := []string{"foo+1~", "foo+2~", "foo+5~", "foo+8~", "foo+9~"}
badFilenames := []string{"foo+0~", "foo+10~", "foo+11~", "foo+22~"}
for _, v := range okFilenames {
ok, err := filepath.Match("foo"+_BACKUP_SUFFIX, v)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Errorf("expected to not match %q", v)
}
}
for _, v := range badFilenames {
ok, err := filepath.Match("foo"+_BACKUP_SUFFIX, v)
if err != nil {
t.Fatal(err)
}
if ok {
t.Errorf("expected to not match %q", v)
}
}
}
const FILENAME = "doc.go"
func TestCopytoTemp(t *testing.T) {
name, err := CopytoTemp(FILENAME, "")
if err != nil {
t.Fatal(err)
}
checkCopytoTemp(name, PREFIX_TEMP, t)
name, err = CopytoTemp(FILENAME, "foo-")
if err != nil {
t.Fatal(err)
}
checkCopytoTemp(name, "foo-", t)
}
func checkCopytoTemp(filename, prefix string, t *testing.T) {
if prefix == "" {
prefix = PREFIX_TEMP
}
if !strings.HasPrefix(filename, filepath.Join(os.TempDir(), prefix)) {
t.Error("got wrong prefix")
}
if err := os.Remove(filename); err != nil {
t.Error(err)
}
}

18
vendor/github.com/tredoe/osutil/file/z_test.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package file
import (
"os"
"testing"
)
func Test_z(t *testing.T) {
if err := os.Remove(TEMP_FILE); err != nil {
t.Error(err)
}
}

48
vendor/github.com/tredoe/osutil/osutil.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
// Copyright 2014 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"errors"
"os"
"os/exec"
)
// Exec executes a command setting both standard input, output and error.
func Exec(cmd string, args ...string) error {
c := exec.Command(cmd, args...)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
return err
}
return nil
}
// ExecSudo executes a command under "sudo".
func ExecSudo(cmd string, args ...string) error {
return Exec("sudo", append([]string{cmd}, args...)...)
}
// Sudo executes command "sudo".
// If some command needs to use "sudo", then could be used this function at
// the beginning so there is not to wait until that it been requested later.
func Sudo() error {
return Exec("sudo", "/bin/true")
}
var ErrNoRoot = errors.New("MUST have administrator privileges")
// MustbeRoot returns an error message if the user is not root.
func MustbeRoot() error {
if os.Getuid() != 0 {
return ErrNoRoot
}
return nil
}

17
vendor/github.com/tredoe/osutil/osutil_test.go generated vendored Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2014 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"testing"
)
func TestSudo(t *testing.T) {
if err := Sudo(); err != nil {
t.Error(err)
}
}

39
vendor/github.com/tredoe/osutil/pkgutil/Info.md generated vendored Normal file
View file

@ -0,0 +1,39 @@
## Linux
#### Deb
#### RPM
http://fedoraproject.org/wiki/FAQ#How_do_I_install_new_software_on_Fedora.3F_Is_there_anything_like_APT.3F
http://yum.baseurl.org/wiki/YumCommands
#### Pacman
https://wiki.archlinux.org/index.php/Pacman#Usage
http://www.archlinux.org/pacman/pacman.8.html
"--noconfirm" bypasses the "Are you sure?" checks
#### Ebuild
http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=1
http://www.gentoo-wiki.info/MAN_emerge
"--ask" argument
#### ZYpp
http://en.opensuse.org/SDB:Zypper_usage
http://www.openss7.org/man2html?zypper%288%29
"--no-confirm"
## Windows
http://www.howtogeek.com/200334/windows-10-includes-a-linux-style-package-manager-named-oneget/?PageSpeed=noscript
http://blogs.msdn.com/b/garretts/archive/2014/04/01/my-little-secret-windows-powershell-oneget.aspx

46
vendor/github.com/tredoe/osutil/pkgutil/deb.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "github.com/tredoe/osutil"
type deb struct{}
func (p deb) Install(name ...string) error {
args := []string{"install", "-y"}
return osutil.ExecSudo("/usr/bin/apt-get", append(args, name...)...)
}
func (p deb) Remove(name ...string) error {
args := []string{"remove", "-y"}
return osutil.ExecSudo("/usr/bin/apt-get", append(args, name...)...)
}
func (p deb) Purge(name ...string) error {
args := []string{"purge", "-y"}
return osutil.ExecSudo("/usr/bin/apt-get", append(args, name...)...)
}
func (p deb) Update() error {
return osutil.ExecSudo("/usr/bin/apt-get", "update", "-qq")
}
func (p deb) Upgrade() error {
return osutil.ExecSudo("/usr/bin/apt-get", "upgrade")
}
func (p deb) Clean() error {
err := osutil.ExecSudo("/usr/bin/apt-get", "autoremove", "-y")
if err != nil {
return err
}
return osutil.ExecSudo("/usr/bin/apt-get", "clean")
}

42
vendor/github.com/tredoe/osutil/pkgutil/ebuild.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "github.com/tredoe/osutil"
type ebuild struct{}
func (p ebuild) Install(name ...string) error {
return osutil.Exec("/usr/bin/emerge", name...)
}
func (p ebuild) Remove(name ...string) error {
args := []string{"--unmerge"}
return osutil.Exec("/usr/bin/emerge", append(args, name...)...)
}
func (p ebuild) Purge(name ...string) error {
return p.Remove(name...)
}
func (p ebuild) Update() error {
return osutil.Exec("/usr/bin/emerge", "--sync")
}
func (p ebuild) Upgrade() error {
return osutil.Exec("/usr/bin/emerge", "--update", "--deep", "--with-bdeps=y", "--newuse @world")
}
func (p ebuild) Clean() error {
err := osutil.Exec("/usr/bin/emerge", "--update", "--deep", "--newuse @world")
if err != nil {
return err
}
return osutil.Exec("/usr/bin/emerge", "--depclean")
}

41
vendor/github.com/tredoe/osutil/pkgutil/pacman.go generated vendored Normal file
View file

@ -0,0 +1,41 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "github.com/tredoe/osutil"
type pacman struct{}
func (p pacman) Install(name ...string) error {
args := []string{"-S", "--needed", "--noprogressbar"}
return osutil.Exec("/usr/bin/pacman", append(args, name...)...)
}
func (p pacman) Remove(name ...string) error {
args := []string{"-Rs"}
return osutil.Exec("/usr/bin/pacman", append(args, name...)...)
}
func (p pacman) Purge(name ...string) error {
args := []string{"-Rsn"}
return osutil.Exec("/usr/bin/pacman", append(args, name...)...)
}
func (p pacman) Update() error {
return osutil.Exec("/usr/bin/pacman", "-Syu", "--needed", "--noprogressbar")
}
func (p pacman) Upgrade() error {
return osutil.Exec("/usr/bin/pacman", "-Syu")
}
func (p pacman) Clean() error {
return osutil.Exec("/usr/bin/paccache", "-r")
}

112
vendor/github.com/tredoe/osutil/pkgutil/pkg.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package pkgutil handles basic operations in the management of packages in
// operating systems.
//
// Important
//
// If you are going to use a package manager different to Deb, then you should
// check the options since I cann't test all.
//
// TODO
//
// Add managers of BSD systems.
//
// Use flag to do not show questions.
package pkgutil
import (
"errors"
"os/exec"
)
// Packager is the common interface to handle different package systems.
type Packager interface {
// Install installs packages.
Install(name ...string) error
// Remove removes packages.
Remove(name ...string) error
// Purge removes packages and its configuration files.
Purge(name ...string) error
// Update resynchronizes the package index files from their sources.
Update() error
// Upgrade upgrades all the packages on the system.
Upgrade() error
// Clean erases both packages downloaded and orphaned dependencies.
Clean() error
}
// PackageType represents a package management system.
type PackageType int8
const (
// Linux
Deb PackageType = iota + 1
RPM
Pacman
Ebuild
ZYpp
)
func (pkg PackageType) String() string {
switch pkg {
case Deb:
return "Deb"
case RPM:
return "RPM"
case Pacman:
return "Pacman"
case Ebuild:
return "Ebuild"
case ZYpp:
return "ZYpp"
}
panic("unreachable")
}
// New returns the interface to handle the package manager.
func New(pkg PackageType) Packager {
switch pkg {
case Deb:
return new(deb)
case RPM:
return new(rpm)
case Pacman:
return new(pacman)
case Ebuild:
return new(ebuild)
case ZYpp:
return new(zypp)
}
panic("unreachable")
}
// execPackagers is a list of executables of package managers.
var execPackagers = [...]string{
Deb: "apt-get",
RPM: "yum",
Pacman: "pacman",
Ebuild: "emerge",
ZYpp: "zypper",
}
// Detect tries to get the package system used in the system, looking for
// executables in directory "/usr/bin".
func Detect() (PackageType, error) {
for k, v := range execPackagers {
_, err := exec.LookPath("/usr/bin/" + v)
if err == nil {
return PackageType(k), nil
}
}
return -1, errors.New("package manager not found in directory '/usr/bin'")
}

36
vendor/github.com/tredoe/osutil/pkgutil/pkg_test.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "testing"
func TestPackager(t *testing.T) {
pkg, err := Detect()
if err != nil {
t.Fatal(err)
}
pack := New(pkg)
cmd := "curl"
if err = pack.Update(); err != nil {
t.Fatal(err)
}
if err = pack.Upgrade(); err != nil {
t.Fatal(err)
}
if err = pack.Install(cmd); err != nil {
t.Errorf("\n%s", err)
}
if err = pack.Remove(cmd); err != nil {
t.Errorf("\n%s", err)
}
if err = pack.Clean(); err != nil {
t.Errorf("\n%s", err)
}
}

39
vendor/github.com/tredoe/osutil/pkgutil/rpm.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "github.com/tredoe/osutil"
type rpm struct{}
func (p rpm) Install(name ...string) error {
args := []string{"install"}
return osutil.Exec("/usr/bin/yum", append(args, name...)...)
}
func (p rpm) Remove(name ...string) error {
args := []string{"remove"}
return osutil.Exec("/usr/bin/yum", append(args, name...)...)
}
func (p rpm) Purge(name ...string) error {
return p.Remove(name...)
}
func (p rpm) Update() error {
return osutil.Exec("/usr/bin/yum", "update")
}
func (p rpm) Upgrade() error {
return osutil.Exec("/usr/bin/yum", "update")
}
func (p rpm) Clean() error {
return osutil.Exec("/usr/bin/yum", "clean", "packages")
}

39
vendor/github.com/tredoe/osutil/pkgutil/zypp.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package pkgutil
import "github.com/tredoe/osutil"
type zypp struct{}
func (p zypp) Install(name ...string) error {
args := []string{"install", "--auto-agree-with-licenses"}
return osutil.Exec("/usr/bin/zypper", append(args, name...)...)
}
func (p zypp) Remove(name ...string) error {
args := []string{"remove"}
return osutil.Exec("/usr/bin/zypper", append(args, name...)...)
}
func (p zypp) Purge(name ...string) error {
return p.Remove(name...)
}
func (p zypp) Update() error {
return osutil.Exec("/usr/bin/zypper", "refresh")
}
func (p zypp) Upgrade() error {
return osutil.Exec("/usr/bin/zypper", "up", "--auto-agree-with-licenses")
}
func (p zypp) Clean() error {
return osutil.Exec("/usr/bin/zypper", "clean")
}

72
vendor/github.com/tredoe/osutil/sh/log.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sh
import (
"io/ioutil"
"log"
"log/syslog"
"os"
)
const PATH = "/sbin:/bin:/usr/sbin:/usr/bin"
const _LOG_FILE = "/.shutil.log" // in boot
var (
_ENV []string
_HOME string // to expand symbol "~"
BOOT bool // does the script is being run during boot?
DEBUG bool
logFile *os.File
Log = log.New(ioutil.Discard, "", 0)
)
// Sets environment variables and a null logger.
func init() {
log.SetFlags(0)
log.SetPrefix("ERROR: ")
if BOOT {
_ENV = []string{"PATH=" + PATH} // from file boot
} else {
_ENV = os.Environ()
_HOME = os.Getenv("HOME")
}
/*if path := os.Getenv("PATH"); path == "" {
if err = os.Setenv("PATH", PATH); err != nil {
log.Print(err)
}
}*/
}
// StartLogger initializes the log file.
func StartLogger() {
var err error
if BOOT {
if logFile, err = os.OpenFile(_LOG_FILE, os.O_WRONLY|os.O_TRUNC, 0); err != nil {
log.Print(err)
} else {
Log = log.New(logFile, "", log.Lshortfile)
}
} else {
if Log, err = syslog.NewLogger(syslog.LOG_NOTICE, log.Lshortfile); err != nil {
log.Fatal(err)
}
}
}
// CloseLogger closes the log file.
func CloseLogger() error {
if BOOT {
return logFile.Close()
}
return nil
}

305
vendor/github.com/tredoe/osutil/sh/sh.go generated vendored Normal file
View file

@ -0,0 +1,305 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package sh interprets a command line just like it is done in the Bash shell.
//
// The main function is Run which lets to call to system commands under a new
// process. It handles pipes, environment variables, and does pattern expansion.
package sh
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)
// Debug shows debug messages in functions like Run.
var Debug bool
// == Errors
var (
errEnvVar = errors.New("the format of the variable has to be VAR=value")
errNoCmdInPipe = errors.New("no command around of pipe")
)
type extraCmdError string
func (e extraCmdError) Error() string {
return "command not added to " + string(e)
}
type runError struct {
cmd string
debug string
errType string
err error
}
func (e runError) Error() string {
if Debug {
if e.debug != "" {
e.debug = "\n## DEBUG\n" + e.debug + "\n"
}
return fmt.Sprintf("Command line: `%s`\n%s\n## %s\n%s", e.cmd, e.debug, e.errType, e.err)
}
return fmt.Sprintf("\n%s", e.err)
}
// RunWithMatch executes external commands with access to shell features such as
// filename wildcards, shell pipes, environment variables, and expansion of the
// shortcut character "~" to home directory.
//
// This function avoids to have execute commands through a shell since an
// unsanitized input from an untrusted source makes a program vulnerable to
// shell injection, a serious security flaw which can result in arbitrary
// command execution.
//
// The most of commands return a text in output or an error if any.
// `match` is used in commands like *grep*, *find*, or *cmp* to indicate if the
// serach is matched.
func RunWithMatch(command string) (output []byte, match bool, err error) {
var (
cmds []*exec.Cmd
outPipes []io.ReadCloser
stdout, stderr bytes.Buffer
)
commands := strings.Split(command, "|")
lastIdxCmd := len(commands) - 1
// Check lonely pipes.
for _, cmd := range commands {
if strings.TrimSpace(cmd) == "" {
err = runError{command, "", "ERR", errNoCmdInPipe}
return
}
}
for i, cmd := range commands {
cmdEnv := _ENV // evironment variables for each command
indexArgs := 1 // position where the arguments start
fields := strings.Fields(cmd)
lastIdxFields := len(fields) - 1
// == Get environment variables in the first arguments, if any.
for j, fCmd := range fields {
if fCmd[len(fCmd)-1] == '=' || // VAR= foo
(j < lastIdxFields && fields[j+1][0] == '=') { // VAR =foo
err = runError{command, "", "ERR", errEnvVar}
return
}
if strings.ContainsRune(fields[0], '=') {
cmdEnv = append([]string{fields[0]}, _ENV...) // Insert the environment variable
fields = fields[1:] // and it is removed from arguments
} else {
break
}
}
// ==
cmdPath, e := exec.LookPath(fields[0])
if e != nil {
err = runError{command, "", "ERR", e}
return
}
// == Get the path of the next command, if any
for j, fCmd := range fields {
cmdBase := path.Base(fCmd)
if cmdBase != "sudo" && cmdBase != "xargs" {
break
}
// It should have an extra command.
if j+1 == len(fields) {
err = runError{command, "", "ERR", extraCmdError(cmdBase)}
return
}
nextCmdPath, e := exec.LookPath(fields[j+1])
if e != nil {
err = runError{command, "", "ERR", e}
return
}
if fields[j+1] != nextCmdPath {
fields[j+1] = nextCmdPath
indexArgs = j + 2
}
}
// == Expansion of arguments
expand := make(map[int][]string, len(fields))
for j := indexArgs; j < len(fields); j++ {
// Skip flags
if fields[j][0] == '-' {
continue
}
// Shortcut character "~"
if fields[j] == "~" || strings.HasPrefix(fields[j], "~/") {
fields[j] = strings.Replace(fields[j], "~", _HOME, 1)
}
// File name wildcards
names, e := filepath.Glob(fields[j])
if e != nil {
err = runError{command, "", "ERR", e}
return
}
if names != nil {
expand[j] = names
}
}
// Substitute the names generated for the pattern starting from last field.
if len(expand) != 0 {
for j := len(fields) - indexArgs; j >= indexArgs; j-- {
if v, ok := expand[j]; ok {
fields = append(fields[:j], append(v, fields[j+1:]...)...)
}
}
}
// == Handle arguments with quotes
hasQuote := false
needUpdate := false
tmpFields := []string{}
for j := indexArgs; j < len(fields); j++ {
v := fields[j]
lastChar := v[len(v)-1]
if !hasQuote && (v[0] == '\'' || v[0] == '"') {
if !needUpdate {
needUpdate = true
}
v = v[1:] // skip quote
if lastChar == '\'' || lastChar == '"' {
v = v[:len(v)-1] // remove quote
} else {
hasQuote = true
}
tmpFields = append(tmpFields, v)
continue
}
if hasQuote {
if lastChar == '\'' || lastChar == '"' {
v = v[:len(v)-1] // remove quote
hasQuote = false
}
tmpFields[len(tmpFields)-1] += " " + v
continue
}
tmpFields = append(tmpFields, v)
}
if needUpdate {
fields = append(fields[:indexArgs], tmpFields...)
}
// == Create command
c := &exec.Cmd{
Path: cmdPath,
Args: append([]string{fields[0]}, fields[1:]...),
Env: cmdEnv,
}
// == Connect pipes
outPipe, e := c.StdoutPipe()
if e != nil {
err = runError{command, "", "ERR", e}
return
}
if i == 0 {
c.Stdin = os.Stdin
} else {
c.Stdin = outPipes[i-1] // anterior output
}
// == Buffers
c.Stderr = &stderr
// Only save the last output
if i == lastIdxCmd {
c.Stdout = &stdout
}
// == Start command
if e := c.Start(); e != nil {
err = runError{command,
fmt.Sprintf("- Command: %s\n- Args: %s", c.Path, c.Args),
"Start", fmt.Errorf("%s", c.Stderr)}
return
}
//
cmds = append(cmds, c)
outPipes = append(outPipes, outPipe)
}
for _, c := range cmds {
if e := c.Wait(); e != nil {
_, isExitError := e.(*exec.ExitError)
// Error type due I/O problems.
if !isExitError {
err = runError{command,
fmt.Sprintf("- Command: %s\n- Args: %s", c.Path, c.Args),
"Wait", fmt.Errorf("%s", c.Stderr)}
return
}
if c.Stderr != nil {
if stderr := fmt.Sprintf("%s", c.Stderr); stderr != "" {
stderr = strings.TrimRight(stderr, "\n")
err = runError{command,
fmt.Sprintf("- Command: %s\n- Args: %s", c.Path, c.Args),
"Stderr", fmt.Errorf("%s", stderr)}
return
}
}
} else {
match = true
}
}
Log.Print(command)
return stdout.Bytes(), match, nil
}
// Run executes external commands just like RunWithMatch, but does not return
// the boolean `match`.
func Run(command string) (output []byte, err error) {
output, _, err = RunWithMatch(command)
return
}
// Runf is like Run, but formats its arguments according to the format.
// Analogous to Printf().
func Runf(format string, args ...interface{}) ([]byte, error) {
return Run(fmt.Sprintf(format, args...))
}
// RunWithMatchf is like RunWithMatch, but formats its arguments according to
// the format. Analogous to Printf().
func RunWithMatchf(format string, args ...interface{}) ([]byte, bool, error) {
return RunWithMatch(fmt.Sprintf(format, args...))
}

90
vendor/github.com/tredoe/osutil/sh/sh_test.go generated vendored Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sh
import (
"errors"
"testing"
)
var testsOk = []struct {
cmd string
match bool
}{
// expansion of "~"
{"ls ~/", true},
}
var testsOutput = []struct {
cmd string
out string
match bool
}{
// values in match
{"true", "", true},
{"false", "", false},
{`grep foo not_exist.go`, "", false}, // no found
{`grep package sh.go`, "package sh\n", true}, // found
// pipes
{"ls sh*.go | wc -l", "2\n", true},
// quotes
{`sh -c 'echo 123'`, "123\n", true},
{`sh -c "echo 123"`, "123\n", true},
{`find -name 'sh*.go'`, "./sh.go\n./sh_test.go\n", true},
}
var testsError = []struct {
cmd string
err error // from Stderr
}{
{"| ls ", errNoCmdInPipe},
{"| ls | wc", errNoCmdInPipe},
{"ls|", errNoCmdInPipe},
{"ls| wc|", errNoCmdInPipe},
{"ls| |wc", errNoCmdInPipe},
{"LANG= C find", errEnvVar},
{"LANG =C find", errEnvVar},
{`LANG=C find -nop README.md`, errors.New("find: unknown predicate `-nop'")},
}
func TestRun(t *testing.T) {
for _, v := range testsOk {
out, match, _ := RunWithMatch(v.cmd)
if v.match != match {
t.Errorf("`%s` (match): expected %t, found %t\n", v.cmd, v.match, match)
}
if string(out) == "" {
t.Errorf("`%s`: output is empty", v.cmd)
}
}
for _, v := range testsOutput {
out, match, _ := RunWithMatch(v.cmd)
if string(out) != v.out {
t.Errorf("`%s` (output): expected %q, found %q\n", v.cmd, v.out, out)
}
if match != v.match {
t.Errorf("`%s` (match): expected %t, found %t\n", v.cmd, v.match, match)
}
}
for _, v := range testsError {
_, err := Run(v.cmd)
mainErr := err.(runError).err
if mainErr.Error() != v.err.Error() {
t.Errorf("`%s` (error): expected %q, found %q\n", v.cmd, v.err, mainErr)
}
}
}

56
vendor/github.com/tredoe/osutil/user/README.md generated vendored Normal file
View file

@ -0,0 +1,56 @@
user
====
Provides access to the users database. It is available for Linux (by now).
[Documentation online](http://gowalker.org/github.com/tredoe/osutil/user)
## Installation
go get github.com/tredoe/osutil/user
To run the tests, it is necessary to run them as root.
Do not worry because the tests are done in copies of original files.
sudo env PATH=$PATH GOPATH=$GOPATH go test -v
## Status
BSD systems and Windows unsopported.
The only backend built is to handle files (such as '/etc/passwd'), and it it not
my priority to handle other backends like LDAP or Kerberos since my goal was
to can use it in home systems.
My list of priorities are (for when I have time):
+ BSD systems (included Mac OS)
+ Windows
## Configuration
Some values are got from the system configuration, i.e. to get the next
available UID or GID, but every distribution of a same system can have a
different configuration system.
In the case of Linux, the research has been done in 10 different distributions:
Arch
CentOS
Debian
Fedora
Gentoo
Mageia (Mandriva's fork)
OpenSUSE
PCLinuxOS
Slackware
Ubuntu
## License
The source files are distributed under the [Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
unless otherwise noted.
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
if you have further questions regarding the license.
* * *
*Generated by [Gowizard](https://github.com/tredoe/wizard)*

68
vendor/github.com/tredoe/osutil/user/a_test.go generated vendored Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"os"
"path/filepath"
"github.com/tredoe/goutil"
"github.com/tredoe/osutil"
"github.com/tredoe/osutil/file"
)
const (
USER = "u_foo"
USER2 = "u_foo2"
SYS_USER = "usys_bar"
GROUP = "g_foo"
SYS_GROUP = "gsys_bar"
)
var MEMBERS = []string{USER, SYS_USER}
// Stores the ids at creating the groups.
var GID, SYS_GID int
// == Copy the system files before of be edited.
func init() {
err := osutil.MustbeRoot()
if err != nil {
goutil.Fatalf("%s", err)
}
if _USER_FILE, err = file.CopytoTemp(_USER_FILE, "test-user_"); err != nil {
goto _error
}
if _GROUP_FILE, err = file.CopytoTemp(_GROUP_FILE, "test-group_"); err != nil {
goto _error
}
if _SHADOW_FILE, err = file.CopytoTemp(_SHADOW_FILE, "test-shadow_"); err != nil {
goto _error
}
if _GSHADOW_FILE, err = file.CopytoTemp(_GSHADOW_FILE, "test-gshadow_"); err != nil {
goto _error
}
return
_error:
removeTempFiles()
goutil.Fatalf("%s", err)
}
func removeTempFiles() {
files, _ := filepath.Glob(filepath.Join(os.TempDir(), file.PREFIX_TEMP+"*"))
for _, f := range files {
if err := os.Remove(f); err != nil {
goutil.Errorf("%s", err)
}
}
}

283
vendor/github.com/tredoe/osutil/user/config_linux.go generated vendored Normal file
View file

@ -0,0 +1,283 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"fmt"
"strings"
"sync"
"github.com/tredoe/goutil/cmdutil"
"github.com/tredoe/goutil/reflectutil"
"github.com/tredoe/osutil/config/shconf"
"github.com/tredoe/osutil/user/crypt"
)
// TODO: handle des, bcrypt and rounds in SHA2.
// TODO: Idea: store struct "configData" to run configData.Init() only when
// the configuration files have been modified.
// == System configuration files.
const _CONF_LOGIN = "/etc/login.defs"
type conf_login struct {
PASS_MIN_DAYS int
PASS_MAX_DAYS int
PASS_MIN_LEN int
PASS_WARN_AGE int
SYS_UID_MIN int
SYS_UID_MAX int
SYS_GID_MIN int
SYS_GID_MAX int
UID_MIN int
UID_MAX int
GID_MIN int
GID_MAX int
ENCRYPT_METHOD string // upper
SHA_CRYPT_MIN_ROUNDS int
SHA_CRYPT_MAX_ROUNDS int
// or
CRYPT_PREFIX string // $2a$
CRYPT_ROUNDS int // 8
}
const _CONF_USERADD = "/etc/default/useradd"
type conf_useradd struct {
HOME string // Default to '/home'
SHELL string // Default to '/bin/sh'
}
// == Optional files.
// Used in systems derivated from Debian: Ubuntu, Mint.
const _CONF_ADDUSER = "/etc/adduser.conf"
type conf_adduser struct {
FIRST_SYSTEM_UID int
LAST_SYSTEM_UID int
FIRST_SYSTEM_GID int
LAST_SYSTEM_GID int
FIRST_UID int
LAST_UID int
FIRST_GID int
LAST_GID int
}
// Used in Arch, Manjaro, OpenSUSE.
// But it is only used by 'pam_unix2.so'.
const _CONF_PASSWD = "/etc/default/passwd"
// TODO: to see the other options of that file.
type conf_passwd struct {
CRYPT string // lower
}
// Used in systems derivated from Red Hat: CentOS, Fedora, Mageia, PCLinuxOS.
const _CONF_LIBUSER = "/etc/libuser.conf"
type conf_libuser struct {
login_defs string
crypt_style string // lower
// For SHA2
hash_rounds_min int
hash_rounds_max int
}
// * * *
var _DEBUG bool
// A configData represents the configuration used to add users and groups.
type configData struct {
login conf_login
useradd conf_useradd
crypter crypt.Crypter
sync.Once
}
var config configData
// init sets the configuration data.
func (c *configData) init() error {
_conf_login := &conf_login{}
cmdutil.SetPrefix("\n* ", "")
cfg, err := shconf.ParseFile(_CONF_LOGIN)
if err != nil {
return err
} else {
if err = cfg.Unmarshal(_conf_login); err != nil {
return err
}
if _DEBUG {
cmdutil.Println(_CONF_LOGIN)
reflectutil.PrintStruct(_conf_login)
}
if _conf_login.PASS_MAX_DAYS == 0 {
_conf_login.PASS_MAX_DAYS = 99999
}
if _conf_login.PASS_WARN_AGE == 0 {
_conf_login.PASS_WARN_AGE = 7
}
}
cfg, err = shconf.ParseFile(_CONF_USERADD)
if err != nil {
return err
} else {
_conf_useradd := &conf_useradd{}
if err = cfg.Unmarshal(_conf_useradd); err != nil {
return err
}
if _DEBUG {
cmdutil.Println(_CONF_USERADD)
reflectutil.PrintStruct(_conf_useradd)
}
if _conf_useradd.HOME == "" {
_conf_useradd.HOME = "/home"
}
if _conf_useradd.SHELL == "" {
_conf_useradd.SHELL = "/bin/sh"
}
config.useradd = *_conf_useradd
}
// Optional files
found, err := exist(_CONF_ADDUSER) // Based in Debian.
if found {
cfg, err := shconf.ParseFile(_CONF_ADDUSER)
if err != nil {
return err
}
_conf_adduser := &conf_adduser{}
if err = cfg.Unmarshal(_conf_adduser); err != nil {
return err
}
if _DEBUG {
cmdutil.Println(_CONF_ADDUSER)
reflectutil.PrintStruct(_conf_adduser)
}
if _conf_login.SYS_UID_MIN == 0 || _conf_login.SYS_UID_MAX == 0 ||
_conf_login.SYS_GID_MIN == 0 || _conf_login.SYS_GID_MAX == 0 ||
_conf_login.UID_MIN == 0 || _conf_login.UID_MAX == 0 ||
_conf_login.GID_MIN == 0 || _conf_login.GID_MAX == 0 {
_conf_login.SYS_UID_MIN = _conf_adduser.FIRST_SYSTEM_UID
_conf_login.SYS_UID_MAX = _conf_adduser.LAST_SYSTEM_UID
_conf_login.SYS_GID_MIN = _conf_adduser.FIRST_SYSTEM_GID
_conf_login.SYS_GID_MAX = _conf_adduser.LAST_SYSTEM_GID
_conf_login.UID_MIN = _conf_adduser.FIRST_UID
_conf_login.UID_MAX = _conf_adduser.LAST_UID
_conf_login.GID_MIN = _conf_adduser.FIRST_GID
_conf_login.GID_MAX = _conf_adduser.LAST_GID
}
} else if err != nil {
return err
} else if found, err = exist(_CONF_LIBUSER); found { // Based in Red Hat.
cfg, err := shconf.ParseFile(_CONF_LIBUSER)
if err != nil {
return err
}
_conf_libuser := &conf_libuser{}
if err = cfg.Unmarshal(_conf_libuser); err != nil {
return err
}
if _DEBUG {
cmdutil.Println(_CONF_LIBUSER)
reflectutil.PrintStruct(_conf_libuser)
}
if _conf_libuser.login_defs != _CONF_LOGIN {
_conf_login.ENCRYPT_METHOD = _conf_libuser.crypt_style
_conf_login.SHA_CRYPT_MIN_ROUNDS = _conf_libuser.hash_rounds_min
_conf_login.SHA_CRYPT_MAX_ROUNDS = _conf_libuser.hash_rounds_max
}
} else if err != nil {
return err
} /*else if found, err = exist(_CONF_PASSWD); found {
cfg, err := shconf.ParseFile(_CONF_PASSWD)
if err != nil {
return err
}
_conf_passwd := &conf_passwd{}
if err = cfg.Unmarshal(_conf_passwd); err != nil {
return err
}
if _DEBUG {
cmdutil.Println(_CONF_PASSWD)
reflectutil.PrintStruct(_conf_passwd)
}
if _conf_passwd.CRYPT != "" {
_conf_login.ENCRYPT_METHOD = _conf_passwd.CRYPT
}
} else if err != nil {
return err
}*/
switch strings.ToUpper(_conf_login.ENCRYPT_METHOD) {
case "MD5":
c.crypter = crypt.New(crypt.MD5)
case "SHA256":
c.crypter = crypt.New(crypt.SHA256)
case "SHA512":
c.crypter = crypt.New(crypt.SHA512)
case "":
if c.crypter, err = lookupCrypter(); err != nil {
return err
}
default:
return fmt.Errorf("user: requested cryp function is unavailable: %s",
c.login.ENCRYPT_METHOD)
}
if _conf_login.SYS_UID_MIN == 0 || _conf_login.SYS_UID_MAX == 0 ||
_conf_login.SYS_GID_MIN == 0 || _conf_login.SYS_GID_MAX == 0 ||
_conf_login.UID_MIN == 0 || _conf_login.UID_MAX == 0 ||
_conf_login.GID_MIN == 0 || _conf_login.GID_MAX == 0 {
_conf_login.SYS_UID_MIN = 100
_conf_login.SYS_UID_MAX = 999
_conf_login.SYS_GID_MIN = 100
_conf_login.SYS_GID_MAX = 999
_conf_login.UID_MIN = 1000
_conf_login.UID_MAX = 29999
_conf_login.GID_MIN = 1000
_conf_login.GID_MAX = 29999
}
config.login = *_conf_login
return nil
}
// loadConfig loads user configuration.
// It has to be loaded before of edit some file.
func loadConfig() {
config.Do(func() {
//checkRoot()
if err := config.init(); err != nil {
panic(err)
}
})
}

16
vendor/github.com/tredoe/osutil/user/config_test.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import "testing"
func TestLoadConfig(t *testing.T) {
if testing.Verbose() {
_DEBUG = true
}
loadConfig()
}

145
vendor/github.com/tredoe/osutil/user/crypt.go generated vendored Normal file
View file

@ -0,0 +1,145 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Passwords
//
// If the passwd field contains some string that is not a valid result of
// hashing, for instance "!" or "*", the user will not be able to use a unix
// passwd to log in (but the user may log in the system by other means).
//
// A passwd field which starts with a exclamation mark means that the passwd is
// locked. The remaining characters on the line represent the passwd field before
// the passwd was locked.
package user
import (
"bufio"
"errors"
"io"
"log"
"os"
"github.com/tredoe/osutil/user/crypt"
_ "github.com/tredoe/osutil/user/crypt/md5_crypt"
_ "github.com/tredoe/osutil/user/crypt/sha256_crypt"
_ "github.com/tredoe/osutil/user/crypt/sha512_crypt"
//_ "github.com/tredoe/osutil/user/crypt/bcrypt"
)
const _LOCK_CHAR = '!' // Character added at the beginning of the passwd to lock it.
var ErrShadowPasswd = errors.New("no found user with shadowed passwd")
// lookupCrypter returns the first crypt function found in shadowed passwd file.
func lookupCrypter() (crypt.Crypter, error) {
f, err := os.Open(_SHADOW_FILE)
if err != nil {
return nil, err
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
return nil, ErrShadowPasswd
}
log.Print(err)
continue
}
shadow, err := parseShadow(string(line))
if err != nil {
log.Print(err)
continue
}
if shadow.password[0] == '$' {
return crypt.NewFromHash(shadow.password), nil
}
}
return nil, ErrShadowPasswd
}
// SetCrypter sets the crypt function to can hash the passwords.
// The type "crypt.Crypt" comes from package "github.com/tredoe/osutil/user/crypt".
func SetCrypter(c crypt.Crypt) {
loadConfig()
config.crypter = crypt.New(c)
}
// Passwd sets a hashed passwd for the actual user.
// The passwd must be supplied in clear-text.
func (s *Shadow) Passwd(key []byte) {
loadConfig()
s.password, _ = config.crypter.Generate(key, nil)
s.setChange()
}
// Passwd sets a hashed passwd for the actual group.
// The passwd must be supplied in clear-text.
func (gs *GShadow) Passwd(key []byte) {
loadConfig()
gs.password, _ = config.crypter.Generate(key, nil)
}
// == Change passwd
// ChPasswd updates passwd.
// The passwd must be supplied in clear-text.
func ChPasswd(user string, key []byte) error {
shadow, err := LookupShadow(user)
if err != nil {
return err
}
shadow.Passwd(key)
return edit(user, shadow)
}
// ChGPasswd updates group passwd.
// The passwd must be supplied in clear-text.
func ChGPasswd(group string, key []byte) error {
gshadow, err := LookupGShadow(group)
if err != nil {
return err
}
gshadow.Passwd(key)
return edit(group, gshadow)
}
// == Locking
// LockUser locks the passwd of the given user.
func LockUser(name string) error {
shadow, err := LookupShadow(name)
if err != nil {
return err
}
if shadow.password[0] != _LOCK_CHAR {
shadow.password = string(_LOCK_CHAR) + shadow.password
return edit(name, shadow)
}
return nil
}
// UnlockUser unlocks the passwd of the given user.
func UnlockUser(name string) error {
shadow, err := LookupShadow(name)
if err != nil {
return err
}
if shadow.password[0] == _LOCK_CHAR {
shadow.password = shadow.password[1:]
return edit(name, shadow)
}
return nil
}

View file

@ -0,0 +1,8 @@
### Initial author
[Jeramey Crawford](https://github.com/jeramey)
### Other authors
[Jonas mg](https://github.com/tredoe)

27
vendor/github.com/tredoe/osutil/user/crypt/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2012, Jeramey Crawford <jeramey@antihe.ro>
Copyright (c) 2013, Jonas mg
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
HOLDER 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.

25
vendor/github.com/tredoe/osutil/user/crypt/README.md generated vendored Normal file
View file

@ -0,0 +1,25 @@
crypt
=====
A password hashing library.
The goal of crypt is to bring a library of many common and popular password
hashing algorithms to Go and to provide a simple and consistent interface to
each of them. As every hashing method is implemented in pure Go, this library
should be as portable as Go itself.
All hashing methods come with a test suite which verifies their operation
against itself as well as the output of other password hashing implementations
to ensure compatibility with them.
I hope you find this library to be useful and easy to use!
Note: forked from <https://github.com/jeramey/go-pwhash>
## Installation
go get github.com/tredoe/osutil/user/crypt
## License
The source files are distributed under a BSD-style license that can be found
in the LICENSE file.

View file

@ -0,0 +1,62 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package apr1_crypt implements the standard Unix MD5-crypt algorithm created
// by Poul-Henning Kamp for FreeBSD, and modified by the Apache project.
//
// The only change from MD5-crypt is the use of the magic constant "$apr1$"
// instead of "$1$". The algorithms are otherwise identical.
package apr1_crypt
import (
"github.com/tredoe/osutil/user/crypt"
"github.com/tredoe/osutil/user/crypt/common"
"github.com/tredoe/osutil/user/crypt/md5_crypt"
)
func init() {
crypt.RegisterCrypt(crypt.APR1, New, MagicPrefix)
}
const (
MagicPrefix = "$apr1$"
SaltLenMin = 1
SaltLenMax = 8
RoundsDefault = 1000
)
var md5Crypt = md5_crypt.New()
func init() {
md5Crypt.SetSalt(GetSalt())
}
type crypter struct{ Salt common.Salt }
// New returns a new crypt.Crypter computing the variant "apr1" of MD5-crypt
func New() crypt.Crypter { return &crypter{common.Salt{}} }
func (c *crypter) Generate(key, salt []byte) (string, error) {
return md5Crypt.Generate(key, salt)
}
func (c *crypter) Verify(hashedKey string, key []byte) error {
return md5Crypt.Verify(hashedKey, key)
}
func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil }
func (c *crypter) SetSalt(salt common.Salt) {}
func GetSalt() common.Salt {
return common.Salt{
MagicPrefix: []byte(MagicPrefix),
SaltLenMin: SaltLenMin,
SaltLenMax: SaltLenMax,
RoundsDefault: RoundsDefault,
}
}

View file

@ -0,0 +1,88 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package apr1_crypt
import "testing"
var apr1Crypt = New()
func TestGenerate(t *testing.T) {
data := []struct {
salt []byte
key []byte
out string
}{
{
[]byte("$apr1$$"),
[]byte("abcdefghijk"),
"$apr1$$NTjzQjNZnhYRPxN6ryN191",
},
{
[]byte("$apr1$an overlong salt$"),
[]byte("abcdefgh"),
"$apr1$an overl$iroRZrWCEoQojCkf6p8LC0",
},
{
[]byte("$apr1$12345678$"),
[]byte("Lorem ipsum dolor sit amet"),
"$apr1$12345678$/DpfgRGBHG8N0cbkmw0Fk/",
},
{
[]byte("$apr1$deadbeef$"),
[]byte("password"),
"$apr1$deadbeef$NWLhx1Ai4ScyoaAboTFco.",
},
{
[]byte("$apr1$$"),
[]byte("missing salt"),
"$apr1$$EcorjwkoQz4mYcksVEk6j0",
},
{
[]byte("$apr1$holy-moly-batman$"),
[]byte("1234567"),
"$apr1$holy-mol$/WX0350ZUEkvQkrrVJsrU.",
},
{
[]byte("$apr1$asdfjkl;$"),
[]byte("A really long password. " +
"Longer than a password has any righ" +
"t to be. Hey bub, don't mess with t" +
"his password."),
"$apr1$asdfjkl;$2MbDUb/Bj6qcIIf38PXzp0",
},
}
for i, d := range data {
hash, err := apr1Crypt.Generate(d.key, d.salt)
if err != nil {
t.Fatal(err)
}
if hash != d.out {
t.Errorf("Test %d failed\nExpected: %s, got: %s", i, d.out, hash)
}
}
}
func TestVerify(t *testing.T) {
data := [][]byte{
[]byte("password"),
[]byte("12345"),
[]byte("That's amazing! I've got the same combination on my luggage!"),
[]byte("And change the combination on my luggage!"),
[]byte(" random spa c ing."),
[]byte("94ajflkvjzpe8u3&*j1k513KLJ&*()"),
}
for i, d := range data {
hash, err := apr1Crypt.Generate(d, nil)
if err != nil {
t.Fatal(err)
}
if err = apr1Crypt.Verify(hash, d); err != nil {
t.Errorf("Test %d failed: %s", i, d)
}
}
}

View file

@ -0,0 +1,60 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package common
const alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// Base64_24Bit is a variant of Base64 encoding, commonly used with password
// hashing algorithms to encode the result of their checksum output.
//
// The algorithm operates on up to 3 bytes at a time, encoding the following
// 6-bit sequences into up to 4 hash64 ASCII bytes.
//
// 1. Bottom 6 bits of the first byte
// 2. Top 2 bits of the first byte, and bottom 4 bits of the second byte.
// 3. Top 4 bits of the second byte, and bottom 2 bits of the third byte.
// 4. Top 6 bits of the third byte.
//
// This encoding method does not emit padding bytes as Base64 does.
func Base64_24Bit(src []byte) (hash []byte) {
if len(src) == 0 {
return []byte{} // TODO: return nil
}
hashSize := (len(src) * 8) / 6
if (len(src) % 6) != 0 {
hashSize += 1
}
hash = make([]byte, hashSize)
dst := hash
for len(src) > 0 {
switch len(src) {
default:
dst[0] = alphabet[src[0]&0x3f]
dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f]
dst[2] = alphabet[((src[1]>>4)|(src[2]<<4))&0x3f]
dst[3] = alphabet[(src[2]>>2)&0x3f]
src = src[3:]
dst = dst[4:]
case 2:
dst[0] = alphabet[src[0]&0x3f]
dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f]
dst[2] = alphabet[(src[1]>>4)&0x3f]
src = src[2:]
dst = dst[3:]
case 1:
dst[0] = alphabet[src[0]&0x3f]
dst[1] = alphabet[(src[0]>>6)&0x3f]
src = src[1:]
dst = dst[2:]
}
}
return
}

View file

@ -0,0 +1,13 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package common contains routines used by multiple password hashing
// algorithms.
//
// Generally, you will never import this package directly. Many of the
// *_crypt packages will import this package if they require it.
package common

View file

@ -0,0 +1,105 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package common
import (
"crypto/rand"
"errors"
"strconv"
)
var (
ErrSaltPrefix = errors.New("invalid magic prefix")
ErrSaltFormat = errors.New("invalid salt format")
ErrSaltRounds = errors.New("invalid rounds")
)
// Salt represents a salt.
type Salt struct {
MagicPrefix []byte
SaltLenMin int
SaltLenMax int
RoundsMin int
RoundsMax int
RoundsDefault int
}
// Generate generates a random salt of a given length.
//
// The length is set thus:
//
// length > SaltLenMax: length = SaltLenMax
// length < SaltLenMin: length = SaltLenMin
func (s *Salt) Generate(length int) []byte {
if length > s.SaltLenMax {
length = s.SaltLenMax
} else if length < s.SaltLenMin {
length = s.SaltLenMin
}
saltLen := (length * 6 / 8)
if (length*6)%8 != 0 {
saltLen += 1
}
salt := make([]byte, saltLen)
rand.Read(salt)
out := make([]byte, len(s.MagicPrefix)+length)
copy(out, s.MagicPrefix)
copy(out[len(s.MagicPrefix):], Base64_24Bit(salt))
return out
}
// GenerateWRounds creates a random salt with the random bytes being of the
// length provided, and the rounds parameter set as specified.
//
// The parameters are set thus:
//
// length > SaltLenMax: length = SaltLenMax
// length < SaltLenMin: length = SaltLenMin
//
// rounds < 0: rounds = RoundsDefault
// rounds < RoundsMin: rounds = RoundsMin
// rounds > RoundsMax: rounds = RoundsMax
//
// If rounds is equal to RoundsDefault, then the "rounds=" part of the salt is
// removed.
func (s *Salt) GenerateWRounds(length, rounds int) []byte {
if length > s.SaltLenMax {
length = s.SaltLenMax
} else if length < s.SaltLenMin {
length = s.SaltLenMin
}
if rounds < 0 {
rounds = s.RoundsDefault
} else if rounds < s.RoundsMin {
rounds = s.RoundsMin
} else if rounds > s.RoundsMax {
rounds = s.RoundsMax
}
saltLen := (length * 6 / 8)
if (length*6)%8 != 0 {
saltLen += 1
}
salt := make([]byte, saltLen)
rand.Read(salt)
roundsText := ""
if rounds != s.RoundsDefault {
roundsText = "rounds=" + strconv.Itoa(rounds)
}
out := make([]byte, len(s.MagicPrefix)+len(roundsText)+length)
copy(out, s.MagicPrefix)
copy(out[len(s.MagicPrefix):], []byte(roundsText))
copy(out[len(s.MagicPrefix)+len(roundsText):], Base64_24Bit(salt))
return out
}

View file

@ -0,0 +1,37 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package common
import "testing"
var _Salt = &Salt{
MagicPrefix: []byte("$foo$"),
SaltLenMin: 1,
SaltLenMax: 8,
}
func TestGenerateSalt(t *testing.T) {
magicPrefixLen := len(_Salt.MagicPrefix)
salt := _Salt.Generate(0)
if len(salt) != magicPrefixLen+1 {
t.Errorf("Expected len 1, got len %d", len(salt))
}
for i := _Salt.SaltLenMin; i <= _Salt.SaltLenMax; i++ {
salt = _Salt.Generate(i)
if len(salt) != magicPrefixLen+i {
t.Errorf("Expected len %d, got len %d", i, len(salt))
}
}
salt = _Salt.Generate(9)
if len(salt) != magicPrefixLen+8 {
t.Errorf("Expected len 8, got len %d", len(salt))
}
}

108
vendor/github.com/tredoe/osutil/user/crypt/crypt.go generated vendored Normal file
View file

@ -0,0 +1,108 @@
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package crypt provides interface for password crypt functions and collects
// common constants.
package crypt
import (
"errors"
"strings"
"github.com/tredoe/osutil/user/crypt/common"
)
var ErrKeyMismatch = errors.New("hashed value is not the hash of the given password")
// Crypter is the common interface implemented by all crypt functions.
type Crypter interface {
// Generate performs the hashing algorithm, returning a full hash suitable
// for storage and later password verification.
//
// If the salt is empty, a randomly-generated salt will be generated with a
// length of SaltLenMax and number RoundsDefault of rounds.
//
// Any error only can be got when the salt argument is not empty.
Generate(key, salt []byte) (string, error)
// Verify compares a hashed key with its possible key equivalent.
// Returns nil on success, or an error on failure; if the hashed key is
// diffrent, the error is "ErrKeyMismatch".
Verify(hashedKey string, key []byte) error
// Cost returns the hashing cost (in rounds) used to create the given hashed
// key.
//
// When, in the future, the hashing cost of a key needs to be increased in
// order to adjust for greater computational power, this function allows one
// to establish which keys need to be updated.
//
// The algorithms based in MD5-crypt use a fixed value of rounds.
Cost(hashedKey string) (int, error)
// SetSalt sets a different salt. It is used to easily create derivated
// algorithms, i.e. "apr1_crypt" from "md5_crypt".
SetSalt(salt common.Salt)
}
// Crypt identifies a crypt function that is implemented in another package.
type Crypt uint
const (
APR1 Crypt = iota + 1 // import "github.com/tredoe/osutil/user/crypt/apr1_crypt"
MD5 // import "github.com/tredoe/osutil/user/crypt/md5_crypt"
SHA256 // import "github.com/tredoe/osutil/user/crypt/sha256_crypt"
SHA512 // import "github.com/tredoe/osutil/user/crypt/sha512_crypt"
maxCrypt
)
var cryptPrefixes = make([]string, maxCrypt)
var crypts = make([]func() Crypter, maxCrypt)
// RegisterCrypt registers a function that returns a new instance of the given
// crypt function. This is intended to be called from the init function in
// packages that implement crypt functions.
func RegisterCrypt(c Crypt, f func() Crypter, prefix string) {
if c >= maxCrypt {
panic("crypt: RegisterHash of unknown crypt function")
}
crypts[c] = f
cryptPrefixes[c] = prefix
}
// New returns a new crypter.
func New(c Crypt) Crypter {
f := crypts[c]
if f != nil {
return f()
}
panic("crypt: requested crypt function is unavailable")
}
// NewFromHash returns a new Crypter using the prefix in the given hashed key.
func NewFromHash(hashedKey string) Crypter {
var f func() Crypter
if strings.HasPrefix(hashedKey, cryptPrefixes[SHA512]) {
f = crypts[SHA512]
} else if strings.HasPrefix(hashedKey, cryptPrefixes[SHA256]) {
f = crypts[SHA256]
} else if strings.HasPrefix(hashedKey, cryptPrefixes[MD5]) {
f = crypts[MD5]
} else if strings.HasPrefix(hashedKey, cryptPrefixes[APR1]) {
f = crypts[APR1]
} else {
toks := strings.SplitN(hashedKey, "$", 3)
prefix := "$" + toks[1] + "$"
panic("crypt: unknown cryp function from prefix: " + prefix)
}
if f != nil {
return f()
}
panic("crypt: requested cryp function is unavailable")
}

View file

@ -0,0 +1,166 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package md5_crypt implements the standard Unix MD5-crypt algorithm created by
// Poul-Henning Kamp for FreeBSD.
package md5_crypt
import (
"bytes"
"crypto/md5"
"github.com/tredoe/osutil/user/crypt"
"github.com/tredoe/osutil/user/crypt/common"
)
func init() {
crypt.RegisterCrypt(crypt.MD5, New, MagicPrefix)
}
// NOTE: Cisco IOS only allows salts of length 4.
const (
MagicPrefix = "$1$"
SaltLenMin = 1 // Real minimum is 0, but that isn't useful.
SaltLenMax = 8
RoundsDefault = 1000
)
type crypter struct{ Salt common.Salt }
// New returns a new crypt.Crypter computing the MD5-crypt password hashing.
func New() crypt.Crypter {
return &crypter{GetSalt()}
}
func (c *crypter) Generate(key, salt []byte) (string, error) {
if len(salt) == 0 {
salt = c.Salt.Generate(SaltLenMax)
}
if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) {
return "", common.ErrSaltPrefix
}
saltToks := bytes.Split(salt, []byte{'$'})
if len(saltToks) < 3 {
return "", common.ErrSaltFormat
} else {
salt = saltToks[2]
}
if len(salt) > 8 {
salt = salt[0:8]
}
// Compute alternate MD5 sum with input KEY, SALT, and KEY.
Alternate := md5.New()
Alternate.Write(key)
Alternate.Write(salt)
Alternate.Write(key)
AlternateSum := Alternate.Sum(nil) // 16 bytes
A := md5.New()
A.Write(key)
A.Write(c.Salt.MagicPrefix)
A.Write(salt)
// Add for any character in the key one byte of the alternate sum.
i := len(key)
for ; i > 16; i -= 16 {
A.Write(AlternateSum)
}
A.Write(AlternateSum[0:i])
// The original implementation now does something weird:
// For every 1 bit in the key, the first 0 is added to the buffer
// For every 0 bit, the first character of the key
// This does not seem to be what was intended but we have to follow this to
// be compatible.
for i = len(key); i > 0; i >>= 1 {
if (i & 1) == 0 {
A.Write(key[0:1])
} else {
A.Write([]byte{0})
}
}
Csum := A.Sum(nil)
// In fear of password crackers here comes a quite long loop which just
// processes the output of the previous round again.
// We cannot ignore this here.
for i = 0; i < RoundsDefault; i++ {
C := md5.New()
// Add key or last result.
if (i & 1) != 0 {
C.Write(key)
} else {
C.Write(Csum)
}
// Add salt for numbers not divisible by 3.
if (i % 3) != 0 {
C.Write(salt)
}
// Add key for numbers not divisible by 7.
if (i % 7) != 0 {
C.Write(key)
}
// Add key or last result.
if (i & 1) == 0 {
C.Write(key)
} else {
C.Write(Csum)
}
Csum = C.Sum(nil)
}
out := make([]byte, 0, 23+len(c.Salt.MagicPrefix)+len(salt))
out = append(out, c.Salt.MagicPrefix...)
out = append(out, salt...)
out = append(out, '$')
out = append(out, common.Base64_24Bit([]byte{
Csum[12], Csum[6], Csum[0],
Csum[13], Csum[7], Csum[1],
Csum[14], Csum[8], Csum[2],
Csum[15], Csum[9], Csum[3],
Csum[5], Csum[10], Csum[4],
Csum[11],
})...)
// Clean sensitive data.
A.Reset()
Alternate.Reset()
for i = 0; i < len(AlternateSum); i++ {
AlternateSum[i] = 0
}
return string(out), nil
}
func (c *crypter) Verify(hashedKey string, key []byte) error {
newHash, err := c.Generate(key, []byte(hashedKey))
if err != nil {
return err
}
if newHash != hashedKey {
return crypt.ErrKeyMismatch
}
return nil
}
func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil }
func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt }
func GetSalt() common.Salt {
return common.Salt{
MagicPrefix: []byte(MagicPrefix),
SaltLenMin: SaltLenMin,
SaltLenMax: SaltLenMax,
RoundsDefault: RoundsDefault,
}
}

View file

@ -0,0 +1,88 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package md5_crypt
import "testing"
var md5Crypt = New()
func TestGenerate(t *testing.T) {
data := []struct {
salt []byte
key []byte
out string
}{
{
[]byte("$1$$"),
[]byte("abcdefghijk"),
"$1$$pL/BYSxMXs.jVuSV1lynn1",
},
{
[]byte("$1$an overlong salt$"),
[]byte("abcdfgh"),
"$1$an overl$ZYftmJDIw8sG5s4gG6r.70",
},
{
[]byte("$1$12345678$"),
[]byte("Lorem ipsum dolor sit amet"),
"$1$12345678$Suzx8CrBlkNJwVHHHv5tZ.",
},
{
[]byte("$1$deadbeef$"),
[]byte("password"),
"$1$deadbeef$Q7g0UO4hRC0mgQUQ/qkjZ0",
},
{
[]byte("$1$$"),
[]byte("missing salt"),
"$1$$Lv61fbMiEGprscPkdE9Iw/",
},
{
[]byte("$1$holy-moly-batman$"),
[]byte("1234567"),
"$1$holy-mol$WKomB0dWknSxdW/e8WYHG0",
},
{
[]byte("$1$asdfjkl;$"),
[]byte("A really long password. Longer " +
"than a password has any right to be" +
". Hey bub, don't mess with this password."),
"$1$asdfjkl;$DUqPhKwbK4smV0aEMyDdx/",
},
}
for i, d := range data {
hash, err := md5Crypt.Generate(d.key, d.salt)
if err != nil {
t.Fatal(err)
}
if hash != d.out {
t.Errorf("Test %d failed\nExpected: %s, got: %s", i, d.out, hash)
}
}
}
func TestVerify(t *testing.T) {
data := [][]byte{
[]byte("password"),
[]byte("12345"),
[]byte("That's amazing! I've got the same combination on my luggage!"),
[]byte("And change the combination on my luggage!"),
[]byte(" random spa c ing."),
[]byte("94ajflkvjzpe8u3&*j1k513KLJ&*()"),
}
for i, d := range data {
hash, err := md5Crypt.Generate(d, nil)
if err != nil {
t.Fatal(err)
}
if err = md5Crypt.Verify(hash, d); err != nil {
t.Errorf("Test %d failed: %s", i, d)
}
}
}

View file

@ -0,0 +1,243 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package sha256_crypt implements Ulrich Drepper's SHA256-crypt password
// hashing algorithm.
//
// The specification for this algorithm can be found here:
// http://www.akkadia.org/drepper/SHA-crypt.txt
package sha256_crypt
import (
"bytes"
"crypto/sha256"
"strconv"
"github.com/tredoe/osutil/user/crypt"
"github.com/tredoe/osutil/user/crypt/common"
)
func init() {
crypt.RegisterCrypt(crypt.SHA256, New, MagicPrefix)
}
const (
MagicPrefix = "$5$"
SaltLenMin = 1
SaltLenMax = 16
RoundsMin = 1000
RoundsMax = 999999999
RoundsDefault = 5000
)
var _rounds = []byte("rounds=")
type crypter struct{ Salt common.Salt }
// New returns a new crypt.Crypter computing the SHA256-crypt password hashing.
func New() crypt.Crypter {
return &crypter{GetSalt()}
}
func (c *crypter) Generate(key, salt []byte) (string, error) {
var rounds int
var isRoundsDef bool
if len(salt) == 0 {
salt = c.Salt.GenerateWRounds(SaltLenMax, RoundsDefault)
}
if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) {
return "", common.ErrSaltPrefix
}
saltToks := bytes.Split(salt, []byte{'$'})
if len(saltToks) < 3 {
return "", common.ErrSaltFormat
}
if bytes.HasPrefix(saltToks[2], _rounds) {
isRoundsDef = true
pr, err := strconv.ParseInt(string(saltToks[2][7:]), 10, 32)
if err != nil {
return "", common.ErrSaltRounds
}
rounds = int(pr)
if rounds < RoundsMin {
rounds = RoundsMin
} else if rounds > RoundsMax {
rounds = RoundsMax
}
salt = saltToks[3]
} else {
rounds = RoundsDefault
salt = saltToks[2]
}
if len(salt) > 16 {
salt = salt[0:16]
}
// Compute alternate SHA256 sum with input KEY, SALT, and KEY.
Alternate := sha256.New()
Alternate.Write(key)
Alternate.Write(salt)
Alternate.Write(key)
AlternateSum := Alternate.Sum(nil) // 32 bytes
A := sha256.New()
A.Write(key)
A.Write(salt)
// Add for any character in the key one byte of the alternate sum.
i := len(key)
for ; i > 32; i -= 32 {
A.Write(AlternateSum)
}
A.Write(AlternateSum[0:i])
// Take the binary representation of the length of the key and for every add
// the alternate sum, for every 0 the key.
for i = len(key); i > 0; i >>= 1 {
if (i & 1) != 0 {
A.Write(AlternateSum)
} else {
A.Write(key)
}
}
Asum := A.Sum(nil)
// Start computation of P byte sequence.
P := sha256.New()
// For every character in the password add the entire password.
for i = 0; i < len(key); i++ {
P.Write(key)
}
Psum := P.Sum(nil)
// Create byte sequence P.
Pseq := make([]byte, 0, len(key))
for i = len(key); i > 32; i -= 32 {
Pseq = append(Pseq, Psum...)
}
Pseq = append(Pseq, Psum[0:i]...)
// Start computation of S byte sequence.
S := sha256.New()
for i = 0; i < (16 + int(Asum[0])); i++ {
S.Write(salt)
}
Ssum := S.Sum(nil)
// Create byte sequence S.
Sseq := make([]byte, 0, len(salt))
for i = len(salt); i > 32; i -= 32 {
Sseq = append(Sseq, Ssum...)
}
Sseq = append(Sseq, Ssum[0:i]...)
Csum := Asum
// Repeatedly run the collected hash value through SHA256 to burn CPU cycles.
for i = 0; i < rounds; i++ {
C := sha256.New()
// Add key or last result.
if (i & 1) != 0 {
C.Write(Pseq)
} else {
C.Write(Csum)
}
// Add salt for numbers not divisible by 3.
if (i % 3) != 0 {
C.Write(Sseq)
}
// Add key for numbers not divisible by 7.
if (i % 7) != 0 {
C.Write(Pseq)
}
// Add key or last result.
if (i & 1) != 0 {
C.Write(Csum)
} else {
C.Write(Pseq)
}
Csum = C.Sum(nil)
}
out := make([]byte, 0, 80)
out = append(out, c.Salt.MagicPrefix...)
if isRoundsDef {
out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...)
}
out = append(out, salt...)
out = append(out, '$')
out = append(out, common.Base64_24Bit([]byte{
Csum[20], Csum[10], Csum[0],
Csum[11], Csum[1], Csum[21],
Csum[2], Csum[22], Csum[12],
Csum[23], Csum[13], Csum[3],
Csum[14], Csum[4], Csum[24],
Csum[5], Csum[25], Csum[15],
Csum[26], Csum[16], Csum[6],
Csum[17], Csum[7], Csum[27],
Csum[8], Csum[28], Csum[18],
Csum[29], Csum[19], Csum[9],
Csum[30], Csum[31],
})...)
// Clean sensitive data.
A.Reset()
Alternate.Reset()
P.Reset()
for i = 0; i < len(Asum); i++ {
Asum[i] = 0
}
for i = 0; i < len(AlternateSum); i++ {
AlternateSum[i] = 0
}
for i = 0; i < len(Pseq); i++ {
Pseq[i] = 0
}
return string(out), nil
}
func (c *crypter) Verify(hashedKey string, key []byte) error {
newHash, err := c.Generate(key, []byte(hashedKey))
if err != nil {
return err
}
if newHash != hashedKey {
return crypt.ErrKeyMismatch
}
return nil
}
func (c *crypter) Cost(hashedKey string) (int, error) {
saltToks := bytes.Split([]byte(hashedKey), []byte{'$'})
if len(saltToks) < 3 {
return 0, common.ErrSaltFormat
}
if !bytes.HasPrefix(saltToks[2], _rounds) {
return RoundsDefault, nil
}
roundToks := bytes.Split(saltToks[2], []byte{'='})
cost, err := strconv.ParseInt(string(roundToks[1]), 10, 0)
return int(cost), err
}
func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt }
func GetSalt() common.Salt {
return common.Salt{
MagicPrefix: []byte(MagicPrefix),
SaltLenMin: SaltLenMin,
SaltLenMax: SaltLenMax,
RoundsDefault: RoundsDefault,
RoundsMin: RoundsMin,
RoundsMax: RoundsMax,
}
}

View file

@ -0,0 +1,109 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package sha256_crypt
import "testing"
var sha256Crypt = New()
func TestGenerate(t *testing.T) {
data := []struct {
salt []byte
key []byte
out string
cost int
}{
{
[]byte("$5$saltstring"),
[]byte("Hello world!"),
"$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5",
RoundsDefault,
},
{
[]byte("$5$rounds=10000$saltstringsaltstring"),
[]byte("Hello world!"),
"$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFM" +
"z2.opqey6IcA",
10000,
},
{
[]byte("$5$rounds=5000$toolongsaltstring"),
[]byte("This is just a test"),
"$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPv" +
"OW8mGRcvxa5",
5000,
},
{
[]byte("$5$rounds=1400$anotherlongsaltstring"),
[]byte("a very much longer text to encrypt. " +
"This one even stretches over more" +
"than one line."),
"$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyx" +
"f12oP84Bnq1",
1400,
},
{
[]byte("$5$rounds=77777$short"),
[]byte("we have a short salt string but not a short password"),
"$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/",
77777,
},
{
[]byte("$5$rounds=123456$asaltof16chars.."),
[]byte("a short string"),
"$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPy" +
"zV/cZKmF/wJvD",
123456,
},
{
[]byte("$5$rounds=10$roundstoolow"),
[]byte("the minimum number is still observed"),
"$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/g" +
"L972bIC",
1000,
},
}
for i, d := range data {
hash, err := sha256Crypt.Generate(d.key, d.salt)
if err != nil {
t.Fatal(err)
}
if hash != d.out {
t.Errorf("Test %d failed\nExpected: %s, got: %s", i, d.out, hash)
}
cost, err := sha256Crypt.Cost(hash)
if err != nil {
t.Fatal(err)
}
if cost != d.cost {
t.Errorf("Test %d failed\nExpected: %d, got: %d", i, d.cost, cost)
}
}
}
func TestVerify(t *testing.T) {
data := [][]byte{
[]byte("password"),
[]byte("12345"),
[]byte("That's amazing! I've got the same combination on my luggage!"),
[]byte("And change the combination on my luggage!"),
[]byte(" random spa c ing."),
[]byte("94ajflkvjzpe8u3&*j1k513KLJ&*()"),
}
for i, d := range data {
hash, err := sha256Crypt.Generate(d, nil)
if err != nil {
t.Fatal(err)
}
if err = sha256Crypt.Verify(hash, d); err != nil {
t.Errorf("Test %d failed: %s", i, d)
}
}
}

View file

@ -0,0 +1,254 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Package sha512_crypt implements Ulrich Drepper's SHA512-crypt password
// hashing algorithm.
//
// The specification for this algorithm can be found here:
// http://www.akkadia.org/drepper/SHA-crypt.txt
package sha512_crypt
import (
"bytes"
"crypto/sha512"
"strconv"
"github.com/tredoe/osutil/user/crypt"
"github.com/tredoe/osutil/user/crypt/common"
)
func init() {
crypt.RegisterCrypt(crypt.SHA512, New, MagicPrefix)
}
const (
MagicPrefix = "$6$"
SaltLenMin = 1
SaltLenMax = 16
RoundsMin = 1000
RoundsMax = 999999999
RoundsDefault = 5000
)
var _rounds = []byte("rounds=")
type crypter struct{ Salt common.Salt }
// New returns a new crypt.Crypter computing the SHA512-crypt password hashing.
func New() crypt.Crypter {
return &crypter{GetSalt()}
}
func (c *crypter) Generate(key, salt []byte) (string, error) {
var rounds int
var isRoundsDef bool
if len(salt) == 0 {
salt = c.Salt.GenerateWRounds(SaltLenMax, RoundsDefault)
}
if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) {
return "", common.ErrSaltPrefix
}
saltToks := bytes.Split(salt, []byte{'$'})
if len(saltToks) < 3 {
return "", common.ErrSaltFormat
}
if bytes.HasPrefix(saltToks[2], _rounds) {
isRoundsDef = true
pr, err := strconv.ParseInt(string(saltToks[2][7:]), 10, 32)
if err != nil {
return "", common.ErrSaltRounds
}
rounds = int(pr)
if rounds < RoundsMin {
rounds = RoundsMin
} else if rounds > RoundsMax {
rounds = RoundsMax
}
salt = saltToks[3]
} else {
rounds = RoundsDefault
salt = saltToks[2]
}
if len(salt) > SaltLenMax {
salt = salt[0:SaltLenMax]
}
// Compute alternate SHA512 sum with input KEY, SALT, and KEY.
Alternate := sha512.New()
Alternate.Write(key)
Alternate.Write(salt)
Alternate.Write(key)
AlternateSum := Alternate.Sum(nil) // 64 bytes
A := sha512.New()
A.Write(key)
A.Write(salt)
// Add for any character in the key one byte of the alternate sum.
i := len(key)
for ; i > 64; i -= 64 {
A.Write(AlternateSum)
}
A.Write(AlternateSum[0:i])
// Take the binary representation of the length of the key and for every add
// the alternate sum, for every 0 the key.
for i = len(key); i > 0; i >>= 1 {
if (i & 1) != 0 {
A.Write(AlternateSum)
} else {
A.Write(key)
}
}
Asum := A.Sum(nil)
// Start computation of P byte sequence.
P := sha512.New()
// For every character in the password add the entire password.
for i = 0; i < len(key); i++ {
P.Write(key)
}
Psum := P.Sum(nil)
// Create byte sequence P.
Pseq := make([]byte, 0, len(key))
for i = len(key); i > 64; i -= 64 {
Pseq = append(Pseq, Psum...)
}
Pseq = append(Pseq, Psum[0:i]...)
// Start computation of S byte sequence.
S := sha512.New()
for i = 0; i < (16 + int(Asum[0])); i++ {
S.Write(salt)
}
Ssum := S.Sum(nil)
// Create byte sequence S.
Sseq := make([]byte, 0, len(salt))
for i = len(salt); i > 64; i -= 64 {
Sseq = append(Sseq, Ssum...)
}
Sseq = append(Sseq, Ssum[0:i]...)
Csum := Asum
// Repeatedly run the collected hash value through SHA512 to burn CPU cycles.
for i = 0; i < rounds; i++ {
C := sha512.New()
// Add key or last result.
if (i & 1) != 0 {
C.Write(Pseq)
} else {
C.Write(Csum)
}
// Add salt for numbers not divisible by 3.
if (i % 3) != 0 {
C.Write(Sseq)
}
// Add key for numbers not divisible by 7.
if (i % 7) != 0 {
C.Write(Pseq)
}
// Add key or last result.
if (i & 1) != 0 {
C.Write(Csum)
} else {
C.Write(Pseq)
}
Csum = C.Sum(nil)
}
out := make([]byte, 0, 123)
out = append(out, c.Salt.MagicPrefix...)
if isRoundsDef {
out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...)
}
out = append(out, salt...)
out = append(out, '$')
out = append(out, common.Base64_24Bit([]byte{
Csum[42], Csum[21], Csum[0],
Csum[1], Csum[43], Csum[22],
Csum[23], Csum[2], Csum[44],
Csum[45], Csum[24], Csum[3],
Csum[4], Csum[46], Csum[25],
Csum[26], Csum[5], Csum[47],
Csum[48], Csum[27], Csum[6],
Csum[7], Csum[49], Csum[28],
Csum[29], Csum[8], Csum[50],
Csum[51], Csum[30], Csum[9],
Csum[10], Csum[52], Csum[31],
Csum[32], Csum[11], Csum[53],
Csum[54], Csum[33], Csum[12],
Csum[13], Csum[55], Csum[34],
Csum[35], Csum[14], Csum[56],
Csum[57], Csum[36], Csum[15],
Csum[16], Csum[58], Csum[37],
Csum[38], Csum[17], Csum[59],
Csum[60], Csum[39], Csum[18],
Csum[19], Csum[61], Csum[40],
Csum[41], Csum[20], Csum[62],
Csum[63],
})...)
// Clean sensitive data.
A.Reset()
Alternate.Reset()
P.Reset()
for i = 0; i < len(Asum); i++ {
Asum[i] = 0
}
for i = 0; i < len(AlternateSum); i++ {
AlternateSum[i] = 0
}
for i = 0; i < len(Pseq); i++ {
Pseq[i] = 0
}
return string(out), nil
}
func (c *crypter) Verify(hashedKey string, key []byte) error {
newHash, err := c.Generate(key, []byte(hashedKey))
if err != nil {
return err
}
if newHash != hashedKey {
return crypt.ErrKeyMismatch
}
return nil
}
func (c *crypter) Cost(hashedKey string) (int, error) {
saltToks := bytes.Split([]byte(hashedKey), []byte{'$'})
if len(saltToks) < 3 {
return 0, common.ErrSaltFormat
}
if !bytes.HasPrefix(saltToks[2], _rounds) {
return RoundsDefault, nil
}
roundToks := bytes.Split(saltToks[2], []byte{'='})
cost, err := strconv.ParseInt(string(roundToks[1]), 10, 0)
return int(cost), err
}
func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt }
func GetSalt() common.Salt {
return common.Salt{
MagicPrefix: []byte(MagicPrefix),
SaltLenMin: SaltLenMin,
SaltLenMax: SaltLenMax,
RoundsDefault: RoundsDefault,
RoundsMin: RoundsMin,
RoundsMax: RoundsMax,
}
}

View file

@ -0,0 +1,111 @@
// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
// Copyright 2013, Jonas mg
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
package sha512_crypt
import "testing"
var sha512Crypt = New()
func TestGenerate(t *testing.T) {
data := []struct {
salt []byte
key []byte
out string
cost int
}{
{
[]byte("$6$saltstring"),
[]byte("Hello world!"),
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjn" +
"QJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1",
RoundsDefault,
},
{
[]byte("$6$rounds=10000$saltstringsaltstring"),
[]byte("Hello world!"),
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh" +
"0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.",
10000,
},
{
[]byte("$6$rounds=5000$toolongsaltstring"),
[]byte("This is just a test"),
"$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoN" +
"eKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0",
5000,
},
{
[]byte("$6$rounds=1400$anotherlongsaltstring"),
[]byte("a very much longer text to encrypt. " +
"This one even stretches over more" +
"than one line."),
"$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs" +
".wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1",
1400,
},
{
[]byte("$6$rounds=77777$short"),
[]byte("we have a short salt string but not a short password"),
"$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkv" +
"r0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0",
77777,
},
{
[]byte("$6$rounds=123456$asaltof16chars.."),
[]byte("a short string"),
"$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4o" +
"PwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1",
123456,
},
{
[]byte("$6$rounds=10$roundstoolow"),
[]byte("the minimum number is still observed"),
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50Yh" +
"H1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.",
1000,
},
}
for i, d := range data {
hash, err := sha512Crypt.Generate(d.key, d.salt)
if err != nil {
t.Fatal(err)
}
if hash != d.out {
t.Errorf("Test %d failed\nExpected: %s, got: %s", i, d.out, hash)
}
cost, err := sha512Crypt.Cost(hash)
if err != nil {
t.Fatal(err)
}
if cost != d.cost {
t.Errorf("Test %d failed\nExpected: %d, got: %d", i, d.cost, cost)
}
}
}
func TestVerify(t *testing.T) {
data := [][]byte{
[]byte("password"),
[]byte("12345"),
[]byte("That's amazing! I've got the same combination on my luggage!"),
[]byte("And change the combination on my luggage!"),
[]byte(" random spa c ing."),
[]byte("94ajflkvjzpe8u3&*j1k513KLJ&*()"),
}
for i, d := range data {
hash, err := sha512Crypt.Generate(d, nil)
if err != nil {
t.Fatal(err)
}
if err = sha512Crypt.Verify(hash, d); err != nil {
t.Errorf("Test %d failed: %s", i, d)
}
}
}

16
vendor/github.com/tredoe/osutil/user/crypt_test.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import "testing"
func TestLookupCrypter(t *testing.T) {
_, err := lookupCrypter()
if err != nil {
t.Fatal(err)
}
}

16
vendor/github.com/tredoe/osutil/user/dbfile.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
// Since tests will be done in temporary files, there is to use variables to
// change the values at testing.
var (
_USER_FILE = "/etc/passwd"
_GROUP_FILE = "/etc/group"
_SHADOW_FILE = "/etc/shadow"
_GSHADOW_FILE = "/etc/gshadow"
)

16
vendor/github.com/tredoe/osutil/user/doc.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/* Package user provides access to UNIX users database in local files.
You must have enough privileges to access to databases in shadowed files
'/etc/shadow' and '/etc/gshadow'. This usually means have to be root.
Note: those files are backed-up before of be modified.
In testing, to print the configuration read from the system, there is to use
"-v" flag.
*/
package user

72
vendor/github.com/tredoe/osutil/user/error.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"errors"
"fmt"
"strconv"
)
var (
ErrUserExist = errors.New("user already exists")
ErrGroupExist = errors.New("group already exists")
)
// IsExist returns whether the error is known to report that an user or group
// already exists. It is satisfied by ErrUserExist and ErrGroupExist.
func IsExist(err error) bool {
if err == ErrUserExist || err == ErrGroupExist {
return true
}
return false
}
// An IdUsedError reports the presence of an identifier already used.
type IdUsedError int
func (e IdUsedError) Error() string { return "id used: " + strconv.Itoa(int(e)) }
// A NoFoundError reports the absence of a value.
type NoFoundError struct {
file string
field string
value interface{}
}
func (e NoFoundError) Error() string {
return fmt.Sprintf("entry \"%v\" not found: file '%s', field %q",
e.value, e.file, e.field)
}
// A RequiredError reports the name of a required field.
type RequiredError string
func (e RequiredError) Error() string { return "required field: " + string(e) }
// An atoiError records the file, row and field that caused the error at turning
// a field from string to int.
type atoiError struct {
file string
row string
field string
}
func (e atoiError) Error() string {
return fmt.Sprintf("field %q on '%s' could not be turned to int\n%s",
e.field, e.file, e.row)
}
// A rowError records the file and row for a format not valid.
type rowError struct {
file string
row string
}
func (e rowError) Error() string {
return fmt.Sprintf("format of row not valid on '%s'\n%s", e.file, e.row)
}

194
vendor/github.com/tredoe/osutil/user/file.go generated vendored Normal file
View file

@ -0,0 +1,194 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"sync"
"github.com/tredoe/osutil/file"
)
// A row represents the structure of a row into a file.
type row interface {
// lookUp is the parser to looking for a value in the field of given line.
lookUp(line string, _field field, value interface{}) interface{}
// filename returns the file name belongs to the file structure.
filename() string
String() string
}
// A field represents a field into a row.
type field interface {
String() string
}
var errSearch = errors.New("no search")
// lookUp is a generic parser to looking for a value.
//
// The count determines the number of fields to return:
// n > 0: at most n fields
// n == 0: the result is nil (zero fields)
// n < 0: all fields
func lookUp(_row row, _field field, value interface{}, n int) (interface{}, error) {
if n == 0 {
return nil, errSearch
}
dbf, err := openDBFile(_row.filename(), os.O_RDONLY)
if err != nil {
return nil, err
}
defer dbf.close()
// Lines where a field is matched.
entries := make([]interface{}, 0, 0)
for {
line, _, err := dbf.rd.ReadLine()
if err == io.EOF {
break
}
entry := _row.lookUp(string(line), _field, value)
if entry != nil {
entries = append(entries, entry)
}
if n < 0 {
continue
} else if n == len(entries) {
break
}
}
if len(entries) != 0 {
return entries, nil
}
return nil, NoFoundError{_row.filename(), _field.String(), value}
}
// == Editing
//
// DO_BACKUP does a backup before of modify the original files.
var DO_BACKUP = true
// A dbfile represents the database file.
type dbfile struct {
sync.Mutex
file *os.File
rd *bufio.Reader
}
// openDBFile opens a file.
func openDBFile(filename string, flag int) (*dbfile, error) {
f, err := os.OpenFile(filename, flag, 0)
if err != nil {
return nil, err
}
db := &dbfile{file: f, rd: bufio.NewReader(f)}
db.Lock()
return db, nil
}
// close closes the file.
func (db *dbfile) close() error {
db.Unlock()
return db.file.Close()
}
// _FILES_BACKUPED are the files that already have been backuped.
var _FILES_BACKUPED = make(map[string]struct{}, 4)
// backup does a backup of a file.
func backup(filename string) error {
if DO_BACKUP {
if _, ok := _FILES_BACKUPED[filename]; !ok {
if err := file.Backup(filename); err != nil {
return err
}
_FILES_BACKUPED[filename] = struct{}{}
}
}
return nil
}
func edit(name string, r row) error { return _edit(name, r, false) }
func del(name string, r row) error { return _edit(name, r, true) }
// _edit is a generic editor for the given user/group name.
// If remove is true, it removes the structure of the user/group name.
//
// TODO: get better performance if start to store since when the file is edited.
// So there is to store the size of all lines read until that point to seek from
// there.
func _edit(name string, _row row, remove bool) (err error) {
filename := _row.filename()
dbf, err := openDBFile(filename, os.O_RDWR)
if err != nil {
return err
}
defer func() {
e := dbf.close()
if e != nil && err == nil {
err = e
}
}()
var buf bytes.Buffer
name_b := []byte(name)
isFound := false
for {
line, err2 := dbf.rd.ReadBytes('\n')
if err2 == io.EOF {
break
}
if !isFound && bytes.HasPrefix(line, name_b) {
isFound = true
if remove { // skip user
continue
}
line = []byte(_row.String())
}
if _, err = buf.Write(line); err != nil {
return err
}
}
if isFound {
if err = backup(filename); err != nil {
return
}
if _, err = dbf.file.Seek(0, os.SEEK_SET); err != nil {
return
}
var n int
n, err = dbf.file.Write(buf.Bytes())
if err != nil {
return
}
err = dbf.file.Truncate(int64(n))
}
return
}

494
vendor/github.com/tredoe/osutil/user/group.go generated vendored Normal file
View file

@ -0,0 +1,494 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type groupField int
// Field names for group database.
const (
G_NAME groupField = 1 << iota
G_PASSWD
G_GID
G_MEMBER
G_ALL
)
func (f groupField) String() string {
switch f {
case G_NAME:
return "Name"
case G_PASSWD:
return "Passwd"
case G_GID:
return "GID"
case G_MEMBER:
return "Member"
}
return "ALL"
}
// A Group represents the format of a group on the system.
type Group struct {
// Group name. (Unique)
Name string
// Hashed password
//
// The (hashed) group password. If this field is empty, no password is needed.
password string
// The numeric group ID. (Unique)
GID int
// User list
//
// A list of the usernames that are members of this group, separated by commas.
UserList []string
addSystemGroup bool
}
// AddGroup returns a new Group.
func NewGroup(name string, members ...string) *Group {
return &Group{
Name: name,
password: "",
GID: -1,
UserList: members,
}
}
// NewSystemGroup adds a system group.
func NewSystemGroup(name string, members ...string) *Group {
return &Group{
Name: name,
password: "",
GID: -1,
UserList: members,
addSystemGroup: true,
}
}
func (g *Group) filename() string { return _GROUP_FILE }
// IsOfSystem indicates whether it is a system group.
func (g *Group) IsOfSystem() bool {
//loadConfig()
if g.GID > config.login.SYS_GID_MIN && g.GID < config.login.SYS_GID_MAX {
return true
}
return false
}
func (g *Group) String() string {
return fmt.Sprintf("%s:%s:%d:%s\n",
g.Name, g.password, g.GID, strings.Join(g.UserList, ","))
}
// parseGroup parses the row of a group.
func parseGroup(row string) (*Group, error) {
fields := strings.Split(row, ":")
if len(fields) != 4 {
return nil, rowError{_GROUP_FILE, row}
}
gid, err := strconv.Atoi(fields[2])
if err != nil {
return nil, atoiError{_GROUP_FILE, row, "GID"}
}
return &Group{
Name: fields[0],
password: fields[1],
GID: gid,
UserList: strings.Split(fields[3], ","),
}, nil
}
// == Lookup
//
// lookUp parses the group line searching a value into the field.
// Returns nil if it is not found.
func (*Group) lookUp(line string, f field, value interface{}) interface{} {
_field := f.(groupField)
allField := strings.Split(line, ":")
arrayField := make(map[int][]string)
intField := make(map[int]int)
arrayField[3] = strings.Split(allField[3], ",")
// Check integers
var err error
if intField[2], err = strconv.Atoi(allField[2]); err != nil {
panic(atoiError{_GROUP_FILE, line, "GID"})
}
// Check fields
var isField bool
if G_NAME&_field != 0 && allField[0] == value.(string) {
isField = true
} else if G_PASSWD&_field != 0 && allField[1] == value.(string) {
isField = true
} else if G_GID&_field != 0 && intField[2] == value.(int) {
isField = true
} else if G_MEMBER&_field != 0 && checkGroup(arrayField[3], value.(string)) {
isField = true
} else if G_ALL&_field != 0 {
isField = true
}
if isField {
return &Group{
Name: allField[0],
password: allField[1],
GID: intField[2],
UserList: arrayField[3],
}
}
return nil
}
// LookupGID looks up a group by group ID.
func LookupGID(gid int) (*Group, error) {
entries, err := LookupInGroup(G_GID, gid, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupGroup looks up a group by name.
func LookupGroup(name string) (*Group, error) {
entries, err := LookupInGroup(G_NAME, name, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupInGroup looks up a group by the given values.
//
// The count determines the number of fields to return:
// n > 0: at most n fields
// n == 0: the result is nil (zero fields)
// n < 0: all fields
func LookupInGroup(field groupField, value interface{}, n int) ([]*Group, error) {
iEntries, err := lookUp(&Group{}, field, value, n)
if err != nil {
return nil, err
}
// == Convert to type group
valueSlice := reflect.ValueOf(iEntries)
entries := make([]*Group, valueSlice.Len())
for i := 0; i < valueSlice.Len(); i++ {
entries[i] = valueSlice.Index(i).Interface().(*Group)
}
return entries, err
}
// Getgroups returns a list of the numeric ids of groups that the caller
// belongs to.
func Getgroups() []int {
user := GetUsername()
list := make([]int, 0)
// The user could have its own group.
if g, err := LookupGroup(user); err == nil {
list = append(list, g.GID)
}
groups, err := LookupInGroup(G_MEMBER, user, -1)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
panic(err)
}
}
for _, v := range groups {
list = append(list, v.GID)
}
return list
}
// GetgroupsName returns a list of the groups that the caller belongs to.
func GetgroupsName() []string {
user := GetUsername()
list := make([]string, 0)
// The user could have its own group.
if _, err := LookupGroup(user); err == nil {
list = append(list, user)
}
groups, err := LookupInGroup(G_MEMBER, user, -1)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
panic(err)
}
}
for _, v := range groups {
list = append(list, v.Name)
}
return list
}
// == Editing
//
// AddGroup adds a group.
func AddGroup(name string, members ...string) (gid int, err error) {
s := NewGShadow(name, members...)
if err = s.Add(nil); err != nil {
return
}
return NewGroup(name, members...).Add()
}
// AddSystemGroup adds a system group.
func AddSystemGroup(name string, members ...string) (gid int, err error) {
s := NewGShadow(name, members...)
if err = s.Add(nil); err != nil {
return
}
return NewSystemGroup(name, members...).Add()
}
// Add adds a new group.
// Whether GID is < 0, it will choose the first id available in the range set
// in the system configuration.
func (g *Group) Add() (gid int, err error) {
loadConfig()
group, err := LookupGroup(g.Name)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
return 0, err
}
}
if group != nil {
return 0, ErrGroupExist
}
if g.Name == "" {
return 0, RequiredError("Name")
}
var db *dbfile
if g.GID < 0 {
db, gid, err = nextGUID(g.addSystemGroup)
if err != nil {
db.close()
return 0, err
}
g.GID = gid
} else {
db, err = openDBFile(_GROUP_FILE, os.O_WRONLY|os.O_APPEND)
if err != nil {
return
}
// Check if Id is unique.
_, err = LookupGID(g.GID)
if err == nil {
return 0, IdUsedError(g.GID)
} else if _, ok := err.(NoFoundError); !ok {
return 0, err
}
}
g.password = "x"
_, err = db.file.WriteString(g.String())
err2 := db.close()
if err2 != nil && err == nil {
err = err2
}
return
}
// DelGroup removes a group from the system.
func DelGroup(name string) (err error) {
err = del(name, &Group{})
if err == nil {
err = del(name, &GShadow{})
}
return
}
// AddUsersToGroup adds the members to a group.
func AddUsersToGroup(name string, members ...string) error {
if len(members) == 0 {
return fmt.Errorf("no members to add")
}
for i, v := range members {
if v == "" {
return EmptyMemberError(fmt.Sprintf("members[%s]", strconv.Itoa(i)))
}
}
// Group
gr, err := LookupGroup(name)
if err != nil {
return err
}
if err = _addMembers(&gr.UserList, members...); err != nil {
return err
}
// Shadow group
sg, err := LookupGShadow(name)
if err != nil {
return err
}
if err = _addMembers(&sg.UserList, members...); err != nil {
return err
}
// Editing
if err = edit(name, gr); err != nil {
return err
}
if err = edit(name, sg); err != nil {
return err
}
return nil
}
func _addMembers(userList *[]string, members ...string) error {
// Check if some member is already in the file.
for _, u := range *userList {
for _, m := range members {
if u == m {
return fmt.Errorf("user %q is already set", u)
}
}
}
if len(*userList) == 1 && (*userList)[0] == "" {
*userList = members
} else {
*userList = append(*userList, members...)
}
return nil
}
// DelUsersInGroup removes the specific members from a group.
func DelUsersInGroup(name string, members ...string) error {
if len(members) == 0 {
return ErrNoMembers
}
for i, v := range members {
if v == "" {
return EmptyMemberError(fmt.Sprintf("members[%s]", strconv.Itoa(i)))
}
}
// Group
gr, err := LookupGroup(name)
if err != nil {
return err
}
if err = _delMembers(&gr.UserList, members...); err != nil {
return err
}
// Shadow group
sg, err := LookupGShadow(name)
if err != nil {
return err
}
if err = _delMembers(&sg.UserList, members...); err != nil {
return err
}
// Editing
if err = edit(name, gr); err != nil {
return err
}
if err = edit(name, sg); err != nil {
return err
}
return nil
}
func _delMembers(userList *[]string, members ...string) error {
if len(*userList) == 1 && (*userList)[0] == "" {
return ErrNoMembers
}
newUserList := make([]string, 0)
for _, u := range *userList {
found := false
for _, m := range members {
if u == m {
found = true
break
}
}
if !found {
newUserList = append(newUserList, u)
}
}
if len(newUserList) == len(*userList) {
return ErrNoMembers
}
*userList = make([]string, len(newUserList))
for i, v := range newUserList {
(*userList)[i] = v
}
return nil
}
// == Utility
//
// checkGroup indicates if a value is into a group.
func checkGroup(group []string, value string) bool {
for _, v := range group {
if v == value {
return true
}
}
return false
}
// == Errors
//
var ErrNoMembers = errors.New("no members to remove")
// EmptyMemberError reports an empty member.
type EmptyMemberError string
func (e EmptyMemberError) Error() string { return "empty field: " + string(e) }

234
vendor/github.com/tredoe/osutil/user/group_test.go generated vendored Normal file
View file

@ -0,0 +1,234 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"bufio"
"io"
"os"
"testing"
)
func TestGroupParser(t *testing.T) {
f, err := os.Open(_GROUP_FILE)
if err != nil {
t.Fatal(err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
break
}
t.Error(err)
continue
}
if _, err = parseGroup(string(line)); err != nil {
t.Error(err)
}
}
}
func TestGroupFull(t *testing.T) {
entry, err := LookupGID(os.Getgid())
if err != nil || entry == nil {
t.Error(err)
}
entry, err = LookupGroup("root")
if err != nil || entry == nil {
t.Error(err)
}
entries, err := LookupInGroup(G_MEMBER, "", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInGroup(G_ALL, nil, -1)
if err != nil || len(entries) == 0 {
t.Error(err)
}
}
func TestGroupCount(t *testing.T) {
count := 5
entries, err := LookupInGroup(G_ALL, nil, count)
if err != nil || len(entries) != count {
t.Error(err)
}
}
func TestGroupError(t *testing.T) {
_, err := LookupGroup("!!!???")
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to report NoFoundError")
}
if _, err = LookupInGroup(G_MEMBER, "", 0); err != errSearch {
t.Error("expected to report errSearch")
}
g := &Group{}
if _, err = g.Add(); err != RequiredError("Name") {
t.Error("expected to report RequiredError")
}
}
func TestGetGroups(t *testing.T) {
gids := Getgroups()
gnames := GetgroupsName()
for i, gid := range gids {
g, err := LookupGID(gid)
if err != nil {
t.Error(err)
}
if g.Name != gnames[i] {
t.Errorf("expected to match GID and group name")
}
}
}
func TestGroup_Add(t *testing.T) {
group := NewGroup(GROUP, MEMBERS...)
_testGroup_Add(t, group, MEMBERS, false)
group = NewSystemGroup(SYS_GROUP, MEMBERS...)
_testGroup_Add(t, group, MEMBERS, true)
}
func _testGroup_Add(t *testing.T, group *Group, members []string, ofSystem bool) {
prefix := "group"
if ofSystem {
prefix = "system " + prefix
}
id, err := group.Add()
if err != nil {
t.Fatal(err)
}
if id == -1 {
t.Errorf("%s: got UID = -1", prefix)
}
if _, err = group.Add(); err == nil {
t.Fatalf("%s: an existent group can not be added again", prefix)
} else {
if !IsExist(err) {
t.Errorf("%s: expected to report ErrExist", prefix)
}
}
if ofSystem {
if !group.IsOfSystem() {
t.Errorf("%s: IsOfSystem(): expected true")
}
} else {
if group.IsOfSystem() {
t.Errorf("%s: IsOfSystem(): expected false")
}
}
// Check value stored
name := ""
if ofSystem {
name = SYS_GROUP
} else {
name = GROUP
}
g, err := LookupGroup(name)
if err != nil {
t.Fatalf("%s: ", err)
}
if g.Name != name {
t.Errorf("%s: expected to get name %q", prefix, name)
}
if g.UserList[0] != members[0] || g.UserList[1] != members[1] {
t.Error("%s: expected to get members: %s", prefix, g.UserList)
}
}
func TestGroup_Members(t *testing.T) {
group := "g1"
member := "m0"
_, err := AddGroup(group, MEMBERS...)
if err != nil {
t.Fatal(err)
}
g_first, err := LookupGroup(group)
if err != nil {
t.Fatal(err)
}
sg_first, err := LookupGShadow(group)
if err != nil {
t.Fatal(err)
}
err = AddUsersToGroup(group, member)
if err != nil {
t.Fatal(err)
}
g_last, err := LookupGroup(group)
if err != nil {
t.Fatal(err)
}
sg_last, err := LookupGShadow(group)
if err != nil {
t.Fatal(err)
}
if len(g_first.UserList) == len(g_last.UserList) ||
g_last.UserList[0] != USER ||
g_last.UserList[1] != SYS_USER ||
g_last.UserList[2] != member {
t.Error("group file: expected to add users into a group")
}
if len(sg_first.UserList) == len(sg_last.UserList) ||
sg_last.UserList[0] != USER ||
sg_last.UserList[1] != SYS_USER ||
sg_last.UserList[2] != member {
t.Error("gshadow file: expected to add users into a group")
}
// == Delete
err = DelUsersInGroup(group, member, USER)
if err != nil {
t.Fatal(err)
}
g_del, err := LookupGroup(group)
if err != nil {
t.Fatal(err)
}
sg_del, err := LookupGShadow(group)
if err != nil {
t.Fatal(err)
}
if len(g_del.UserList) == len(g_last.UserList) ||
g_del.UserList[0] != SYS_USER {
t.Error("group file: expected to remove members of a group")
}
if len(sg_del.UserList) == len(sg_last.UserList) ||
sg_del.UserList[0] != SYS_USER {
t.Error("gshadow file: expected to remove members of a group")
}
}

237
vendor/github.com/tredoe/osutil/user/gshadow.go generated vendored Normal file
View file

@ -0,0 +1,237 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"fmt"
"os"
"reflect"
"strings"
)
type gshadowField int
// Field names for shadowed group database.
const (
GS_NAME gshadowField = 1 << iota
GS_PASSWD
GS_ADMIN
GS_MEMBER
GS_ALL
)
func (f gshadowField) String() string {
switch f {
case GS_NAME:
return "Name"
case GS_PASSWD:
return "Passwd"
case GS_ADMIN:
return "Admin"
case GS_MEMBER:
return "Member"
}
return "ALL"
}
// A GShadow represents the format of the shadowed information for a group account.
type GShadow struct {
// Group name. (Unique)
//
// It must be a valid group name, which exist on the system.
Name string
// Hashed password
//
// If the password field contains some string that is not a valid result of
// crypt, for instance "!" or "*", users will not be able to use a unix
// password to access the group (but group members do not need the password).
//
// The password is used when an user who is not a member of the group wants
// to gain the permissions of this group (see "newgrp(1)").
//
// This field may be empty, in which case only the group members can gain
// the group permissions.
//
// A password field which starts with a exclamation mark means that the
// password is locked. The remaining characters on the line represent the
// password field before the password was locked.
//
// This password supersedes any password specified in '/etc/group'.
password string
// Group administrator list
//
// It must be a comma-separated list of user names.
//
// Administrators can change the password or the members of the group.
// Administrators also have the same permissions as the members (see below).
AdminList []string
// Group member list
//
// It must be a comma-separated list of user names.
//
// Members can access the group without being prompted for a password.
// You should use the same list of users as in /etc/group.
UserList []string
}
// NewGShadow returns a new GShadow.
func NewGShadow(username string, members ...string) *GShadow {
return &GShadow{
Name: username,
UserList: members,
}
}
func (gs *GShadow) filename() string { return _GSHADOW_FILE }
func (gs *GShadow) String() string {
return fmt.Sprintf("%s:%s:%s:%s\n",
gs.Name, gs.password, strings.Join(gs.AdminList, ","), strings.Join(gs.UserList, ","))
}
// parseGShadow parses the row of a group shadow.
func parseGShadow(row string) (*GShadow, error) {
fields := strings.Split(row, ":")
if len(fields) != 4 {
return nil, rowError{_GSHADOW_FILE, row}
}
return &GShadow{
fields[0],
fields[1],
strings.Split(fields[2], ","),
strings.Split(fields[3], ","),
}, nil
}
// == Lookup
//
// lookUp parses the shadowed group line searching a value into the field.
// Returns nil if it isn't found.
func (*GShadow) lookUp(line string, f field, value interface{}) interface{} {
_field := f.(gshadowField)
_value := value.(string)
allField := strings.Split(line, ":")
arrayField := make(map[int][]string)
arrayField[2] = strings.Split(allField[2], ",")
arrayField[3] = strings.Split(allField[3], ",")
// Check fields
var isField bool
if GS_NAME&_field != 0 && allField[0] == _value {
isField = true
} else if GS_PASSWD&_field != 0 && allField[1] == _value {
isField = true
} else if GS_ADMIN&_field != 0 && checkGroup(arrayField[2], _value) {
isField = true
} else if GS_MEMBER&_field != 0 && checkGroup(arrayField[3], _value) {
isField = true
} else if GS_ALL&_field != 0 {
isField = true
}
if isField {
return &GShadow{
allField[0],
allField[1],
arrayField[2],
arrayField[3],
}
}
return nil
}
// LookupGShadow looks up a shadowed group by name.
func LookupGShadow(name string) (*GShadow, error) {
entries, err := LookupInGShadow(GS_NAME, name, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupInGShadow looks up a shadowed group by the given values.
//
// The count determines the number of fields to return:
// n > 0: at most n fields
// n == 0: the result is nil (zero fields)
// n < 0: all fields
func LookupInGShadow(field gshadowField, value string, n int) ([]*GShadow, error) {
checkRoot()
iEntries, err := lookUp(&GShadow{}, field, value, n)
if err != nil {
return nil, err
}
// == Convert to type GShadow
valueSlice := reflect.ValueOf(iEntries)
entries := make([]*GShadow, valueSlice.Len())
for i := 0; i < valueSlice.Len(); i++ {
entries[i] = valueSlice.Index(i).Interface().(*GShadow)
}
return entries, err
}
// == Editing
//
// Add adds a new shadowed group.
// If the key is not nil, generates a hashed password.
//
// It is created a backup before of modify the original file.
func (gs *GShadow) Add(key []byte) (err error) {
loadConfig()
gshadow, err := LookupGShadow(gs.Name)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
return
}
}
if gshadow != nil {
return ErrGroupExist
}
if gs.Name == "" {
return RequiredError("Name")
}
// Backup
if err = backup(_GSHADOW_FILE); err != nil {
return
}
db, err := openDBFile(_GSHADOW_FILE, os.O_WRONLY|os.O_APPEND)
if err != nil {
return
}
defer func() {
e := db.close()
if e != nil && err == nil {
err = e
}
}()
if key != nil {
gs.password, _ = config.crypter.Generate(key, nil)
} else {
gs.password = "*" // Password disabled.
}
_, err = db.file.WriteString(gs.String())
return
}

129
vendor/github.com/tredoe/osutil/user/gshadow_test.go generated vendored Normal file
View file

@ -0,0 +1,129 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"bufio"
"io"
"os"
"testing"
)
func TestGShadowParser(t *testing.T) {
f, err := os.Open(_GSHADOW_FILE)
if err != nil {
t.Fatal(err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
break
}
t.Error(err)
continue
}
if _, err = parseGShadow(string(line)); err != nil {
t.Error(err)
}
}
}
func TestGShadowFull(t *testing.T) {
entry, err := LookupGShadow("root")
if err != nil || entry == nil {
t.Error(err)
}
entries, err := LookupInGShadow(GS_PASSWD, "!", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInGShadow(GS_ALL, "", -1)
if err != nil || len(entries) == 0 {
t.Error(err)
}
}
func TestGShadowCount(t *testing.T) {
count := 5
entries, err := LookupInGShadow(GS_ALL, "", count)
if err != nil || len(entries) != count {
t.Error(err)
}
}
func TestGShadowError(t *testing.T) {
_, err := LookupGShadow("!!!???")
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to report NoFoundError")
}
if _, err = LookupInGShadow(GS_MEMBER, "", 0); err != errSearch {
t.Error("expected to report errSearch")
}
gs := &GShadow{}
if err = gs.Add(nil); err != RequiredError("Name") {
t.Error("expected to report RequiredError")
}
}
func TestGShadow_Add(t *testing.T) {
shadow := NewGShadow(GROUP, MEMBERS...)
err := shadow.Add(nil)
if err != nil {
t.Fatal(err)
}
if err = shadow.Add(nil); err == nil {
t.Fatal("a shadowed group existent can not be added again")
} else {
if !IsExist(err) {
t.Error("shadowed group: expected to report ErrExist")
}
}
s, err := LookupGShadow(GROUP)
if err != nil {
t.Fatal(err)
}
if s.Name != GROUP {
t.Errorf("shadowed group: expected to get name %q", GROUP)
}
}
var (
GROUP_KEY1 = []byte("abc")
GROUP_KEY2 = []byte("def")
)
func TestGShadowCrypt(t *testing.T) {
gs, err := LookupGShadow(GROUP)
if err != nil {
t.Fatal(err)
}
gs.Passwd(GROUP_KEY1)
if err = config.crypter.Verify(gs.password, GROUP_KEY1); err != nil {
t.Fatalf("expected to get the same hashed password for %q", GROUP_KEY1)
}
if err = ChGPasswd(GROUP, GROUP_KEY2); err != nil {
t.Fatalf("expected to change password: %s", err)
}
gs, _ = LookupGShadow(GROUP)
if err = config.crypter.Verify(gs.password, GROUP_KEY2); err != nil {
t.Fatalf("ChGPasswd: expected to get the same hashed password for %q", GROUP_KEY2)
}
}

179
vendor/github.com/tredoe/osutil/user/id.go generated vendored Normal file
View file

@ -0,0 +1,179 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"io"
"os"
"strconv"
)
// nextUID returns the next free user id to use, according to whether it is a
// system's user.
func nextUID(isSystem bool) (db *dbfile, uid int, err error) {
loadConfig()
db, err = openDBFile(_USER_FILE, os.O_RDWR)
if err != nil {
return
}
// Seek to file half size.
info, err := db.file.Stat()
if err != nil {
db.close()
return nil, 0, err
}
if _, err = db.file.Seek(info.Size()/2, os.SEEK_SET); err != nil {
db.close()
return nil, 0, err
}
// To starting to read from a new line
if _, _, err = db.rd.ReadLine(); err != nil {
db.close()
return nil, 0, err
}
var minUid, maxUid int
if isSystem {
minUid, maxUid = config.login.SYS_UID_MIN, config.login.SYS_UID_MAX
} else {
minUid, maxUid = config.login.UID_MIN, config.login.UID_MAX
}
for {
line, _, err := db.rd.ReadLine()
if err == io.EOF {
break
}
u, err := parseUser(string(line))
if err != nil {
db.close()
return nil, 0, err
}
if u.UID >= minUid && u.UID <= maxUid {
uid = u.UID
}
}
uid++
if uid == maxUid {
return nil, 0, &IdRangeError{maxUid, isSystem, true}
}
return
}
// nextGUID returns the next free group id to use, according to whether it is a
// system's group.
func nextGUID(isSystem bool) (db *dbfile, gid int, err error) {
loadConfig()
db, err = openDBFile(_GROUP_FILE, os.O_RDWR)
if err != nil {
return
}
// Seek to file half size.
info, err := db.file.Stat()
if err != nil {
db.close()
return nil, 0, err
}
if _, err = db.file.Seek(info.Size()/2, os.SEEK_SET); err != nil {
db.close()
return nil, 0, err
}
// To starting to read from a new line
if _, _, err = db.rd.ReadLine(); err != nil {
db.close()
return nil, 0, err
}
var minGid, maxGid int
if isSystem {
minGid, maxGid = config.login.SYS_GID_MIN, config.login.SYS_GID_MAX
} else {
minGid, maxGid = config.login.GID_MIN, config.login.GID_MAX
}
for {
line, _, err := db.rd.ReadLine()
if err == io.EOF {
break
}
gr, err := parseGroup(string(line))
if err != nil {
db.close()
return nil, 0, err
}
if gr.GID >= minGid && gr.GID <= maxGid {
gid = gr.GID
}
}
gid++
if gid == maxGid {
return nil, 0, &IdRangeError{maxGid, isSystem, false}
}
return
}
// NextSystemUID returns the next free system user id to use.
func NextSystemUID() (int, error) {
db, uid, err := nextUID(true)
db.close()
return uid, err
}
// NextSystemGID returns the next free system group id to use.
func NextSystemGID() (int, error) {
db, gid, err := nextGUID(true)
db.close()
return gid, err
}
// NextUID returns the next free user id to use.
func NextUID() (int, error) {
db, uid, err := nextUID(false)
db.close()
return uid, err
}
// NextGID returns the next free group id to use.
func NextGID() (int, error) {
db, gid, err := nextGUID(false)
db.close()
return gid, err
}
// * * *
// An IdRangeError records an error during the search for a free id to use.
type IdRangeError struct {
LastId int
IsSystem bool
IsUser bool
}
func (e *IdRangeError) Error() string {
str := ""
if e.IsSystem {
str = "system "
}
if e.IsUser {
str += "user: "
} else {
str += "group: "
}
str += strconv.Itoa(e.LastId)
return "reached maximum identifier in " + str
}

46
vendor/github.com/tredoe/osutil/user/id_test.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"fmt"
"testing"
)
// TestAddGroup add a new group, so the next GIDs will have a value greater than
// in the systems file.
func TestID(t *testing.T) {
id, err := NextSystemUID()
if err != nil {
t.Error(err)
}
if testing.Verbose() {
fmt.Print(" Next system UID: ", id)
}
if id, err = NextUID(); err != nil {
t.Error(err)
}
if testing.Verbose() {
fmt.Println("\tNext UID:", id)
}
if id, err = NextSystemGID(); err != nil {
t.Error(err)
}
if testing.Verbose() {
fmt.Print(" Next system GID: ", id)
}
if id, err = NextGID(); err != nil {
t.Error(err)
}
if testing.Verbose() {
fmt.Println("\tNext GID:", id)
}
}

477
vendor/github.com/tredoe/osutil/user/shadow.go generated vendored Normal file
View file

@ -0,0 +1,477 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
)
type shadowField int
// Field names for shadowed password database.
const (
S_NAME shadowField = 1 << iota
S_PASSWD
S_CHANGED
S_MIN
S_MAX
S_WARN
S_INACTIVE
S_EXPIRE
S_FLAG
S_ALL
)
func (f shadowField) String() string {
switch f {
case S_NAME:
return "Name"
case S_PASSWD:
return "Passwd"
case S_CHANGED:
return "Changed"
case S_MIN:
return "Min"
case S_MAX:
return "Max"
case S_WARN:
return "Warn"
case S_INACTIVE:
return "Inactive"
case S_EXPIRE:
return "Expire"
case S_FLAG:
return "Flag"
}
return "ALL"
}
// changeType represents the options for last password change:
//
// < 0: disable aging
// 0: change password
// 1: enable aging
// > 1: number of days
type changeType int
const (
_DISABLE_AGING changeType = -1 + iota
_CHANGE_PASSWORD
_ENABLE_AGING
)
func (c changeType) String() string {
if c == _DISABLE_AGING {
return ""
}
return strconv.Itoa(int(c))
}
func parseChange(s string) (changeType, error) {
if s == "" {
return _DISABLE_AGING, nil
}
i, err := strconv.Atoi(s)
return changeType(i), err
}
// A Shadow represents the format of the information for a system's account and
// optional aging information.
//
// The fields "changed" and "expire" deal with days from Jan 1, 1970; but since
// package "time" deals with seconds, there is to divide it between the seconds
// that a day has (24*60*60) which is done by functions "setChange" and
// "SetExpire".
//
// To simulate an empty field in numeric fields, it is used a negative value.
type Shadow struct {
// Login name. (Unique)
//
// It must be a valid account name, which exist on the system.
Name string
// Hashed password
//
// If the password field contains some string that is not a valid result of
// crypt, for instance "!" or "*", the user will not be able to use a unix
// password to log in (but the user may log in the system by other means).
//
// This field may be empty, in which case no passwords are required to
// authenticate as the specified login name. However, some applications
// which read the '/etc/shadow' file may decide not to permit any access at
// all if the password field is empty.
//
// A password field which starts with a exclamation mark means that the
// password is locked. The remaining characters on the line represent the
// password field before the password was locked.
password string
// Date of last password change
//
// The date of the last password change, expressed as the number of days
// since Jan 1, 1970.
//
// The value 0 has a special meaning, which is that the user should change
// her pasword the next time he will log in the system.
//
// An empty field means that password aging features are disabled.
changed changeType
// Minimum password age
//
// The minimum password age is the number of days the user will have to wait
// before he will be allowed to change her password again.
//
// An empty field and value 0 mean that there are no minimum password age.
Min int
// Maximum password age
//
// The maximum password age is the number of days after which the user will
// have to change her password.
//
// After this number of days is elapsed, the password may still be valid.
// The user should be asked to change her password the next time he will
// log in.
//
// An empty field means that there are no maximum password age, no password
// warning period, and no password inactivity period (see below).
//
// If the maximum password age is lower than the minimum password age, the
// user cannot change her password.
Max int
// Password warning period
//
// The number of days before a password is going to expire (see the maximum
// password age above) during which the user should be warned.
//
// An empty field and value 0 mean that there are no password warning period.
Warn int
// Password inactivity period
//
// The number of days after a password has expired (see the maximum password
// age above) during which the password should still be accepted (and the
// user should update her password during the next login).
//
// After expiration of the password and this expiration period is elapsed,
// no login is possible using the current user's password.
// The user should contact her administrator.
//
// An empty field means that there are no enforcement of an inactivity period.
Inactive int
// Account expiration date
//
// The date of expiration of the account, expressed as the number of days
// since Jan 1, 1970.
//
// Note that an account expiration differs from a password expiration. In
// case of an acount expiration, the user shall not be allowed to login. In
// case of a password expiration, the user is not allowed to login using her
// password.
//
// An empty field means that the account will never expire.
//
// The value 0 should not be used as it is interpreted as either an account
// with no expiration, or as an expiration on Jan 1, 1970.
expire int
// Reserved field
//
// This field is reserved for future use.
flag int
}
// NewShadow returns a structure Shadow with fields "Min", "Max" and "Warn"
// got from the system configuration, and enabling the features of password aging.
func NewShadow(username string) *Shadow {
loadConfig()
return &Shadow{
Name: username,
changed: _ENABLE_AGING,
Min: config.login.PASS_MIN_DAYS,
Max: config.login.PASS_MAX_DAYS,
Warn: config.login.PASS_WARN_AGE,
}
}
// setChange sets the date of the last password change to the current one.
func (s *Shadow) setChange() { s.changed = changeType(secToDay(time.Now().Unix())) }
// SetChangePasswd sets the account for that the user change her pasword the
// next time he will log in the system.
func (s *Shadow) SetChangePasswd() { s.changed = _CHANGE_PASSWORD }
// DisableAging disables the features of password aging.
func (s *Shadow) DisableAging() { s.changed = _DISABLE_AGING }
// EnableAging enables the features of password aging.
func (s *Shadow) EnableAging() { s.setChange() }
// SetExpire sets the date of expiration of the account.
func (s *Shadow) SetExpire(t *time.Time) { s.expire = secToDay(t.Unix()) }
func (s *Shadow) filename() string { return _SHADOW_FILE }
func (s *Shadow) String() string {
var inactive, expire, flag string
// Optional fields
if s.Inactive != 0 {
inactive = strconv.Itoa(s.Inactive)
}
if s.expire != 0 {
expire = strconv.Itoa(s.expire)
}
if s.flag != 0 {
flag = strconv.Itoa(s.flag)
}
return fmt.Sprintf("%s:%s:%s:%d:%d:%d:%s:%s:%s\n",
s.Name, s.password, s.changed, s.Min, s.Max, s.Warn, inactive, expire, flag)
}
// parseShadow parses the row of a shadowed password.
func parseShadow(row string) (*Shadow, error) {
fields := strings.Split(row, ":")
if len(fields) != 9 {
return nil, rowError{_SHADOW_FILE, row}
}
var inactive, expire, flag int
changed, err := parseChange(fields[2])
if err != nil {
return nil, atoiError{_SHADOW_FILE, row, "changed"}
}
min, err := strconv.Atoi(fields[3])
if err != nil {
return nil, atoiError{_SHADOW_FILE, row, "Min"}
}
max, err := strconv.Atoi(fields[4])
if err != nil {
return nil, atoiError{_SHADOW_FILE, row, "Max"}
}
warn, err := strconv.Atoi(fields[5])
if err != nil {
return nil, atoiError{_SHADOW_FILE, row, "Warn"}
}
// Optional fields
if fields[6] != "" {
if inactive, err = strconv.Atoi(fields[6]); err != nil {
return nil, atoiError{_SHADOW_FILE, row, "Inactive"}
}
}
if fields[7] != "" {
if expire, err = strconv.Atoi(fields[7]); err != nil {
return nil, atoiError{_SHADOW_FILE, row, "expire"}
}
}
if fields[8] != "" {
if flag, err = strconv.Atoi(fields[8]); err != nil {
return nil, atoiError{_SHADOW_FILE, row, "flag"}
}
}
return &Shadow{
fields[0],
fields[1],
changed,
min,
max,
warn,
inactive,
expire,
flag,
}, nil
}
// == Lookup
//
// lookUp parses the shadow passwd line searching a value into the field.
// Returns nil if is not found.
func (*Shadow) lookUp(line string, f field, value interface{}) interface{} {
_field := f.(shadowField)
allField := strings.Split(line, ":")
intField := make(map[int]int)
// Check integers
changed, err := parseChange(allField[2])
if err != nil {
panic(atoiError{_SHADOW_FILE, line, "changed"})
}
if intField[3], err = strconv.Atoi(allField[3]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "Min"})
}
if intField[4], err = strconv.Atoi(allField[4]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "Max"})
}
if intField[5], err = strconv.Atoi(allField[5]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "Warn"})
}
// These fields could be empty.
if allField[6] != "" {
if intField[6], err = strconv.Atoi(allField[6]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "Inactive"})
}
}
if allField[7] != "" {
if intField[7], err = strconv.Atoi(allField[7]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "expire"})
}
}
if allField[8] != "" {
if intField[8], err = strconv.Atoi(allField[8]); err != nil {
panic(atoiError{_SHADOW_FILE, line, "flag"})
}
}
// Check fields
var isField bool
if S_NAME&_field != 0 && allField[0] == value.(string) {
isField = true
} else if S_PASSWD&_field != 0 && allField[1] == value.(string) {
isField = true
} else if S_CHANGED&_field != 0 && int(changed) == value.(int) {
isField = true
} else if S_MIN&_field != 0 && intField[3] == value.(int) {
isField = true
} else if S_MAX&_field != 0 && intField[4] == value.(int) {
isField = true
} else if S_WARN&_field != 0 && intField[5] == value.(int) {
isField = true
} else if S_INACTIVE&_field != 0 && intField[6] == value.(int) {
isField = true
} else if S_EXPIRE&_field != 0 && intField[7] == value.(int) {
isField = true
} else if S_FLAG&_field != 0 && intField[8] == value.(int) {
isField = true
} else if S_ALL&_field != 0 {
isField = true
}
if isField {
return &Shadow{
allField[0],
allField[1],
changed,
intField[3],
intField[4],
intField[5],
intField[6],
intField[7],
intField[8],
}
}
return nil
}
// LookupShadow looks for the entry for the given user name.
func LookupShadow(name string) (*Shadow, error) {
entries, err := LookupInShadow(S_NAME, name, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupInShadow looks up a shadowed password by the given values.
//
// The count determines the number of fields to return:
// n > 0: at most n fields
// n == 0: the result is nil (zero fields)
// n < 0: all fields
func LookupInShadow(field shadowField, value interface{}, n int) ([]*Shadow, error) {
checkRoot()
iEntries, err := lookUp(&Shadow{}, field, value, n)
if err != nil {
return nil, err
}
// == Convert to type shadow
valueSlice := reflect.ValueOf(iEntries)
entries := make([]*Shadow, valueSlice.Len())
for i := 0; i < valueSlice.Len(); i++ {
entries[i] = valueSlice.Index(i).Interface().(*Shadow)
}
return entries, err
}
// == Editing
//
// Add adds a new shadowed user.
// If the key is not nil, generates a hashed password.
//
// It is created a backup before of modify the original file.
func (s *Shadow) Add(key []byte) (err error) {
loadConfig()
shadow, err := LookupShadow(s.Name)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
return
}
}
if shadow != nil {
return ErrUserExist
}
if s.Name == "" {
return RequiredError("Name")
}
if s.Max == 0 {
return RequiredError("Max")
}
if s.Warn == 0 {
return RequiredError("Warn")
}
// Backup
if err = backup(_SHADOW_FILE); err != nil {
return
}
db, err := openDBFile(_SHADOW_FILE, os.O_WRONLY|os.O_APPEND)
if err != nil {
return
}
defer func() {
e := db.close()
if e != nil && err == nil {
err = e
}
}()
if key != nil {
s.password, _ = config.crypter.Generate(key, nil)
if s.changed == _ENABLE_AGING {
s.setChange()
}
} else {
s.password = "*" // Password disabled.
}
_, err = db.file.WriteString(s.String())
return
}

135
vendor/github.com/tredoe/osutil/user/shadow_test.go generated vendored Normal file
View file

@ -0,0 +1,135 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"bufio"
"io"
"os"
"testing"
)
func TestShadowParser(t *testing.T) {
f, err := os.Open(_SHADOW_FILE)
if err != nil {
t.Fatal(err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
break
}
t.Error(err)
continue
}
if _, err = parseShadow(string(line)); err != nil {
t.Error(err)
}
}
}
func TestShadowFull(t *testing.T) {
entry, err := LookupShadow("root")
if err != nil || entry == nil {
t.Error(err)
}
entries, err := LookupInShadow(S_PASSWD, "!", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInShadow(S_ALL, nil, -1)
if err != nil || len(entries) == 0 {
t.Error(err)
}
}
func TestShadowCount(t *testing.T) {
count := 2
entries, err := LookupInShadow(S_MIN, 0, count)
if err != nil || len(entries) != count {
t.Error(err)
}
count = 5
entries, err = LookupInShadow(S_ALL, nil, count)
if err != nil || len(entries) != count {
t.Error(err)
}
}
func TestShadowError(t *testing.T) {
_, err := LookupShadow("!!!???")
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to report NoFoundError")
}
if _, err = LookupInShadow(S_MIN, 0, 0); err != errSearch {
t.Error("expected to report errSearch")
}
s := &Shadow{}
if err = s.Add(nil); err != RequiredError("Name") {
t.Error("expected to report RequiredError")
}
}
func TestShadow_Add(t *testing.T) {
shadow := NewShadow(USER)
err := shadow.Add(nil)
if err != nil {
t.Fatal(err)
}
if err = shadow.Add(nil); err == nil {
t.Fatal("a shadowed user existent can not be added again")
} else {
if !IsExist(err) {
t.Error("shadow: expected to report ErrExist")
}
}
s, err := LookupShadow(USER)
if err != nil {
t.Fatal(err)
}
if s.Name != USER {
t.Errorf("shadow: expected to get name %q", USER)
}
}
var (
USER_KEY1 = []byte("123")
USER_KEY2 = []byte("456")
)
func TestShadowCrypt(t *testing.T) {
s, err := LookupShadow(USER)
if err != nil {
t.Fatal(err)
}
s.Passwd(USER_KEY1)
if err = config.crypter.Verify(s.password, USER_KEY1); err != nil {
t.Fatalf("expected to get the same hashed password for %q", USER_KEY1)
}
if err = ChPasswd(USER, USER_KEY2); err != nil {
t.Fatalf("expected to change password: %s", err)
}
s, _ = LookupShadow(USER)
if err = config.crypter.Verify(s.password, USER_KEY2); err != nil {
t.Fatalf("ChPasswd: expected to get the same hashed password for %q", USER_KEY2)
}
}

395
vendor/github.com/tredoe/osutil/user/user.go generated vendored Normal file
View file

@ -0,0 +1,395 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"fmt"
"os"
"path"
"reflect"
"strconv"
"strings"
)
type userField int
// Field names for user database.
const (
U_NAME userField = 1 << iota
U_PASSWD
U_UID
U_GID
U_GECOS
U_DIR
U_SHELL
U_ALL // To get lines without searching into a field.
)
func (f userField) String() string {
switch f {
case U_NAME:
return "Name"
case U_PASSWD:
return "Passwd"
case U_UID:
return "UID"
case U_GID:
return "GID"
case U_GECOS:
return "GECOS"
case U_DIR:
return "Dir"
case U_SHELL:
return "Shell"
}
return "ALL"
}
// An User represents an user account.
type User struct {
// Login name. (Unique)
Name string
// Optional hashed password
//
// The hashed password field may be blank, in which case no password is
// required to authenticate as the specified login name. However, some
// applications which read the '/etc/passwd' file may decide not to permit
// any access at all if the password field is blank. If the password field
// is a lower-case "x", then the encrypted password is actually stored in
// the "shadow(5)" file instead; there must be a corresponding line in the
// '/etc/shadow' file, or else the user account is invalid. If the password
// field is any other string, then it will be treated as an hashed password,
// as specified by "crypt(3)".
password string
// Numerical user ID. (Unique)
UID int
// Numerical group ID
GID int
// User name or comment field
//
// The comment field is used by various system utilities, such as "finger(1)".
Gecos string
// User home directory
//
// The home directory field provides the name of the initial working
// directory. The login program uses this information to set the value of
// the $HOME environmental variable.
Dir string
// Optional user command interpreter
//
// The command interpreter field provides the name of the user's command
// language interpreter, or the name of the initial program to execute.
// The login program uses this information to set the value of the "$SHELL"
// environmental variable. If this field is empty, it defaults to the value
// "/bin/sh".
Shell string
addSystemUser bool
}
// NewUser returns a new User with both fields "Dir" and "Shell" got from
// the system configuration.
func NewUser(name string, gid int) *User {
loadConfig()
return &User{
Name: name,
Dir: path.Join(config.useradd.HOME, name),
Shell: config.useradd.SHELL,
UID: -1,
GID: gid,
}
}
// NewSystemUser returns a new system user.
func NewSystemUser(name, homeDir string, gid int) *User {
return &User{
Name: name,
Dir: homeDir,
Shell: "/bin/false",
UID: -1,
GID: gid,
addSystemUser: true,
}
}
func (u *User) filename() string { return _USER_FILE }
// IsOfSystem indicates whether it is a system user.
func (u *User) IsOfSystem() bool {
//loadConfig()
if u.UID > config.login.SYS_UID_MIN && u.UID < config.login.SYS_UID_MAX {
return true
}
return false
}
func (u *User) String() string {
return fmt.Sprintf("%s:%s:%d:%d:%s:%s:%s\n",
u.Name, u.password, u.UID, u.GID, u.Gecos, u.Dir, u.Shell)
}
// parseUser parses the row of an user.
func parseUser(row string) (*User, error) {
fields := strings.Split(row, ":")
if len(fields) != 7 {
return nil, rowError{_USER_FILE, row}
}
uid, err := strconv.Atoi(fields[2])
if err != nil {
return nil, atoiError{_USER_FILE, row, "UID"}
}
gid, err := strconv.Atoi(fields[3])
if err != nil {
return nil, atoiError{_USER_FILE, row, "GID"}
}
return &User{
Name: fields[0],
password: fields[1],
UID: uid,
GID: gid,
Gecos: fields[4],
Dir: fields[5],
Shell: fields[6],
}, nil
}
// == Lookup
//
// lookUp parses the user line searching a value into the field.
// Returns nil if is not found.
func (*User) lookUp(line string, f field, value interface{}) interface{} {
_field := f.(userField)
allField := strings.Split(line, ":")
intField := make(map[int]int)
// Check integers
var err error
if intField[2], err = strconv.Atoi(allField[2]); err != nil {
panic(atoiError{_USER_FILE, line, "UID"})
}
if intField[3], err = strconv.Atoi(allField[3]); err != nil {
panic(atoiError{_USER_FILE, line, "GID"})
}
// Check fields
var isField bool
if U_NAME&_field != 0 && allField[0] == value.(string) {
isField = true
} else if U_PASSWD&_field != 0 && allField[1] == value.(string) {
isField = true
} else if U_UID&_field != 0 && intField[2] == value.(int) {
isField = true
} else if U_GID&_field != 0 && intField[3] == value.(int) {
isField = true
} else if U_GECOS&_field != 0 && allField[4] == value.(string) {
isField = true
} else if U_DIR&_field != 0 && allField[5] == value.(string) {
isField = true
} else if U_SHELL&_field != 0 && allField[6] == value.(string) {
isField = true
} else if U_ALL&_field != 0 {
isField = true
}
if isField {
return &User{
Name: allField[0],
password: allField[1],
UID: intField[2],
GID: intField[3],
Gecos: allField[4],
Dir: allField[5],
Shell: allField[6],
}
}
return nil
}
// LookupUID looks up an user by user ID.
func LookupUID(uid int) (*User, error) {
entries, err := LookupInUser(U_UID, uid, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupUser looks up an user by name.
func LookupUser(name string) (*User, error) {
entries, err := LookupInUser(U_NAME, name, 1)
if err != nil {
return nil, err
}
return entries[0], err
}
// LookupInUser looks up an user by the given values.
//
// The count determines the number of fields to return:
// n > 0: at most n fields
// n == 0: the result is nil (zero fields)
// n < 0: all fields
func LookupInUser(field userField, value interface{}, n int) ([]*User, error) {
iEntries, err := lookUp(&User{}, field, value, n)
if err != nil {
return nil, err
}
// == Convert to type user
valueSlice := reflect.ValueOf(iEntries)
entries := make([]*User, valueSlice.Len())
for i := 0; i < valueSlice.Len(); i++ {
entries[i] = valueSlice.Index(i).Interface().(*User)
}
return entries, err
}
// GetUsername returns the user name from the password database for the actual
// process.
// It panics whther there is an error at searching the UID.
func GetUsername() string {
entry, err := LookupUID(os.Getuid())
if err != nil {
panic(err)
}
return entry.Name
}
// GetUsernameFromEnv returns the user name from the environment variable
// for the actual process.
func GetUsernameFromEnv() string {
user_env := []string{"USER", "USERNAME", "LOGNAME", "LNAME"}
for _, val := range user_env {
name := os.Getenv(val)
if name != "" {
return name
}
}
return ""
}
// == Editing
//
// AddUser adds an user to both user and shadow files.
func AddUser(name string, gid int) (uid int, err error) {
s := NewShadow(name)
if err = s.Add(nil); err != nil {
return
}
return NewUser(name, gid).Add()
}
// AddSystemUser adds a system user to both user and shadow files.
func AddSystemUser(name, homeDir string, gid int) (uid int, err error) {
s := NewShadow(name)
if err = s.Add(nil); err != nil {
return
}
return NewSystemUser(name, homeDir, gid).Add()
}
// Add adds a new user.
// Whether UID is < 0, it will choose the first id available in the range set
// in the system configuration.
func (u *User) Add() (uid int, err error) {
loadConfig()
user, err := LookupUser(u.Name)
if err != nil {
if _, ok := err.(NoFoundError); !ok {
return
}
}
if user != nil {
return 0, ErrUserExist
}
if u.Name == "" {
return 0, RequiredError("Name")
}
if u.Dir == "" {
return 0, RequiredError("Dir")
}
if u.Dir == config.useradd.HOME {
return 0, HomeError(config.useradd.HOME)
}
if u.Shell == "" {
return 0, RequiredError("Shell")
}
var db *dbfile
if u.UID < 0 {
db, uid, err = nextUID(u.addSystemUser)
if err != nil {
db.close()
return 0, err
}
u.UID = uid
} else {
db, err = openDBFile(_USER_FILE, os.O_WRONLY|os.O_APPEND)
if err != nil {
return 0, err
}
// Check if Id is unique.
_, err = LookupUID(u.UID)
if err == nil {
return 0, IdUsedError(u.UID)
} else if _, ok := err.(NoFoundError); !ok {
return 0, err
}
}
u.password = "x"
_, err = db.file.WriteString(u.String())
err2 := db.close()
if err2 != nil && err == nil {
err = err2
}
return
}
// DelUser removes an user from the system.
func DelUser(name string) (err error) {
err = del(name, &User{})
if err == nil {
err = del(name, &Shadow{})
}
return
}
// == Errors
//
// A HomeError reports an error at adding an account with invalid home directory.
type HomeError string
func (e HomeError) Error() string {
return "invalid directory for the home directory of an account: " + string(e)
}

198
vendor/github.com/tredoe/osutil/user/user_test.go generated vendored Normal file
View file

@ -0,0 +1,198 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"bufio"
"io"
"os"
"testing"
)
func TestUserParser(t *testing.T) {
f, err := os.Open(_USER_FILE)
if err != nil {
t.Fatal(err)
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
break
}
t.Error(err)
continue
}
if _, err = parseUser(string(line)); err != nil {
t.Error(err)
}
}
}
func TestUserFull(t *testing.T) {
entry, err := LookupUID(os.Getuid())
if err != nil || entry == nil {
t.Error(err)
}
entry, err = LookupUser("root")
if err != nil || entry == nil {
t.Error(err)
}
entries, err := LookupInUser(U_GID, 65534, -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInUser(U_GECOS, "", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInUser(U_DIR, "/bin", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInUser(U_SHELL, "/bin/false", -1)
if err != nil || entries == nil {
t.Error(err)
}
entries, err = LookupInUser(U_ALL, nil, -1)
if err != nil || len(entries) == 0 {
t.Error(err)
}
}
func TestUserCount(t *testing.T) {
count := 2
entries, err := LookupInUser(U_SHELL, "/bin/false", count)
if err != nil || len(entries) != count {
t.Error(err)
}
count = 5
entries, err = LookupInUser(U_ALL, nil, count)
if err != nil || len(entries) != count {
t.Error(err)
}
}
func TestUserError(t *testing.T) {
_, err := LookupUser("!!!???")
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to report NoFoundError")
}
if _, err = LookupInUser(U_SHELL, "/bin/false", 0); err != errSearch {
t.Error("expected to report errSearch")
}
u := &User{}
if _, err = u.Add(); err != RequiredError("Name") {
t.Error("expected to report RequiredError")
}
u = &User{Name: USER, Dir: config.useradd.HOME, Shell: config.useradd.SHELL}
if _, err = u.Add(); err != HomeError(config.useradd.HOME) {
t.Error("expected to report HomeError")
}
}
func TestUser_Add(t *testing.T) {
user := NewUser(USER, GID)
user.Dir = "/tmp"
_testUser_Add(t, user, false)
user = NewSystemUser(SYS_USER, "/tmp", GID)
_testUser_Add(t, user, true)
}
func _testUser_Add(t *testing.T, user *User, ofSystem bool) {
prefix := "user"
if ofSystem {
prefix = "system " + prefix
}
id, err := user.Add()
if err != nil {
t.Fatal(err)
}
if id == -1 {
t.Errorf("%s: got UID = -1", prefix)
}
if _, err = user.Add(); err == nil {
t.Fatalf("%s: an existent user can not be added again", prefix)
} else {
if !IsExist(err) {
t.Errorf("%s: expected to report ErrExist", prefix)
}
}
if ofSystem {
if !user.IsOfSystem() {
t.Errorf("%s: IsOfSystem(): expected true")
}
} else {
if user.IsOfSystem() {
t.Errorf("%s: IsOfSystem(): expected false")
}
}
// Check value stored
name := ""
if ofSystem {
name = SYS_USER
} else {
name = USER
}
u, err := LookupUser(name)
if err != nil {
t.Fatalf("%s: ", err)
}
if u.Name != name {
t.Errorf("%s: expected to get name %q", prefix, name)
}
}
func TestUserLock(t *testing.T) {
err := LockUser(USER)
if err != nil {
t.Fatal(err)
}
s, err := LookupShadow(USER)
if err != nil {
t.Fatal(err)
}
if s.password[0] != _LOCK_CHAR {
t.Fatalf("expected to get password starting with '%c', got: '%c'",
_LOCK_CHAR, s.password[0])
}
err = UnlockUser(USER)
if err != nil {
t.Fatal(err)
}
s, err = LookupShadow(USER)
if err != nil {
t.Fatal(err)
}
if s.password[0] == _LOCK_CHAR {
t.Fatalf("no expected to get password starting with '%c'", _LOCK_CHAR)
}
}

43
vendor/github.com/tredoe/osutil/user/util.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import "os"
var isRoot bool
func init() {
// Root's user ID is 0.
if os.Getuid() == 0 {
isRoot = true
}
}
// checkRoot checks if the user is root.
func checkRoot() {
if !isRoot {
panic("you have to be Root")
}
}
// exist checks if the file exists.
func exist(file string) (bool, error) {
_, err := os.Stat(file)
if err != nil {
if err == os.ErrNotExist {
return false, nil
}
return false, err
}
return true, nil
}
// _SEC_PER_DAY is the number of secons that a day has.
const _SEC_PER_DAY = 24 * 60 * 60
// secToDay converts from secons to days.
func secToDay(sec int64) int { return int(sec / _SEC_PER_DAY) }

27
vendor/github.com/tredoe/osutil/user/util_test.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import (
"testing"
"time"
)
func TestSecToDays(t *testing.T) {
now := time.Now()
if now.Day() > 3 {
before := time.Date(now.Year(), now.Month(), now.Day()-3,
now.Hour(), now.Minute(), now.Second(), now.Nanosecond(),
time.Local)
diff := secToDay(now.Unix()) - secToDay(before.Unix())
if diff != 3 {
t.Fatalf("expected to get a difference of 3 days, got %d", diff)
}
}
}

45
vendor/github.com/tredoe/osutil/user/z_test.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
// Copyright 2012 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package user
import "testing"
func TestDelUser(t *testing.T) {
err := DelUser(USER)
if err != nil {
t.Fatal(err)
}
_, err = LookupUser(USER)
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to get error NoFoundError")
}
_, err = LookupShadow(USER)
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to get error NoFoundError")
}
}
func TestDelGroup(t *testing.T) {
err := DelGroup(GROUP)
if err != nil {
t.Fatal(err)
}
_, err = LookupGroup(GROUP)
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to get error NoFoundError")
}
_, err = LookupGShadow(GROUP)
if _, ok := err.(NoFoundError); !ok {
t.Error("expected to get error NoFoundError")
}
}
func TestZ(*testing.T) {
removeTempFiles()
}

Some files were not shown because too many files have changed in this diff Show more