mirror of
https://github.com/Luzifer/password.git
synced 2024-11-09 01:40:04 +00:00
Fix: Vendor new dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
495cb05264
commit
8af625be60
392 changed files with 81886 additions and 1 deletions
20
Gopkg.lock
generated
20
Gopkg.lock
generated
|
@ -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
5
vendor/github.com/Luzifer/go_helpers/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- tip
|
34
vendor/github.com/Luzifer/go_helpers/History.md
generated
vendored
Normal file
34
vendor/github.com/Luzifer/go_helpers/History.md
generated
vendored
Normal 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
|
37
vendor/github.com/Luzifer/go_helpers/accessLogger/accessLogger.go
generated
vendored
Normal file
37
vendor/github.com/Luzifer/go_helpers/accessLogger/accessLogger.go
generated
vendored
Normal 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
61
vendor/github.com/Luzifer/go_helpers/duration/time.go
generated
vendored
Normal 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
|
||||
}
|
35
vendor/github.com/Luzifer/go_helpers/duration/time_test.go
generated
vendored
Normal file
35
vendor/github.com/Luzifer/go_helpers/duration/time_test.go
generated
vendored
Normal 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
26
vendor/github.com/Luzifer/go_helpers/env/env.go
generated
vendored
Normal 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
|
||||
}
|
13
vendor/github.com/Luzifer/go_helpers/env/env_suite_test.go
generated
vendored
Normal file
13
vendor/github.com/Luzifer/go_helpers/env/env_suite_test.go
generated
vendored
Normal 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
55
vendor/github.com/Luzifer/go_helpers/env/env_test.go
generated
vendored
Normal 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))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
13
vendor/github.com/Luzifer/go_helpers/float/float_suite_test.go
generated
vendored
Normal file
13
vendor/github.com/Luzifer/go_helpers/float/float_suite_test.go
generated
vendored
Normal 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
14
vendor/github.com/Luzifer/go_helpers/float/round.go
generated
vendored
Normal 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)
|
||||
}
|
35
vendor/github.com/Luzifer/go_helpers/float/round_test.go
generated
vendored
Normal file
35
vendor/github.com/Luzifer/go_helpers/float/round_test.go
generated
vendored
Normal 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
213
vendor/github.com/Luzifer/go_helpers/github/updater.go
generated
vendored
Normal 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
|
||||
}
|
35
vendor/github.com/Luzifer/go_helpers/http/logHandler.go
generated
vendored
Normal file
35
vendor/github.com/Luzifer/go_helpers/http/logHandler.go
generated
vendored
Normal 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),
|
||||
)
|
||||
}
|
21
vendor/github.com/Luzifer/go_helpers/position/haversine.go
generated
vendored
Normal file
21
vendor/github.com/Luzifer/go_helpers/position/haversine.go
generated
vendored
Normal 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
|
||||
}
|
34
vendor/github.com/Luzifer/go_helpers/position/haversine_test.go
generated
vendored
Normal file
34
vendor/github.com/Luzifer/go_helpers/position/haversine_test.go
generated
vendored
Normal 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))
|
||||
}
|
||||
})
|
||||
|
||||
})
|
13
vendor/github.com/Luzifer/go_helpers/position/position_suite_test.go
generated
vendored
Normal file
13
vendor/github.com/Luzifer/go_helpers/position/position_suite_test.go
generated
vendored
Normal 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
21
vendor/github.com/Luzifer/go_helpers/str/slice.go
generated
vendored
Normal 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
52
vendor/github.com/Luzifer/go_helpers/str/slice_test.go
generated
vendored
Normal 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))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
13
vendor/github.com/Luzifer/go_helpers/str/str_suite_test.go
generated
vendored
Normal file
13
vendor/github.com/Luzifer/go_helpers/str/str_suite_test.go
generated
vendored
Normal 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
54
vendor/github.com/Luzifer/go_helpers/which/which.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
13
vendor/github.com/Luzifer/go_helpers/which/which_suite_test.go
generated
vendored
Normal file
13
vendor/github.com/Luzifer/go_helpers/which/which_suite_test.go
generated
vendored
Normal 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")
|
||||
}
|
63
vendor/github.com/Luzifer/go_helpers/which/which_test.go
generated
vendored
Normal file
63
vendor/github.com/Luzifer/go_helpers/which/which_test.go
generated
vendored
Normal 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
16
vendor/github.com/tredoe/osutil/AUTHORS.md
generated
vendored
Normal 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
18
vendor/github.com/tredoe/osutil/CONTRIBUTORS.md
generated
vendored
Normal 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
374
vendor/github.com/tredoe/osutil/LICENSE-MPL.txt
generated
vendored
Normal 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
25
vendor/github.com/tredoe/osutil/README.md
generated
vendored
Normal 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
36
vendor/github.com/tredoe/osutil/TODO.md
generated
vendored
Normal 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
74
vendor/github.com/tredoe/osutil/config/env/Info.md
generated
vendored
Normal 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
342
vendor/github.com/tredoe/osutil/config/env/env.go
generated
vendored
Normal 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
34
vendor/github.com/tredoe/osutil/config/env/env_test.go
generated
vendored
Normal 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
356
vendor/github.com/tredoe/osutil/config/shconf/scan.go
generated
vendored
Normal 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
|
||||
}
|
80
vendor/github.com/tredoe/osutil/config/shconf/scan_test.go
generated
vendored
Normal file
80
vendor/github.com/tredoe/osutil/config/shconf/scan_test.go
generated
vendored
Normal 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
213
vendor/github.com/tredoe/osutil/config/shconf/shconf.go
generated
vendored
Normal 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
|
||||
}
|
122
vendor/github.com/tredoe/osutil/config/shconf/shconf_test.go
generated
vendored
Normal file
122
vendor/github.com/tredoe/osutil/config/shconf/shconf_test.go
generated
vendored
Normal 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
86
vendor/github.com/tredoe/osutil/distro/distro.go
generated
vendored
Normal 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
15
vendor/github.com/tredoe/osutil/distro/distro_test.go
generated
vendored
Normal 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
17
vendor/github.com/tredoe/osutil/file/a_test.go
generated
vendored
Normal 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
17
vendor/github.com/tredoe/osutil/file/doc.go
generated
vendored
Normal 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
468
vendor/github.com/tredoe/osutil/file/edit.go
generated
vendored
Normal 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
162
vendor/github.com/tredoe/osutil/file/edit_test.go
generated
vendored
Normal 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
62
vendor/github.com/tredoe/osutil/file/find.go
generated
vendored
Normal 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
27
vendor/github.com/tredoe/osutil/file/find_test.go
generated
vendored
Normal 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
172
vendor/github.com/tredoe/osutil/file/info.go
generated
vendored
Normal 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
32
vendor/github.com/tredoe/osutil/file/info_test.go
generated
vendored
Normal 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
185
vendor/github.com/tredoe/osutil/file/io.go
generated
vendored
Normal 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
68
vendor/github.com/tredoe/osutil/file/io_test.go
generated
vendored
Normal 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
18
vendor/github.com/tredoe/osutil/file/z_test.go
generated
vendored
Normal 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
48
vendor/github.com/tredoe/osutil/osutil.go
generated
vendored
Normal 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
17
vendor/github.com/tredoe/osutil/osutil_test.go
generated
vendored
Normal 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
39
vendor/github.com/tredoe/osutil/pkgutil/Info.md
generated
vendored
Normal 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
46
vendor/github.com/tredoe/osutil/pkgutil/deb.go
generated
vendored
Normal 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
42
vendor/github.com/tredoe/osutil/pkgutil/ebuild.go
generated
vendored
Normal 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
41
vendor/github.com/tredoe/osutil/pkgutil/pacman.go
generated
vendored
Normal 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
112
vendor/github.com/tredoe/osutil/pkgutil/pkg.go
generated
vendored
Normal 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
36
vendor/github.com/tredoe/osutil/pkgutil/pkg_test.go
generated
vendored
Normal 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
39
vendor/github.com/tredoe/osutil/pkgutil/rpm.go
generated
vendored
Normal 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
39
vendor/github.com/tredoe/osutil/pkgutil/zypp.go
generated
vendored
Normal 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
72
vendor/github.com/tredoe/osutil/sh/log.go
generated
vendored
Normal 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
305
vendor/github.com/tredoe/osutil/sh/sh.go
generated
vendored
Normal 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
90
vendor/github.com/tredoe/osutil/sh/sh_test.go
generated
vendored
Normal 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
56
vendor/github.com/tredoe/osutil/user/README.md
generated
vendored
Normal 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
68
vendor/github.com/tredoe/osutil/user/a_test.go
generated
vendored
Normal 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
283
vendor/github.com/tredoe/osutil/user/config_linux.go
generated
vendored
Normal 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
16
vendor/github.com/tredoe/osutil/user/config_test.go
generated
vendored
Normal 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
145
vendor/github.com/tredoe/osutil/user/crypt.go
generated
vendored
Normal 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
|
||||
}
|
8
vendor/github.com/tredoe/osutil/user/crypt/AUTHORS.md
generated
vendored
Normal file
8
vendor/github.com/tredoe/osutil/user/crypt/AUTHORS.md
generated
vendored
Normal 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
27
vendor/github.com/tredoe/osutil/user/crypt/LICENSE
generated
vendored
Normal 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
25
vendor/github.com/tredoe/osutil/user/crypt/README.md
generated
vendored
Normal 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.
|
62
vendor/github.com/tredoe/osutil/user/crypt/apr1_crypt/apr1_crypt.go
generated
vendored
Normal file
62
vendor/github.com/tredoe/osutil/user/crypt/apr1_crypt/apr1_crypt.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
88
vendor/github.com/tredoe/osutil/user/crypt/apr1_crypt/apr1_crypt_test.go
generated
vendored
Normal file
88
vendor/github.com/tredoe/osutil/user/crypt/apr1_crypt/apr1_crypt_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
60
vendor/github.com/tredoe/osutil/user/crypt/common/base64.go
generated
vendored
Normal file
60
vendor/github.com/tredoe/osutil/user/crypt/common/base64.go
generated
vendored
Normal 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
|
||||
}
|
13
vendor/github.com/tredoe/osutil/user/crypt/common/doc.go
generated
vendored
Normal file
13
vendor/github.com/tredoe/osutil/user/crypt/common/doc.go
generated
vendored
Normal 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
|
105
vendor/github.com/tredoe/osutil/user/crypt/common/salt.go
generated
vendored
Normal file
105
vendor/github.com/tredoe/osutil/user/crypt/common/salt.go
generated
vendored
Normal 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
|
||||
}
|
37
vendor/github.com/tredoe/osutil/user/crypt/common/salt_test.go
generated
vendored
Normal file
37
vendor/github.com/tredoe/osutil/user/crypt/common/salt_test.go
generated
vendored
Normal 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
108
vendor/github.com/tredoe/osutil/user/crypt/crypt.go
generated
vendored
Normal 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")
|
||||
}
|
166
vendor/github.com/tredoe/osutil/user/crypt/md5_crypt/md5_crypt.go
generated
vendored
Normal file
166
vendor/github.com/tredoe/osutil/user/crypt/md5_crypt/md5_crypt.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
88
vendor/github.com/tredoe/osutil/user/crypt/md5_crypt/md5_crypt_test.go
generated
vendored
Normal file
88
vendor/github.com/tredoe/osutil/user/crypt/md5_crypt/md5_crypt_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
243
vendor/github.com/tredoe/osutil/user/crypt/sha256_crypt/sha256_crypt.go
generated
vendored
Normal file
243
vendor/github.com/tredoe/osutil/user/crypt/sha256_crypt/sha256_crypt.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
109
vendor/github.com/tredoe/osutil/user/crypt/sha256_crypt/sha256_crypt_test.go
generated
vendored
Normal file
109
vendor/github.com/tredoe/osutil/user/crypt/sha256_crypt/sha256_crypt_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
254
vendor/github.com/tredoe/osutil/user/crypt/sha512_crypt/sha512_crypt.go
generated
vendored
Normal file
254
vendor/github.com/tredoe/osutil/user/crypt/sha512_crypt/sha512_crypt.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
111
vendor/github.com/tredoe/osutil/user/crypt/sha512_crypt/sha512_crypt_test.go
generated
vendored
Normal file
111
vendor/github.com/tredoe/osutil/user/crypt/sha512_crypt/sha512_crypt_test.go
generated
vendored
Normal 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
16
vendor/github.com/tredoe/osutil/user/crypt_test.go
generated
vendored
Normal 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
16
vendor/github.com/tredoe/osutil/user/dbfile.go
generated
vendored
Normal 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
16
vendor/github.com/tredoe/osutil/user/doc.go
generated
vendored
Normal 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
72
vendor/github.com/tredoe/osutil/user/error.go
generated
vendored
Normal 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
194
vendor/github.com/tredoe/osutil/user/file.go
generated
vendored
Normal 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
494
vendor/github.com/tredoe/osutil/user/group.go
generated
vendored
Normal 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
234
vendor/github.com/tredoe/osutil/user/group_test.go
generated
vendored
Normal 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
237
vendor/github.com/tredoe/osutil/user/gshadow.go
generated
vendored
Normal 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
129
vendor/github.com/tredoe/osutil/user/gshadow_test.go
generated
vendored
Normal 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
179
vendor/github.com/tredoe/osutil/user/id.go
generated
vendored
Normal 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
46
vendor/github.com/tredoe/osutil/user/id_test.go
generated
vendored
Normal 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
477
vendor/github.com/tredoe/osutil/user/shadow.go
generated
vendored
Normal 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
135
vendor/github.com/tredoe/osutil/user/shadow_test.go
generated
vendored
Normal 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
395
vendor/github.com/tredoe/osutil/user/user.go
generated
vendored
Normal 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
198
vendor/github.com/tredoe/osutil/user/user_test.go
generated
vendored
Normal 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
43
vendor/github.com/tredoe/osutil/user/util.go
generated
vendored
Normal 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
27
vendor/github.com/tredoe/osutil/user/util_test.go
generated
vendored
Normal 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
45
vendor/github.com/tredoe/osutil/user/z_test.go
generated
vendored
Normal 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
Loading…
Reference in a new issue